From ab48934e9cefb510d39ba3fe8c0dcf7619bec4cf Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 19 Feb 2018 23:06:54 +0100 Subject: [PATCH 01/11] add support for stack traces on macosx Add basic address->symbol resolution support. Uses symtab data from the MachO image, not external dSYM data; that's left as a future exercise. The net effect is that we can now map addresses to function names but not much more. File names and line number data will have to wait until a future pull request. Partially fixes #434. --- CMakeLists.txt | 1 + std/debug/index.zig | 148 ++++++++++++++++++++++-------------- std/index.zig | 2 + std/macho.zig | 177 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 271 insertions(+), 57 deletions(-) create mode 100644 std/macho.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index bdc3465830..2856a20606 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,6 +386,7 @@ set(ZIG_STD_FILES "index.zig" "io.zig" "linked_list.zig" + "macho.zig" "math/acos.zig" "math/acosh.zig" "math/asin.zig" diff --git a/std/debug/index.zig b/std/debug/index.zig index cc4832b1ea..2418654986 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -5,6 +5,7 @@ const io = std.io; const os = std.os; const elf = std.elf; const DW = std.dwarf; +const macho = std.macho; const ArrayList = std.ArrayList; const builtin = @import("builtin"); @@ -180,43 +181,57 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator, } fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void { - if (builtin.os == builtin.Os.windows) { - return error.UnsupportedDebugInfo; - } // TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal // at compile time. I'll call it issue #313 const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}"; - const compile_unit = findCompileUnit(debug_info, address) catch { - try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", - address); - return; - }; - const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); - if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { - defer line_info.deinit(); - try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ - DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", - line_info.file_name, line_info.line, line_info.column, - address, compile_unit_name); - if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { - if (line_info.column == 0) { - try out_stream.write("\n"); - } else { - {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { - try out_stream.writeByte(' '); - }} - try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); - } - } else |err| switch (err) { - error.EndOfFile => {}, - else => return err, - } - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name); + switch (builtin.os) { + builtin.Os.windows => return error.UnsupportedDebugInfo, + builtin.Os.macosx => { + // TODO(bnoordhuis) It's theoretically possible to obtain the + // compilation unit from the symbtab but it's not that useful + // in practice because the compiler dumps everything in a single + // object file. Future improvement: use external dSYM data when + // available. + const unknown = macho.Symbol { .name = "???", .address = address }; + const symbol = debug_info.symbol_table.search(address) ?? &unknown; + try out_stream.print(WHITE ++ "{}" ++ RESET ++ ": " ++ + DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n", + symbol.name, address); + }, + else => { + const compile_unit = findCompileUnit(debug_info, address) catch { + try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", + address); + return; + }; + const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); + if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { + defer line_info.deinit(); + try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ + DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", + line_info.file_name, line_info.line, line_info.column, + address, compile_unit_name); + if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { + if (line_info.column == 0) { + try out_stream.write("\n"); + } else { + {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { + try out_stream.writeByte(' '); + }} + try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); + } + } else |err| switch (err) { + error.EndOfFile => {}, + else => return err, + } + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name); + }, + else => return err, + } }, - else => return err, } } @@ -249,12 +264,22 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { try scanAllCompileUnits(st); return st; }, + builtin.ObjectFormat.macho => { + var exe_file = try os.openSelfExe(); + defer exe_file.close(); + + const st = try allocator.create(ElfStackTrace); + errdefer allocator.destroy(st); + + *st = ElfStackTrace { + .symbol_table = try macho.loadSymbols(allocator, &io.FileInStream.init(&exe_file)), + }; + + return st; + }, builtin.ObjectFormat.coff => { return error.TodoSupportCoffDebugInfo; }, - builtin.ObjectFormat.macho => { - return error.TodoSupportMachoDebugInfo; - }, builtin.ObjectFormat.wasm => { return error.TodoSupportCOFFDebugInfo; }, @@ -297,31 +322,40 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con } } -pub const ElfStackTrace = struct { - self_exe_file: os.File, - elf: elf.Elf, - debug_info: &elf.SectionHeader, - debug_abbrev: &elf.SectionHeader, - debug_str: &elf.SectionHeader, - debug_line: &elf.SectionHeader, - debug_ranges: ?&elf.SectionHeader, - abbrev_table_list: ArrayList(AbbrevTableHeader), - compile_unit_list: ArrayList(CompileUnit), +pub const ElfStackTrace = switch (builtin.os) { + builtin.Os.macosx => struct { + symbol_table: macho.SymbolTable, - pub fn allocator(self: &const ElfStackTrace) &mem.Allocator { - return self.abbrev_table_list.allocator; - } + pub fn close(self: &ElfStackTrace) void { + self.symbol_table.deinit(); + } + }, + else => struct { + self_exe_file: os.File, + elf: elf.Elf, + debug_info: &elf.SectionHeader, + debug_abbrev: &elf.SectionHeader, + debug_str: &elf.SectionHeader, + debug_line: &elf.SectionHeader, + debug_ranges: ?&elf.SectionHeader, + abbrev_table_list: ArrayList(AbbrevTableHeader), + compile_unit_list: ArrayList(CompileUnit), - pub fn readString(self: &ElfStackTrace) ![]u8 { - var in_file_stream = io.FileInStream.init(&self.self_exe_file); - const in_stream = &in_file_stream.stream; - return readStringRaw(self.allocator(), in_stream); - } + pub fn allocator(self: &const ElfStackTrace) &mem.Allocator { + return self.abbrev_table_list.allocator; + } - pub fn close(self: &ElfStackTrace) void { - self.self_exe_file.close(); - self.elf.close(); - } + pub fn readString(self: &ElfStackTrace) ![]u8 { + var in_file_stream = io.FileInStream.init(&self.self_exe_file); + const in_stream = &in_file_stream.stream; + return readStringRaw(self.allocator(), in_stream); + } + + pub fn close(self: &ElfStackTrace) void { + self.self_exe_file.close(); + self.elf.close(); + } + }, }; const PcRange = struct { diff --git a/std/index.zig b/std/index.zig index 8d292c2f5c..179eae159e 100644 --- a/std/index.zig +++ b/std/index.zig @@ -21,6 +21,7 @@ pub const endian = @import("endian.zig"); pub const fmt = @import("fmt/index.zig"); pub const heap = @import("heap.zig"); pub const io = @import("io.zig"); +pub const macho = @import("macho.zig"); pub const math = @import("math/index.zig"); pub const mem = @import("mem.zig"); pub const net = @import("net.zig"); @@ -51,6 +52,7 @@ test "std" { _ = @import("endian.zig"); _ = @import("fmt/index.zig"); _ = @import("io.zig"); + _ = @import("macho.zig"); _ = @import("math/index.zig"); _ = @import("mem.zig"); _ = @import("heap.zig"); diff --git a/std/macho.zig b/std/macho.zig new file mode 100644 index 0000000000..05239bf191 --- /dev/null +++ b/std/macho.zig @@ -0,0 +1,177 @@ +const builtin = @import("builtin"); +const std = @import("index.zig"); +const io = std.io; +const mem = std.mem; + +const MH_MAGIC_64 = 0xFEEDFACF; +const MH_PIE = 0x200000; +const LC_SYMTAB = 2; + +const MachHeader64 = packed struct { + magic: u32, + cputype: u32, + cpusubtype: u32, + filetype: u32, + ncmds: u32, + sizeofcmds: u32, + flags: u32, + reserved: u32, +}; + +const LoadCommand = packed struct { + cmd: u32, + cmdsize: u32, +}; + +const SymtabCommand = packed struct { + symoff: u32, + nsyms: u32, + stroff: u32, + strsize: u32, +}; + +const Nlist64 = packed struct { + n_strx: u32, + n_type: u8, + n_sect: u8, + n_desc: u16, + n_value: u64, +}; + +pub const Symbol = struct { + name: []const u8, + address: u64, + + fn addressLessThan(lhs: &const Symbol, rhs: &const Symbol) bool { + return lhs.address < rhs.address; + } +}; + +pub const SymbolTable = struct { + allocator: &mem.Allocator, + symbols: []const Symbol, + strings: []const u8, + + // Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols(). + // Ideally we'd use _mh_execute_header because it's always at 0x100000000 + // in the image but as it's located in a different section than executable + // code, its displacement is different. + pub fn deinit(self: &SymbolTable) void { + self.allocator.free(self.symbols); + self.symbols = []const Symbol {}; + + self.allocator.free(self.strings); + self.strings = []const u8 {}; + } + + pub fn search(self: &const SymbolTable, address: usize) ?&const Symbol { + var min: usize = 0; + var max: usize = self.symbols.len - 1; // Exclude sentinel. + while (min < max) { + const mid = min + (max - min) / 2; + const curr = &self.symbols[mid]; + const next = &self.symbols[mid + 1]; + if (address >= next.address) { + min = mid + 1; + } else if (address < curr.address) { + max = mid; + } else { + return curr; + } + } + return null; + } +}; + +pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable { + var file = in.file; + try file.seekTo(0); + + var hdr: MachHeader64 = undefined; + try readNoEof(in, &hdr); + if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo; + const is_pie = MH_PIE == (hdr.flags & MH_PIE); + + var pos: usize = @sizeOf(@typeOf(hdr)); + var ncmd: u32 = hdr.ncmds; + while (ncmd != 0) : (ncmd -= 1) { + try file.seekTo(pos); + var lc: LoadCommand = undefined; + try readNoEof(in, &lc); + if (lc.cmd == LC_SYMTAB) break; + pos += lc.cmdsize; + } else { + return error.MissingDebugInfo; + } + + var cmd: SymtabCommand = undefined; + try readNoEof(in, &cmd); + + try file.seekTo(cmd.symoff); + var syms = try allocator.alloc(Nlist64, cmd.nsyms); + defer allocator.free(syms); + try readNoEof(in, syms); + + try file.seekTo(cmd.stroff); + var strings = try allocator.alloc(u8, cmd.strsize); + errdefer allocator.free(strings); + try in.stream.readNoEof(strings); + + var nsyms: usize = 0; + for (syms) |sym| if (isSymbol(sym)) nsyms += 1; + if (nsyms == 0) return error.MissingDebugInfo; + + var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel. + errdefer allocator.free(symbols); + + var pie_slide: usize = 0; + var nsym: usize = 0; + for (syms) |sym| { + if (!isSymbol(sym)) continue; + const start = sym.n_strx; + const end = ??mem.indexOfScalarPos(u8, strings, start, 0); + const name = strings[start..end]; + const address = sym.n_value; + symbols[nsym] = Symbol { .name = name, .address = address }; + nsym += 1; + if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) { + pie_slide = @ptrToInt(SymbolTable.deinit) - address; + } + } + + // Effectively a no-op, lld emits symbols in ascending order. + std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan); + + // Insert the sentinel. Since we don't know where the last function ends, + // we arbitrarily limit it to the start address + 4 KB. + const top = symbols[nsyms - 1].address + 4096; + symbols[nsyms] = Symbol { .name = "", .address = top }; + + if (pie_slide != 0) { + for (symbols) |*symbol| symbol.address += pie_slide; + } + + return SymbolTable { + .allocator = allocator, + .symbols = symbols, + .strings = strings, + }; +} + +fn readNoEof(in: &io.FileInStream, sink: var) !void { + if (@typeOf(sink) == []Nlist64) { + const T = @typeOf(sink[0]); + const len = @sizeOf(T) * sink.len; + const bytes = @ptrCast(&u8, &sink[0]); + return in.stream.readNoEof(bytes[0..len]); + } else { + const T = @typeOf(*sink); + const len = @sizeOf(T); + const bytes = @ptrCast(&u8, sink); + return in.stream.readNoEof(bytes[0..len]); + } +} + +fn isSymbol(sym: &const Nlist64) bool { + return sym.n_value != 0 and sym.n_desc == 0; +} From 2b35615ffbe238c8ec421654a7e1ae0890477fe0 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 19 Feb 2018 23:06:54 +0100 Subject: [PATCH 02/11] fix memory leak in std.debug.openSelfDebugInfo() --- std/debug/index.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/std/debug/index.zig b/std/debug/index.zig index 2418654986..5de201b0e6 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -239,6 +239,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { switch (builtin.object_format) { builtin.ObjectFormat.elf => { const st = try allocator.create(ElfStackTrace); + errdefer allocator.destroy(st); *st = ElfStackTrace { .self_exe_file = undefined, .elf = undefined, From 623466762eba820f263a40622d70dc46ba0cb8ab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Feb 2018 02:00:33 -0500 Subject: [PATCH 03/11] clean up mach-o stack trace code --- std/macho.zig | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/std/macho.zig b/std/macho.zig index 05239bf191..70e2c09788 100644 --- a/std/macho.zig +++ b/std/macho.zig @@ -88,7 +88,7 @@ pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable try file.seekTo(0); var hdr: MachHeader64 = undefined; - try readNoEof(in, &hdr); + try readOneNoEof(in, MachHeader64, &hdr); if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo; const is_pie = MH_PIE == (hdr.flags & MH_PIE); @@ -97,7 +97,7 @@ pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable while (ncmd != 0) : (ncmd -= 1) { try file.seekTo(pos); var lc: LoadCommand = undefined; - try readNoEof(in, &lc); + try readOneNoEof(in, LoadCommand, &lc); if (lc.cmd == LC_SYMTAB) break; pos += lc.cmdsize; } else { @@ -105,12 +105,12 @@ pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable } var cmd: SymtabCommand = undefined; - try readNoEof(in, &cmd); + try readOneNoEof(in, SymtabCommand, &cmd); try file.seekTo(cmd.symoff); var syms = try allocator.alloc(Nlist64, cmd.nsyms); defer allocator.free(syms); - try readNoEof(in, syms); + try readNoEof(in, Nlist64, syms); try file.seekTo(cmd.stroff); var strings = try allocator.alloc(u8, cmd.strsize); @@ -158,18 +158,11 @@ pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable }; } -fn readNoEof(in: &io.FileInStream, sink: var) !void { - if (@typeOf(sink) == []Nlist64) { - const T = @typeOf(sink[0]); - const len = @sizeOf(T) * sink.len; - const bytes = @ptrCast(&u8, &sink[0]); - return in.stream.readNoEof(bytes[0..len]); - } else { - const T = @typeOf(*sink); - const len = @sizeOf(T); - const bytes = @ptrCast(&u8, sink); - return in.stream.readNoEof(bytes[0..len]); - } +fn readNoEof(in: &io.FileInStream, comptime T: type, result: []T) !void { + return in.stream.readNoEof(([]u8)(result)); +} +fn readOneNoEof(in: &io.FileInStream, comptime T: type, result: &T) !void { + return readNoEof(in, T, result[0..1]); } fn isSymbol(sym: &const Nlist64) bool { From 0845cbe27783486feb5b4b57b2839326a2c86a6b Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 22 Feb 2018 17:53:58 +0100 Subject: [PATCH 04/11] name types inside functions after variable Before this commit: fn f() []const u8 { const S = struct {}; return @typeName(S); // "f()", unexpected. } And now: fn f() []const u8 { const S = struct {}; return @typeName(S); // "S", expected. } Fixes #675. --- src/ir.cpp | 6 ++++++ std/fmt/index.zig | 10 ++++------ test/cases/misc.zig | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 7eac9e4d23..b276abff33 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -4172,7 +4172,13 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol))); } + // Temporarily set the name of the IrExecutable to the VariableDeclaration + // so that the struct or enum from the init expression inherits the name. + Buf *old_exec_name = irb->exec->name; + irb->exec->name = variable_declaration->symbol; IrInstruction *init_value = ir_gen_node(irb, variable_declaration->expr, scope); + irb->exec->name = old_exec_name; + if (init_value == irb->codegen->invalid_instruction) return init_value; diff --git a/std/fmt/index.zig b/std/fmt/index.zig index 56b0add86d..bd5b5710e0 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -550,12 +550,6 @@ test "parse unsigned comptime" { } } -// Dummy field because of https://github.com/zig-lang/zig/issues/557. -// At top level because of https://github.com/zig-lang/zig/issues/675. -const Struct = struct { - unused: u8, -}; - test "fmt.format" { { var buf1: [32]u8 = undefined; @@ -588,6 +582,10 @@ test "fmt.format" { assert(mem.eql(u8, result, "u3: 5\n")); } { + // Dummy field because of https://github.com/zig-lang/zig/issues/557. + const Struct = struct { + unused: u8, + }; var buf1: [32]u8 = undefined; const value = Struct { .unused = 42, diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 964c5babc1..5e453fcbc1 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -499,12 +499,29 @@ test "@canImplicitCast" { } test "@typeName" { + const Struct = struct { + }; + const Union = union { + unused: u8, + }; + const Enum = enum { + Unused, + }; comptime { assert(mem.eql(u8, @typeName(i64), "i64")); assert(mem.eql(u8, @typeName(&usize), "&usize")); + // https://github.com/zig-lang/zig/issues/675 + assert(mem.eql(u8, @typeName(TypeFromFn(u8)), "TypeFromFn(u8)")); + assert(mem.eql(u8, @typeName(Struct), "Struct")); + assert(mem.eql(u8, @typeName(Union), "Union")); + assert(mem.eql(u8, @typeName(Enum), "Enum")); } } +fn TypeFromFn(comptime T: type) type { + return struct {}; +} + test "volatile load and store" { var number: i32 = 1234; const ptr = (&volatile i32)(&number); From f11b9480192dea44acb92cb9edd7a91c7c73cd2f Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 23 Feb 2018 15:25:42 +0100 Subject: [PATCH 05/11] allow implicit cast from `S` to `?&const S` Allow implicit casts from container types to nullable const pointers to said container type. That is: fn f() void { const s = S {}; g(s); // Works. g(&s); // So does this. } fn g(_: ?&const S) void { // Nullable const pointer. } Fixes #731. --- src/ir.cpp | 17 +++++++++-- test/cases/cast.zig | 71 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index b276abff33..e79235830c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8817,16 +8817,29 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst // explicit cast from child type of maybe type to maybe type if (wanted_type->id == TypeTableEntryIdMaybe) { - if (types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, actual_type, source_node).id == ConstCastResultIdOk) { + TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type; + if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node).id == ConstCastResultIdOk) { return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); } else if (actual_type->id == TypeTableEntryIdNumLitInt || actual_type->id == TypeTableEntryIdNumLitFloat) { - if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.maybe.child_type, true)) { + if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) { return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); } else { return ira->codegen->invalid_instruction; } + } else if (wanted_child_type->id == TypeTableEntryIdPointer && + wanted_child_type->data.pointer.is_const && + is_container(actual_type)) { + IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value); + if (type_is_invalid(cast1->value.type)) + return ira->codegen->invalid_instruction; + + IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); + if (type_is_invalid(cast2->value.type)) + return ira->codegen->invalid_instruction; + + return cast2; } } diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 6ffb558174..dabf97a799 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -32,6 +32,77 @@ fn funcWithConstPtrPtr(x: &const &i32) void { **x += 1; } +test "implicitly cast a container to a const pointer of it" { + const z = Struct(void) { .x = void{} }; + assert(0 == @sizeOf(@typeOf(z))); + assert(void{} == Struct(void).pointer(z).x); + assert(void{} == Struct(void).pointer(&z).x); + assert(void{} == Struct(void).maybePointer(z).x); + assert(void{} == Struct(void).maybePointer(&z).x); + assert(void{} == Struct(void).maybePointer(null).x); + const s = Struct(u8) { .x = 42 }; + assert(0 != @sizeOf(@typeOf(s))); + assert(42 == Struct(u8).pointer(s).x); + assert(42 == Struct(u8).pointer(&s).x); + assert(42 == Struct(u8).maybePointer(s).x); + assert(42 == Struct(u8).maybePointer(&s).x); + assert(0 == Struct(u8).maybePointer(null).x); + const u = Union { .x = 42 }; + assert(42 == Union.pointer(u).x); + assert(42 == Union.pointer(&u).x); + assert(42 == Union.maybePointer(u).x); + assert(42 == Union.maybePointer(&u).x); + assert(0 == Union.maybePointer(null).x); + const e = Enum.Some; + assert(Enum.Some == Enum.pointer(e)); + assert(Enum.Some == Enum.pointer(&e)); + assert(Enum.Some == Enum.maybePointer(e)); + assert(Enum.Some == Enum.maybePointer(&e)); + assert(Enum.None == Enum.maybePointer(null)); +} + +fn Struct(comptime T: type) type { + return struct { + const Self = this; + x: T, + + fn pointer(self: &const Self) Self { + return *self; + } + + fn maybePointer(self: ?&const Self) Self { + const none = Self { .x = if (T == void) void{} else 0 }; + return *(self ?? &none); + } + }; +} + +const Union = union { + x: u8, + + fn pointer(self: &const Union) Union { + return *self; + } + + fn maybePointer(self: ?&const Union) Union { + const none = Union { .x = 0 }; + return *(self ?? &none); + } +}; + +const Enum = enum { + None, + Some, + + fn pointer(self: &const Enum) Enum { + return *self; + } + + fn maybePointer(self: ?&const Enum) Enum { + return *(self ?? &Enum.None); + } +}; + test "explicit cast from integer to error type" { testCastIntToErr(error.ItBroke); comptime testCastIntToErr(error.ItBroke); From 8db7a1420f892deb4a2a3c63b1a3227b65aadccf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 23 Feb 2018 20:43:47 -0500 Subject: [PATCH 06/11] update errors section of docs closes #768 --- doc/langref.html.in | 103 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 17 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 2b09ca81bd..abbebaa6fb 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2782,30 +2782,96 @@ test "fn reflection" { {#header_close#} {#header_close#} {#header_open|Errors#} + {#header_open|Error Set Type#}

- One of the distinguishing features of Zig is its exception handling strategy. + An error set is like an {#link|enum#}. + However, each error name across the entire compilation gets assigned an unsigned integer + greater than 0. You are allowed to declare the same error name more than once, and if you do, it + gets assigned the same integer value.

- TODO rewrite the errors section to take into account error sets + The number of unique error values across the entire compilation should determine the size of the error set type. + However right now it is hard coded to be a u16. See #768.

- These error values are assigned an unsigned integer value greater than 0 at - compile time. You are allowed to declare the same error value more than once, - and if you do, it gets assigned the same integer value. + You can implicitly cast an error from a subset to its superset: +

+ {#code_begin|test#} +const std = @import("std"); + +const FileOpenError = error { + AccessDenied, + OutOfMemory, + FileNotFound, +}; + +const AllocationError = error { + OutOfMemory, +}; + +test "implicit cast subset to superset" { + const err = foo(AllocationError.OutOfMemory); + std.debug.assert(err == FileOpenError.OutOfMemory); +} + +fn foo(err: AllocationError) FileOpenError { + return err; +} + {#code_end#} +

+ But you cannot implicitly cast an error from a superset to a subset: +

+ {#code_begin|test_err|not a member of destination error set#} +const FileOpenError = error { + AccessDenied, + OutOfMemory, + FileNotFound, +}; + +const AllocationError = error { + OutOfMemory, +}; + +test "implicit cast superset to subset" { + foo(FileOpenError.OutOfMemory) catch {}; +} + +fn foo(err: FileOpenError) AllocationError { + return err; +} + {#code_end#} +

+ There is a shortcut for declaring an error set with only 1 value, and then getting that value: +

+ {#code_begin|syntax#} +const err = error.FileNotFound; + {#code_end#} +

This is equivalent to:

+ {#code_begin|syntax#} +const err = (error {FileNotFound}).FileNotFound; + {#code_end#} +

+ This becomes useful when using {#link|Inferred Error Sets#}. +

+ {#header_open|The Global Error Set#} +

error refers to the global error set. + This is the error set that contains all errors in the entire compilation unit. + It is a superset of all other error sets and a subset of none of them.

- You can refer to these error values with the error namespace such as - error.FileNotFound. + You can implicitly cast any error set to the global one, and you can explicitly + cast an error of global error set to a non-global one. This inserts a language-level + assert to make sure the error value is in fact in the destination error set.

- Each error value across the entire compilation unit gets a unique integer, - and this determines the size of the error set type. + The global error set should generally be avoided when possible, because it prevents + the compiler from knowing what errors are possible at compile-time. Knowing + the error set at compile-time is better for generated documentationt and for + helpful error messages such as forgetting a possible error value in a {#link|switch#}.

-

- The error set type is one of the error values, and in the same way that pointers - cannot be null, a error set instance is always an error. -

- {#code_begin|syntax#}const pure_error = error.FileNotFound;{#code_end#} + {#header_close#} + {#header_close#} + {#header_open|Error Union Type#}

Most of the time you will not find yourself using an error set type. Instead, likely you will be using the error union type. This is when you take an error set @@ -2918,7 +2984,6 @@ fn doAThing(str: []u8) !void { a panic in Debug and ReleaseSafe modes and undefined behavior in ReleaseFast mode. So, while we're debugging the application, if there was a surprise error here, the application would crash appropriately. - TODO: mention error return traces

Finally, you may want to take a different action for every situation. For that, we combine @@ -2986,7 +3051,7 @@ fn createFoo(param: i32) !Foo { {#see_also|defer|if|switch#} - {#header_open|Error Union Type#} +

An error union is created with the ! binary operator. You can use compile-time reflection to access the child type of an error union:

{#code_begin|test#} @@ -3008,8 +3073,12 @@ test "error union" { comptime assert(@typeOf(foo).ErrorSet == error); } {#code_end#} +

TODO the || operator for error sets

+ {#header_open|Inferred Error Sets#} +

TODO

{#header_close#} - {#header_open|Error Set Type#} + {#header_close#} + {#header_open|Error Return Traces#}

TODO

{#header_close#} {#header_close#} From 08d595b4724217f0b2b10cd3b9e31a71698cf5c1 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Fri, 23 Feb 2018 21:20:15 +1300 Subject: [PATCH 07/11] Add utf8 string view --- std/unicode.zig | 149 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 9 deletions(-) diff --git a/std/unicode.zig b/std/unicode.zig index df62e9162f..81bbc2aab6 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -1,4 +1,5 @@ const std = @import("./index.zig"); +const debug = std.debug; /// Given the first byte of a UTF-8 codepoint, /// returns a number 1-4 indicating the total length of the codepoint in bytes. @@ -25,8 +26,8 @@ pub fn utf8Decode(bytes: []const u8) !u32 { }; } pub fn utf8Decode2(bytes: []const u8) !u32 { - std.debug.assert(bytes.len == 2); - std.debug.assert(bytes[0] & 0b11100000 == 0b11000000); + debug.assert(bytes.len == 2); + debug.assert(bytes[0] & 0b11100000 == 0b11000000); var value: u32 = bytes[0] & 0b00011111; if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation; @@ -38,8 +39,8 @@ pub fn utf8Decode2(bytes: []const u8) !u32 { return value; } pub fn utf8Decode3(bytes: []const u8) !u32 { - std.debug.assert(bytes.len == 3); - std.debug.assert(bytes[0] & 0b11110000 == 0b11100000); + debug.assert(bytes.len == 3); + debug.assert(bytes[0] & 0b11110000 == 0b11100000); var value: u32 = bytes[0] & 0b00001111; if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation; @@ -56,8 +57,8 @@ pub fn utf8Decode3(bytes: []const u8) !u32 { return value; } pub fn utf8Decode4(bytes: []const u8) !u32 { - std.debug.assert(bytes.len == 4); - std.debug.assert(bytes[0] & 0b11111000 == 0b11110000); + debug.assert(bytes.len == 4); + debug.assert(bytes[0] & 0b11111000 == 0b11110000); var value: u32 = bytes[0] & 0b00000111; if (bytes[1] & 0b11000000 != 0b10000000) return error.Utf8ExpectedContinuation; @@ -78,6 +79,136 @@ pub fn utf8Decode4(bytes: []const u8) !u32 { return value; } +pub fn utf8ValidateSlice(s: []const u8) bool { + var i: usize = 0; + while (i < s.len) { + if (utf8ByteSequenceLength(s[i])) |cp_len| { + if (i + cp_len > s.len) { + return false; + } + + if (utf8Decode(s[i..i+cp_len])) |_| {} else |_| { return false; } + i += cp_len; + } else |err| { + return false; + } + } + return true; +} + +const Utf8View = struct { + bytes: []const u8, + + pub fn init(s: []const u8) !Utf8View { + if (!utf8ValidateSlice(s)) { + return error.InvalidUtf8; + } + + return initUnchecked(s); + } + + pub fn initUnchecked(s: []const u8) Utf8View { + return Utf8View { + .bytes = s, + }; + } + + pub fn initComptime(comptime s: []const u8) Utf8View { + if (comptime init(s)) |r| { + return r; + } else |err| switch (err) { + error.InvalidUtf8 => { + @compileError("invalid utf8"); + unreachable; + } + } + } + + pub fn Iterator(s: &const Utf8View) Utf8Iterator { + return Utf8Iterator { + .bytes = s.bytes, + .i = 0, + }; + } +}; + +const Utf8Iterator = struct { + bytes: []const u8, + i: usize, + + pub fn nextCodepointSlice(it: &Utf8Iterator) ?[]const u8 { + if (it.i >= it.bytes.len) { + return null; + } + + const cp_len = utf8ByteSequenceLength(it.bytes[it.i]) catch unreachable; + + it.i += cp_len; + return it.bytes[it.i-cp_len..it.i]; + } + + pub fn nextCodepoint(it: &Utf8Iterator) ?u32 { + const slice = it.nextCodepointSlice() ?? return null; + + const r = switch (slice.len) { + 1 => u32(slice[0]), + 2 => utf8Decode2(slice), + 3 => utf8Decode3(slice), + 4 => utf8Decode4(slice), + else => unreachable, + }; + + return r catch unreachable; + } +}; + +test "utf8 iterator on ascii" { + const s = Utf8View.initComptime("abc"); + + var it1 = s.Iterator(); + debug.assert(std.mem.eql(u8, "a", ??it1.nextCodepointSlice())); + debug.assert(std.mem.eql(u8, "b", ??it1.nextCodepointSlice())); + debug.assert(std.mem.eql(u8, "c", ??it1.nextCodepointSlice())); + debug.assert(it1.nextCodepointSlice() == null); + + var it2 = s.Iterator(); + debug.assert(??it2.nextCodepoint() == 'a'); + debug.assert(??it2.nextCodepoint() == 'b'); + debug.assert(??it2.nextCodepoint() == 'c'); + debug.assert(it2.nextCodepoint() == null); +} + +test "utf8 view bad" { + // Compile-time error. + // const s3 = Utf8View.initComptime("\xfe\xf2"); + + const s = Utf8View.init("hel\xadlo"); + if (s) |_| { unreachable; } else |err| { debug.assert(err == error.InvalidUtf8); } +} + +test "utf8 view ok" { + const s = Utf8View.initComptime("東京市"); + + var it1 = s.Iterator(); + debug.assert(std.mem.eql(u8, "東", ??it1.nextCodepointSlice())); + debug.assert(std.mem.eql(u8, "京", ??it1.nextCodepointSlice())); + debug.assert(std.mem.eql(u8, "市", ??it1.nextCodepointSlice())); + debug.assert(it1.nextCodepointSlice() == null); + + var it2 = s.Iterator(); + debug.assert(??it2.nextCodepoint() == 0x6771); + debug.assert(??it2.nextCodepoint() == 0x4eac); + debug.assert(??it2.nextCodepoint() == 0x5e02); + debug.assert(it2.nextCodepoint() == null); +} + +test "bad utf8 slice" { + debug.assert(utf8ValidateSlice("abc")); + debug.assert(!utf8ValidateSlice("abc\xc0")); + debug.assert(!utf8ValidateSlice("abc\xc0abc")); + debug.assert(utf8ValidateSlice("abc\xdf\xbf")); +} + test "valid utf8" { testValid("\x00", 0x0); testValid("\x20", 0x20); @@ -145,17 +276,17 @@ fn testError(bytes: []const u8, expected_err: error) void { if (testDecode(bytes)) |_| { unreachable; } else |err| { - std.debug.assert(err == expected_err); + debug.assert(err == expected_err); } } fn testValid(bytes: []const u8, expected_codepoint: u32) void { - std.debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint); + debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint); } fn testDecode(bytes: []const u8) !u32 { const length = try utf8ByteSequenceLength(bytes[0]); if (bytes.len < length) return error.UnexpectedEof; - std.debug.assert(bytes.len == length); + debug.assert(bytes.len == length); return utf8Decode(bytes); } From 9aa65c0e8e6e4135dcc04bcb388d1fa38c6d10f6 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 26 Feb 2018 18:40:33 +0100 Subject: [PATCH 08/11] allow implicit cast from &const to ?&const &const Allow implicit casts from n-th degree const pointers to nullable const pointers of degree n+1. That is: fn f() void { const s = S {}; const p = &s; g(p); // Works. g(&p); // So does this. } fn g(_: ?&const &const S) void { // Nullable 2nd degree const ptr. } Fixes #731 some more. --- src/ir.cpp | 3 ++- test/cases/cast.zig | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index e79235830c..b1fd7104ea 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8830,7 +8830,8 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } else if (wanted_child_type->id == TypeTableEntryIdPointer && wanted_child_type->data.pointer.is_const && - is_container(actual_type)) { + (actual_type->id == TypeTableEntryIdPointer || is_container(actual_type))) + { IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value); if (type_is_invalid(cast1->value.type)) return ira->codegen->invalid_instruction; diff --git a/test/cases/cast.zig b/test/cases/cast.zig index dabf97a799..d2671680c8 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -103,6 +103,37 @@ const Enum = enum { } }; +test "implicitly cast indirect pointer to maybe-indirect pointer" { + const S = struct { + const Self = this; + x: u8, + fn constConst(p: &const &const Self) u8 { + return (*p).x; + } + fn maybeConstConst(p: ?&const &const Self) u8 { + return (*??p).x; + } + fn constConstConst(p: &const &const &const Self) u8 { + return (**p).x; + } + fn maybeConstConstConst(p: ?&const &const &const Self) u8 { + return (**??p).x; + } + }; + const s = S { .x = 42 }; + const p = &s; + const q = &p; + const r = &q; + assert(42 == S.constConst(p)); + assert(42 == S.constConst(q)); + assert(42 == S.maybeConstConst(p)); + assert(42 == S.maybeConstConst(q)); + assert(42 == S.constConstConst(q)); + assert(42 == S.constConstConst(r)); + assert(42 == S.maybeConstConstConst(q)); + assert(42 == S.maybeConstConstConst(r)); +} + test "explicit cast from integer to error type" { testCastIntToErr(error.ItBroke); comptime testCastIntToErr(error.ItBroke); From 439621e44a68b436f958a84fcdb0bdac83613aea Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Feb 2018 11:14:14 -0500 Subject: [PATCH 09/11] remove signal hanlding stuff from std.os.ChildProcess --- std/os/child_process.zig | 50 ---------------------------------------- 1 file changed, 50 deletions(-) diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 27a91c1619..06802e657c 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -32,9 +32,6 @@ pub const ChildProcess = struct { pub argv: []const []const u8, - /// Possibly called from a signal handler. Must set this before calling `spawn`. - pub onTerm: ?fn(&ChildProcess)void, - /// Leave as null to use the current env map using the supplied allocator. pub env_map: ?&const BufMap, @@ -102,7 +99,6 @@ pub const ChildProcess = struct { .err_pipe = undefined, .llnode = undefined, .term = null, - .onTerm = null, .env_map = null, .cwd = null, .uid = if (is_windows) {} else null, @@ -124,7 +120,6 @@ pub const ChildProcess = struct { self.gid = user_info.gid; } - /// onTerm can be called before `spawn` returns. /// On success must call `kill` or `wait`. pub fn spawn(self: &ChildProcess) !void { if (is_windows) { @@ -165,9 +160,6 @@ pub const ChildProcess = struct { } pub fn killPosix(self: &ChildProcess) !Term { - block_SIGCHLD(); - defer restore_SIGCHLD(); - if (self.term) |term| { self.cleanupStreams(); return term; @@ -246,9 +238,6 @@ pub const ChildProcess = struct { } fn waitPosix(self: &ChildProcess) !Term { - block_SIGCHLD(); - defer restore_SIGCHLD(); - if (self.term) |term| { self.cleanupStreams(); return term; @@ -298,10 +287,6 @@ pub const ChildProcess = struct { fn handleWaitResult(self: &ChildProcess, status: i32) void { self.term = self.cleanupAfterWait(status); - - if (self.onTerm) |onTerm| { - onTerm(self); - } } fn cleanupStreams(self: &ChildProcess) void { @@ -347,9 +332,6 @@ pub const ChildProcess = struct { } fn spawnPosix(self: &ChildProcess) !void { - // TODO atomically set a flag saying that we already did this - install_SIGCHLD_handler(); - const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try makePipe() else undefined; errdefer if (self.stdin_behavior == StdIo.Pipe) { destroyPipe(stdin_pipe); }; @@ -387,11 +369,9 @@ pub const ChildProcess = struct { const err_pipe = try makePipe(); errdefer destroyPipe(err_pipe); - block_SIGCHLD(); const pid_result = posix.fork(); const pid_err = posix.getErrno(pid_result); if (pid_err > 0) { - restore_SIGCHLD(); return switch (pid_err) { posix.EAGAIN, posix.ENOMEM, posix.ENOSYS => error.SystemResources, else => os.unexpectedErrorPosix(pid_err), @@ -399,7 +379,6 @@ pub const ChildProcess = struct { } if (pid_result == 0) { // we are the child - restore_SIGCHLD(); setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err); @@ -451,8 +430,6 @@ pub const ChildProcess = struct { // TODO make this atomic so it works even with threads children_nodes.prepend(&self.llnode); - restore_SIGCHLD(); - if (self.stdin_behavior == StdIo.Pipe) { os.close(stdin_pipe[0]); } if (self.stdout_behavior == StdIo.Pipe) { os.close(stdout_pipe[1]); } if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); } @@ -824,30 +801,3 @@ fn handleTerm(pid: i32, status: i32) void { } } } - -const sigchld_set = x: { - var signal_set = posix.empty_sigset; - posix.sigaddset(&signal_set, posix.SIGCHLD); - break :x signal_set; -}; - -fn block_SIGCHLD() void { - const err = posix.getErrno(posix.sigprocmask(posix.SIG_BLOCK, &sigchld_set, null)); - assert(err == 0); -} - -fn restore_SIGCHLD() void { - const err = posix.getErrno(posix.sigprocmask(posix.SIG_UNBLOCK, &sigchld_set, null)); - assert(err == 0); -} - -const sigchld_action = posix.Sigaction { - .handler = sigchld_handler, - .mask = posix.empty_sigset, - .flags = posix.SA_RESTART | posix.SA_NOCLDSTOP, -}; - -fn install_SIGCHLD_handler() void { - const err = posix.getErrno(posix.sigaction(posix.SIGCHLD, &sigchld_action, null)); - assert(err == 0); -} From 90598b4631e3b68565c7d62102a9e4615514a721 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 28 Feb 2018 00:51:22 +0100 Subject: [PATCH 10/11] fix assert on self-referencing function ptr field The construct `struct S { f: fn(S) void }` is not legal because structs are not copyable but it should not result in an ICE. Fixes #795. --- src/analyze.cpp | 4 +++- test/compile_errors.zig | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index c16a5d462a..9d5e7d77af 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1670,6 +1670,9 @@ static void resolve_struct_type(CodeGen *g, TypeTableEntry *struct_type) { if (struct_type->data.structure.is_invalid) return; + if (struct_type->data.structure.zero_bits_loop_flag) + return; + AstNode *decl_node = struct_type->data.structure.decl_node; if (struct_type->data.structure.embedded_in_current) { @@ -1682,7 +1685,6 @@ static void resolve_struct_type(CodeGen *g, TypeTableEntry *struct_type) { return; } - assert(!struct_type->data.structure.zero_bits_loop_flag); assert(struct_type->data.structure.fields); assert(decl_node->type == NodeTypeContainerDecl); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 940125711b..a3ac4e2344 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3090,4 +3090,16 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { , ".tmp_source.zig:11:20: error: runtime cast to union 'Value' which has non-void fields", ".tmp_source.zig:3:5: note: field 'A' has type 'i32'"); + + cases.add("self-referencing function pointer field", + \\const S = struct { + \\ f: fn(_: S) void, + \\}; + \\fn f(_: S) void { + \\} + \\export fn entry() void { + \\ var _ = S { .f = f }; + \\} + , + ".tmp_source.zig:4:9: error: type 'S' is not copyable; cannot pass by value"); } From 556f22a751e47d572404992befe15c09c0f2eb0b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Feb 2018 00:28:26 -0500 Subject: [PATCH 11/11] different way of fixing previous commit get_fn_type doesn't need the complete parameter type, it can just ensure zero bits known. --- src/analyze.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 9d5e7d77af..45e6780791 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -997,7 +997,7 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { gen_param_info->src_index = i; gen_param_info->gen_index = SIZE_MAX; - ensure_complete_type(g, type_entry); + type_ensure_zero_bits_known(g, type_entry); if (type_has_bits(type_entry)) { TypeTableEntry *gen_type; if (handle_is_ptr(type_entry)) { @@ -1670,9 +1670,6 @@ static void resolve_struct_type(CodeGen *g, TypeTableEntry *struct_type) { if (struct_type->data.structure.is_invalid) return; - if (struct_type->data.structure.zero_bits_loop_flag) - return; - AstNode *decl_node = struct_type->data.structure.decl_node; if (struct_type->data.structure.embedded_in_current) { @@ -1685,6 +1682,7 @@ static void resolve_struct_type(CodeGen *g, TypeTableEntry *struct_type) { return; } + assert(!struct_type->data.structure.zero_bits_loop_flag); assert(struct_type->data.structure.fields); assert(decl_node->type == NodeTypeContainerDecl); @@ -2131,6 +2129,7 @@ static void resolve_enum_zero_bits(CodeGen *g, TypeTableEntry *enum_type) { if (enum_type->data.enumeration.zero_bits_loop_flag) { enum_type->data.enumeration.zero_bits_known = true; + enum_type->data.enumeration.zero_bits_loop_flag = false; return; } @@ -2285,6 +2284,7 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) { // the alignment is pointer width, then assert that the first field is within that // alignment struct_type->data.structure.zero_bits_known = true; + struct_type->data.structure.zero_bits_loop_flag = false; if (struct_type->data.structure.abi_alignment == 0) { if (struct_type->data.structure.layout == ContainerLayoutPacked) { struct_type->data.structure.abi_alignment = 1;