From 2fc18b52788f789ceba7b4f60e850de3ce67495c Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 8 Aug 2020 01:18:22 +0200 Subject: stage2: make link data in Decl into unions This will allow for implementation of non-Elf backends without wasting memory. --- src-self-hosted/Module.zig | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index bda2b3205f..e056b02b2e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -177,14 +177,14 @@ pub const Decl = struct { /// Represents the position of the code in the output file. /// This is populated regardless of semantic analysis and code generation. - link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty, + link: link.File.LinkBlock, /// Represents the function in the linked output file, if the `Decl` is a function. /// This is stored here and not in `Fn` because `Decl` survives across updates but /// `Fn` does not. /// TODO Look into making `Fn` a longer lived structure and moving this field there /// to save on memory usage. - fn_link: link.File.Elf.SrcFn = link.File.Elf.SrcFn.empty, + fn_link: link.File.LinkFn, contents_hash: std.zig.SrcHash, @@ -1538,10 +1538,13 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); decl.contents_hash = contents_hash; - } else if (decl.fn_link.len != 0) { - // TODO Look into detecting when this would be unnecessary by storing enough state - // in `Decl` to notice that the line number did not change. - self.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); + } else switch (self.bin_file.tag) { + .elf => if (decl.fn_link.elf.len != 0) { + // TODO Look into detecting when this would be unnecessary by storing enough state + // in `Decl` to notice that the line number did not change. + self.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); + }, + .c => {}, } } } else { @@ -1745,7 +1748,14 @@ fn allocateNewDecl( .analysis = .unreferenced, .deletion_flag = false, .contents_hash = contents_hash, - .link = link.File.Elf.TextBlock.empty, + .link = switch (self.bin_file.tag) { + .elf => .{ .elf = link.File.Elf.TextBlock.empty }, + .c => .{ .c = {} }, + }, + .fn_link = switch (self.bin_file.tag) { + .elf => .{ .elf = link.File.Elf.SrcFn.empty }, + .c => .{ .c = {} }, + }, .generation = 0, }; return new_decl; -- cgit v1.2.3 From fd47839064775c0cc956f12a012f0893e1f3a440 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 8 Aug 2020 18:19:48 -0700 Subject: stage2: fix crash on empty source file --- src-self-hosted/Module.zig | 8 +++----- src-self-hosted/link.zig | 14 ++++++++++---- src-self-hosted/main.zig | 3 +++ test/stage2/compare_output.zig | 5 ++++- 4 files changed, 20 insertions(+), 10 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index e056b02b2e..e3233c6403 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -949,10 +949,8 @@ pub fn update(self: *Module) !void { try self.deleteDecl(decl); } - if (self.totalErrorCount() == 0) { - // This is needed before reading the error flags. - try self.bin_file.flush(); - } + // This is needed before reading the error flags. + try self.bin_file.flush(); self.link_error_flags = self.bin_file.errorFlags(); @@ -2537,7 +2535,7 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst } } - return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); + return self.fail(scope, inst.src, "expected {}, found {}", .{ dest_type, inst.ty }); } pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst { diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 6fd9329188..8bfd3bdb48 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1172,7 +1172,10 @@ pub const File = struct { self.debug_aranges_section_dirty = false; } - if (self.debug_line_header_dirty) { + if (self.debug_line_header_dirty) debug_line: { + if (self.dbg_line_fn_first == null) { + break :debug_line; // Error in module; leave debug_line_header_dirty=true. + } const dbg_line_prg_off = self.getDebugLineProgramOff(); const dbg_line_prg_end = self.getDebugLineProgramEnd(); assert(dbg_line_prg_end != 0); @@ -1403,18 +1406,21 @@ pub const File = struct { self.shdr_table_dirty = false; } if (self.entry_addr == null and self.base.options.output_mode == .Exe) { - log.debug(.link, "no_entry_point_found = true\n", .{}); + log.debug(.link, "flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { + log.debug(.link, "flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; try self.writeElfHeader(); } - // The point of flush() is to commit changes, so nothing should be dirty after this. + // The point of flush() is to commit changes, so in theory, nothing should + // be dirty after this. However, it is possible for some things to remain + // dirty because they fail to be written in the event of compile errors, + // such as debug_line_header_dirty. assert(!self.debug_info_section_dirty); assert(!self.debug_abbrev_section_dirty); assert(!self.debug_aranges_section_dirty); - assert(!self.debug_line_header_dirty); assert(!self.phdr_table_dirty); assert(!self.shdr_table_dirty); assert(!self.shstrtab_dirty); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 5e2120f41a..2795da0017 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -64,6 +64,9 @@ var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; pub fn main() !void { const gpa = if (std.builtin.link_libc) std.heap.c_allocator else &general_purpose_allocator.allocator; + defer if (!std.builtin.link_libc) { + _ = general_purpose_allocator.deinit(); + }; var arena_instance = std.heap.ArenaAllocator.init(gpa); defer arena_instance.deinit(); const arena = &arena_instance.allocator; diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 1e4db06572..bb3e542f13 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -23,6 +23,9 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("hello world with updates", linux_x64); + + case.addError("", &[_][]const u8{":1:1: error: no entry point found"}); + // Regular old hello world case.addCompareOutput( \\export fn _start() noreturn { @@ -123,7 +126,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); } - + { var case = ctx.exe("hello world", linux_riscv64); // Regular old hello world -- cgit v1.2.3 From 8282f4271cf3a3e0d2159c698142b3cafe6b1603 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 11 Aug 2020 22:23:32 -0700 Subject: stage2: basic support for parameters .debug_info see #6014 --- src-self-hosted/Module.zig | 24 +++++-- src-self-hosted/codegen.zig | 137 ++++++++++++++++++++++-------------- src-self-hosted/codegen/riscv64.zig | 9 +++ src-self-hosted/codegen/x86.zig | 79 +++++++++++++++++++++ src-self-hosted/codegen/x86_64.zig | 109 ++++++++++++++++++++++++++++ src-self-hosted/ir.zig | 16 ++++- src-self-hosted/link.zig | 113 ++++++++++++++++++++--------- src-self-hosted/type.zig | 66 ++++++++++++++++- src-self-hosted/zir.zig | 29 +++++++- src-self-hosted/zir_sema.zig | 5 +- 10 files changed, 491 insertions(+), 96 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index e3233c6403..6abd4f51e1 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1301,14 +1301,16 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { for (fn_proto.params()) |param, i| { const name_token = param.name_token.?; const src = tree.token_locs[name_token].start; - const param_name = tree.tokenSlice(name_token); - const arg = try gen_scope_arena.allocator.create(zir.Inst.NoOp); + const param_name = tree.tokenSlice(name_token); // TODO: call identifierTokenString + const arg = try gen_scope_arena.allocator.create(zir.Inst.Arg); arg.* = .{ .base = .{ .tag = .arg, .src = src, }, - .positionals = .{}, + .positionals = .{ + .name = param_name, + }, .kw_args = .{}, }; gen_scope.instructions.items[i] = &arg.base; @@ -1934,6 +1936,20 @@ pub fn addBinOp( return &inst.base; } +pub fn addArg(self: *Module, block: *Scope.Block, src: usize, ty: Type, name: [*:0]const u8) !*Inst { + const inst = try block.arena.create(Inst.Arg); + inst.* = .{ + .base = .{ + .tag = .arg, + .ty = ty, + .src = src, + }, + .name = name, + }; + try block.instructions.append(self.gpa, &inst.base); + return &inst.base; +} + pub fn addBr( self: *Module, scope_block: *Scope.Block, @@ -2535,7 +2551,7 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst } } - return self.fail(scope, inst.src, "expected {}, found {}", .{ dest_type, inst.ty }); + return self.fail(scope, inst.src, "expected {}, found {}", .{ dest_type, inst.ty }); } pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst { diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index db99b88d0e..a3c95349e1 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -51,6 +51,7 @@ pub fn generateSymbol( code: *std.ArrayList(u8), dbg_line: *std.ArrayList(u8), dbg_info: *std.ArrayList(u8), + dbg_info_type_relocs: *link.File.Elf.DbgInfoTypeRelocsTable, ) GenerateSymbolError!Result { const tracy = trace(@src()); defer tracy.end(); @@ -58,57 +59,57 @@ pub fn generateSymbol( switch (typed_value.ty.zigTypeTag()) { .Fn => { switch (bin_file.base.options.target.cpu.arch) { - //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), - //.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info), + //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + //.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), else => @panic("Backend architectures that don't have good support yet are commented out, to improve compilation performance. If you are interested in one of these other backends feel free to uncomment them. Eventually these will be completed, but stage1 is slow and a memory hog."), } }, @@ -122,7 +123,7 @@ pub fn generateSymbol( switch (try generateSymbol(bin_file, src, .{ .ty = typed_value.ty.elemType(), .val = sentinel, - }, code, dbg_line, dbg_info)) { + }, code, dbg_line, dbg_info, dbg_info_type_relocs)) { .appended => return Result{ .appended = {} }, .externally_managed => |slice| { code.appendSliceAssumeCapacity(slice); @@ -225,6 +226,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { code: *std.ArrayList(u8), dbg_line: *std.ArrayList(u8), dbg_info: *std.ArrayList(u8), + dbg_info_type_relocs: *link.File.Elf.DbgInfoTypeRelocsTable, err_msg: ?*ErrorMsg, args: []MCValue, ret_mcv: MCValue, @@ -395,6 +397,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { code: *std.ArrayList(u8), dbg_line: *std.ArrayList(u8), dbg_info: *std.ArrayList(u8), + dbg_info_type_relocs: *link.File.Elf.DbgInfoTypeRelocsTable, ) GenerateSymbolError!Result { const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; @@ -433,6 +436,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .code = code, .dbg_line = dbg_line, .dbg_info = dbg_info, + .dbg_info_type_relocs = dbg_info_type_relocs, .err_msg = null, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` @@ -610,6 +614,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + /// Adds a Type to the .debug_info at the current position. The bytes will be populated later, + /// after codegen for this symbol is done. + fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void { + assert(ty.hasCodeGenBits()); + const index = self.dbg_info.items.len; + try self.dbg_info.resize(index + 4); // DW.AT_type, DW.FORM_ref4 + + const gop = try self.dbg_info_type_relocs.getOrPut(self.gpa, ty); + if (!gop.found_existing) { + gop.entry.value = .{ + .off = undefined, + .relocs = .{}, + }; + } + try gop.entry.value.relocs.append(self.gpa, @intCast(u32, index)); + } + fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue { switch (inst.tag) { .add => return self.genAdd(inst.castTag(.add).?), @@ -1009,7 +1030,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genArg(self: *Self, inst: *ir.Inst.NoOp) !MCValue { + fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue { if (FreeRegInt == u0) { return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch}); } @@ -1022,10 +1043,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const result = self.args[self.arg_index]; self.arg_index += 1; + const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; switch (result) { .register => |reg| { branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = &inst.base }); branch.markRegUsed(reg); + + try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len); + self.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); + self.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT_location, DW.FORM_exprloc + 1, // ULEB128 dwarf expression length + reg.dwarfLocOp(), + }); + try self.addDbgInfoTypeReloc(inst.base.ty); // DW.AT_type, DW.FORM_ref4 + self.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT_name, DW.FORM_string }, else => {}, } diff --git a/src-self-hosted/codegen/riscv64.zig b/src-self-hosted/codegen/riscv64.zig index c5c762709a..793731c83c 100644 --- a/src-self-hosted/codegen/riscv64.zig +++ b/src-self-hosted/codegen/riscv64.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const DW = std.dwarf; pub const instructions = struct { pub const CallBreak = packed struct { @@ -48,6 +49,10 @@ pub const RawRegister = enum(u8) { x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31, + + pub fn dwarfLocOp(reg: RawRegister) u8 { + return @enumToInt(reg) + DW.OP_reg0; + } }; pub const Register = enum(u8) { @@ -83,6 +88,10 @@ pub const Register = enum(u8) { } return null; } + + pub fn dwarfLocOp(reg: Register) u8 { + return @enumToInt(reg) + DW.OP_reg0; + } }; // zig fmt: on diff --git a/src-self-hosted/codegen/x86.zig b/src-self-hosted/codegen/x86.zig index e0d0848bf5..fdad4e56db 100644 --- a/src-self-hosted/codegen/x86.zig +++ b/src-self-hosted/codegen/x86.zig @@ -1,3 +1,6 @@ +const std = @import("std"); +const DW = std.dwarf; + // zig fmt: off pub const Register = enum(u8) { // 0 through 7, 32-bit registers. id is int value @@ -37,8 +40,84 @@ pub const Register = enum(u8) { else => null, }; } + + /// Convert from any register to its 32 bit alias. + pub fn to32(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id())); + } + + /// Convert from any register to its 16 bit alias. + pub fn to16(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id()) + 8); + } + + /// Convert from any register to its 8 bit alias. + pub fn to8(self: Register) Register { + return @intToEnum(Register, @as(u8, self.id()) + 16); + } + + + pub fn dwarfLocOp(reg: Register) u8 { + return switch (reg.to32()) { + .eax => DW.OP_reg0, + .ecx => DW.OP_reg1, + .edx => DW.OP_reg2, + .ebx => DW.OP_reg3, + .esp => DW.OP_reg4, + .ebp => DW.OP_reg5, + .esi => DW.OP_reg6, + .edi => DW.OP_reg7, + else => unreachable, + }; + } }; // zig fmt: on pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi }; + +// TODO add these to Register enum and corresponding dwarfLocOp +// // Return Address register. This is stored in `0(%esp, "")` and is not a physical register. +// RA = (8, "RA"), +// +// ST0 = (11, "st0"), +// ST1 = (12, "st1"), +// ST2 = (13, "st2"), +// ST3 = (14, "st3"), +// ST4 = (15, "st4"), +// ST5 = (16, "st5"), +// ST6 = (17, "st6"), +// ST7 = (18, "st7"), +// +// XMM0 = (21, "xmm0"), +// XMM1 = (22, "xmm1"), +// XMM2 = (23, "xmm2"), +// XMM3 = (24, "xmm3"), +// XMM4 = (25, "xmm4"), +// XMM5 = (26, "xmm5"), +// XMM6 = (27, "xmm6"), +// XMM7 = (28, "xmm7"), +// +// MM0 = (29, "mm0"), +// MM1 = (30, "mm1"), +// MM2 = (31, "mm2"), +// MM3 = (32, "mm3"), +// MM4 = (33, "mm4"), +// MM5 = (34, "mm5"), +// MM6 = (35, "mm6"), +// MM7 = (36, "mm7"), +// +// MXCSR = (39, "mxcsr"), +// +// ES = (40, "es"), +// CS = (41, "cs"), +// SS = (42, "ss"), +// DS = (43, "ds"), +// FS = (44, "fs"), +// GS = (45, "gs"), +// +// TR = (48, "tr"), +// LDTR = (49, "ldtr"), +// +// FS_BASE = (93, "fs.base"), +// GS_BASE = (94, "gs.base"), diff --git a/src-self-hosted/codegen/x86_64.zig b/src-self-hosted/codegen/x86_64.zig index c149613ae9..dea39f82cd 100644 --- a/src-self-hosted/codegen/x86_64.zig +++ b/src-self-hosted/codegen/x86_64.zig @@ -1,4 +1,6 @@ +const std = @import("std"); const Type = @import("../Type.zig"); +const DW = std.dwarf; // zig fmt: off @@ -101,6 +103,30 @@ pub const Register = enum(u8) { pub fn to8(self: Register) Register { return @intToEnum(Register, @as(u8, self.id()) + 48); } + + pub fn dwarfLocOp(self: Register) u8 { + return switch (self.to64()) { + .rax => DW.OP_reg0, + .rdx => DW.OP_reg1, + .rcx => DW.OP_reg2, + .rbx => DW.OP_reg3, + .rsi => DW.OP_reg4, + .rdi => DW.OP_reg5, + .rbp => DW.OP_reg6, + .rsp => DW.OP_reg7, + + .r8 => DW.OP_reg8, + .r9 => DW.OP_reg9, + .r10 => DW.OP_reg10, + .r11 => DW.OP_reg11, + .r12 => DW.OP_reg12, + .r13 => DW.OP_reg13, + .r14 => DW.OP_reg14, + .r15 => DW.OP_reg15, + + else => unreachable, + }; + } }; // zig fmt: on @@ -109,3 +135,86 @@ pub const Register = enum(u8) { pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx }; + +// TODO add these registers to the enum and populate dwarfLocOp +// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register. +// RA = (16, "RA"), +// +// XMM0 = (17, "xmm0"), +// XMM1 = (18, "xmm1"), +// XMM2 = (19, "xmm2"), +// XMM3 = (20, "xmm3"), +// XMM4 = (21, "xmm4"), +// XMM5 = (22, "xmm5"), +// XMM6 = (23, "xmm6"), +// XMM7 = (24, "xmm7"), +// +// XMM8 = (25, "xmm8"), +// XMM9 = (26, "xmm9"), +// XMM10 = (27, "xmm10"), +// XMM11 = (28, "xmm11"), +// XMM12 = (29, "xmm12"), +// XMM13 = (30, "xmm13"), +// XMM14 = (31, "xmm14"), +// XMM15 = (32, "xmm15"), +// +// ST0 = (33, "st0"), +// ST1 = (34, "st1"), +// ST2 = (35, "st2"), +// ST3 = (36, "st3"), +// ST4 = (37, "st4"), +// ST5 = (38, "st5"), +// ST6 = (39, "st6"), +// ST7 = (40, "st7"), +// +// MM0 = (41, "mm0"), +// MM1 = (42, "mm1"), +// MM2 = (43, "mm2"), +// MM3 = (44, "mm3"), +// MM4 = (45, "mm4"), +// MM5 = (46, "mm5"), +// MM6 = (47, "mm6"), +// MM7 = (48, "mm7"), +// +// RFLAGS = (49, "rFLAGS"), +// ES = (50, "es"), +// CS = (51, "cs"), +// SS = (52, "ss"), +// DS = (53, "ds"), +// FS = (54, "fs"), +// GS = (55, "gs"), +// +// FS_BASE = (58, "fs.base"), +// GS_BASE = (59, "gs.base"), +// +// TR = (62, "tr"), +// LDTR = (63, "ldtr"), +// MXCSR = (64, "mxcsr"), +// FCW = (65, "fcw"), +// FSW = (66, "fsw"), +// +// XMM16 = (67, "xmm16"), +// XMM17 = (68, "xmm17"), +// XMM18 = (69, "xmm18"), +// XMM19 = (70, "xmm19"), +// XMM20 = (71, "xmm20"), +// XMM21 = (72, "xmm21"), +// XMM22 = (73, "xmm22"), +// XMM23 = (74, "xmm23"), +// XMM24 = (75, "xmm24"), +// XMM25 = (76, "xmm25"), +// XMM26 = (77, "xmm26"), +// XMM27 = (78, "xmm27"), +// XMM28 = (79, "xmm28"), +// XMM29 = (80, "xmm29"), +// XMM30 = (81, "xmm30"), +// XMM31 = (82, "xmm31"), +// +// K0 = (118, "k0"), +// K1 = (119, "k1"), +// K2 = (120, "k2"), +// K3 = (121, "k3"), +// K4 = (122, "k4"), +// K5 = (123, "k5"), +// K6 = (124, "k6"), +// K7 = (125, "k7"), diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 1188230a54..176fd7e303 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -87,7 +87,6 @@ pub const Inst = struct { .alloc, .retvoid, .unreach, - .arg, .breakpoint, .dbg_stmt, => NoOp, @@ -115,6 +114,7 @@ pub const Inst = struct { .store, => BinOp, + .arg => Arg, .assembly => Assembly, .block => Block, .br => Br, @@ -253,6 +253,20 @@ pub const Inst = struct { } }; + pub const Arg = struct { + pub const base_tag = Tag.arg; + + base: Inst, + name: [*:0]const u8, + + pub fn operandCount(self: *const Arg) usize { + return 0; + } + pub fn getOperand(self: *const Arg, index: usize) ?*Inst { + return null; + } + }; + pub const Assembly = struct { pub const base_tag = Tag.assembly; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 77534fca4a..d05c676a38 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -391,6 +391,17 @@ pub const File = struct { const minimum_text_block_size = 64; const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den; + pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, true); + + const DbgInfoTypeReloc = struct { + /// Offset from `TextBlock.dbg_info_off` (the buffer that is local to a Decl). + /// This is where the .debug_info tag for the type is. + off: u32, + /// Offset from `TextBlock.dbg_info_off` (the buffer that is local to a Decl). + /// List of DW.AT_type / DW.FORM_ref4 that points to the type. + relocs: std.ArrayListUnmanaged(u32), + }; + pub const TextBlock = struct { /// Each decl always gets a local symbol with the fully qualified name. /// The vaddr and size are found here directly. @@ -991,6 +1002,7 @@ pub const File = struct { pub const abbrev_subprogram_retvoid = 3; pub const abbrev_base_type = 4; pub const abbrev_pad1 = 5; + pub const abbrev_parameter = 6; /// Commit pending changes and write headers. pub fn flush(self: *Elf) !void { @@ -1044,6 +1056,12 @@ pub const File = struct { abbrev_pad1, DW.TAG_unspecified_type, DW.CHILDREN_no, // header 0, 0, // table sentinel + abbrev_parameter, DW.TAG_formal_parameter, DW.CHILDREN_no, // header + DW.AT_location , DW.FORM_exprloc, + DW.AT_type , DW.FORM_ref4, + DW.AT_name , DW.FORM_string, + 0, 0, // table sentinel + 0, 0, 0, // section sentinel }; @@ -1088,7 +1106,8 @@ pub const File = struct { // not including the initial length itself. // We have to come back and write it later after we know the size. const after_init_len = di_buf.items.len + init_len_size; - const dbg_info_end = last_dbg_info_decl.dbg_info_off + last_dbg_info_decl.dbg_info_len; + // +1 for the final 0 that ends the compilation unit children. + const dbg_info_end = last_dbg_info_decl.dbg_info_off + last_dbg_info_decl.dbg_info_len + 1; const init_len = dbg_info_end - after_init_len; switch (self.ptr_width) { .p32 => { @@ -1138,7 +1157,7 @@ pub const File = struct { @panic("TODO: handle .debug_info header exceeding its padding"); } const jmp_amt = first_dbg_info_decl.dbg_info_off - di_buf.items.len; - try self.pwriteDbgInfoNops(0, di_buf.items, jmp_amt, debug_info_sect.sh_offset); + try self.pwriteDbgInfoNops(0, di_buf.items, jmp_amt, false, debug_info_sect.sh_offset); self.debug_info_header_dirty = false; } @@ -1858,12 +1877,19 @@ pub const File = struct { var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator); defer dbg_info_buffer.deinit(); + var dbg_info_type_relocs: DbgInfoTypeRelocsTable = .{}; + defer { + for (dbg_info_type_relocs.items()) |*entry| { + entry.value.relocs.deinit(self.base.allocator); + } + dbg_info_type_relocs.deinit(self.base.allocator); + } + const typed_value = decl.typed_value.most_recent.typed_value; const is_fn: bool = switch (typed_value.ty.zigTypeTag()) { .Fn => true, else => false, }; - var fn_ret_has_bits: bool = undefined; if (is_fn) { // For functions we need to add a prologue to the debug line program. try dbg_line_buffer.ensureCapacity(26); @@ -1919,7 +1945,8 @@ pub const File = struct { const decl_name_with_null = decl.name[0..mem.lenZ(decl.name) + 1]; try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 25 + decl_name_with_null.len); - fn_ret_has_bits = typed_value.ty.fnReturnType().hasCodeGenBits(); + const fn_ret_type = typed_value.ty.fnReturnType(); + const fn_ret_has_bits = fn_ret_type.hasCodeGenBits(); if (fn_ret_has_bits) { dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram); } else { @@ -1933,14 +1960,21 @@ pub const File = struct { assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); dbg_info_buffer.items.len += 4; // DW.AT_high_pc, DW.FORM_data4 if (fn_ret_has_bits) { - assert(self.getRelocDbgInfoSubprogramRetType() == dbg_info_buffer.items.len); + const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type); + if (!gop.found_existing) { + gop.entry.value = .{ + .off = undefined, + .relocs = .{}, + }; + } + try gop.entry.value.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len)); dbg_info_buffer.items.len += 4; // DW.AT_type, DW.FORM_ref4 } dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string } else { // TODO implement .debug_info for global variables } - const res = try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer, &dbg_line_buffer, &dbg_info_buffer); + const res = try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer, &dbg_line_buffer, &dbg_info_buffer, &dbg_info_type_relocs); const code = switch (res) { .externally_managed => |x| x, .appended => code_buffer.items, @@ -2011,7 +2045,6 @@ pub const File = struct { const text_block = &decl.link.elf; // If the Decl is a function, we need to update the .debug_line program. - var fn_ret_type_index: usize = undefined; if (is_fn) { // Perform the relocations based on vaddr. switch (self.ptr_width) { @@ -2117,28 +2150,30 @@ pub const File = struct { const file_pos = debug_line_sect.sh_offset + src_fn.off; try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos); - // .debug_info - try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 2); - // End the TAG_subprogram children. - dbg_info_buffer.appendAssumeCapacity(0); - if (fn_ret_has_bits) { - // Now we do the return type of the function. The relocation must be performed - // later after the offset for this subprogram is computed. - fn_ret_type_index = dbg_info_buffer.items.len; - try self.addDbgInfoType(typed_value.ty.fnReturnType(), &dbg_info_buffer); - } + // .debug_info - End the TAG_subprogram children. + try dbg_info_buffer.append(0); + } + + // Now we emit the .debug_info types of the Decl. These will count towards the size of + // the buffer, so we have to do it before computing the offset, and we can't perform the actual + // relocations yet. + for (dbg_info_type_relocs.items()) |*entry| { + entry.value.off = @intCast(u32, dbg_info_buffer.items.len); + try self.addDbgInfoType(entry.key, &dbg_info_buffer); } try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len)); - if (is_fn and fn_ret_has_bits) { - // Perform function return type relocation. - mem.writeInt( - u32, - dbg_info_buffer.items[self.getRelocDbgInfoSubprogramRetType()..][0..4], - text_block.dbg_info_off + @intCast(u32, fn_ret_type_index), - target_endian, - ); + // Now that we have the offset assigned we can finally perform type relocations. + for (dbg_info_type_relocs.items()) |entry| { + for (entry.value.relocs.items) |off| { + mem.writeInt( + u32, + dbg_info_buffer.items[off..][0..4], + text_block.dbg_info_off + entry.value.off, + target_endian, + ); + } } try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items); @@ -2151,7 +2186,8 @@ pub const File = struct { /// Asserts the type has codegen bits. fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !void { switch (ty.zigTypeTag()) { - .Void, .NoReturn => unreachable, + .Void => unreachable, + .NoReturn => unreachable, .Bool => { try dbg_info_buffer.appendSlice(&[_]u8{ abbrev_base_type, @@ -2201,7 +2237,7 @@ pub const File = struct { text_block.dbg_info_next = null; // Populate where it used to be with NOPs. const file_pos = debug_info_sect.sh_offset + text_block.dbg_info_off; - try self.pwriteDbgInfoNops(0, &[0]u8{}, text_block.dbg_info_len, file_pos); + try self.pwriteDbgInfoNops(0, &[0]u8{}, text_block.dbg_info_len, false, file_pos); // TODO Look at the free list before appending at the end. text_block.dbg_info_prev = last; last.dbg_info_next = text_block; @@ -2238,7 +2274,8 @@ pub const File = struct { const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; const last_decl = self.dbg_info_decl_last.?; - const needed_size = last_decl.dbg_info_off + last_decl.dbg_info_len; + // +1 for a trailing zero to end the children of the decl tag. + const needed_size = last_decl.dbg_info_off + last_decl.dbg_info_len + 1; if (needed_size != debug_info_sect.sh_size) { if (needed_size > self.allocatedSize(debug_info_sect.sh_offset)) { const new_offset = self.findFreeSpace(needed_size, 1); @@ -2265,10 +2302,13 @@ pub const File = struct { else 0; + // To end the children of the decl tag. + const trailing_zero = text_block.dbg_info_next == null; + // We only have support for one compilation unit so far, so the offsets are directly // from the .debug_info section. const file_pos = debug_info_sect.sh_offset + text_block.dbg_info_off; - try self.pwriteDbgInfoNops(prev_padding_size, dbg_info_buf, next_padding_size, file_pos); + try self.pwriteDbgInfoNops(prev_padding_size, dbg_info_buf, next_padding_size, trailing_zero, file_pos); } /// Must be called only after a successful call to `updateDecl`. @@ -2598,10 +2638,6 @@ pub const File = struct { return dbg_info_low_pc_reloc_index + self.ptrWidthBytes(); } - fn getRelocDbgInfoSubprogramRetType(self: Elf) u32 { - return self.getRelocDbgInfoSubprogramHighPC() + 4; - } - fn dbgLineNeededHeaderBytes(self: Elf) u32 { const directory_entry_format_count = 1; const file_name_entry_format_count = 1; @@ -2710,6 +2746,7 @@ pub const File = struct { prev_padding_size: usize, buf: []const u8, next_padding_size: usize, + trailing_zero: bool, offset: usize, ) !void { const tracy = trace(@src()); @@ -2761,6 +2798,16 @@ pub const File = struct { vec_index += 1; } } + + if (trailing_zero) { + var zbuf = [1]u8{0}; + vecs[vec_index] = .{ + .iov_base = &zbuf, + .iov_len = zbuf.len, + }; + vec_index += 1; + } + try self.base.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); } diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index ca46caeaa8..8d643c2005 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -103,7 +103,6 @@ pub const Type = extern union { } pub fn eql(a: Type, b: Type) bool { - //std.debug.warn("test {} == {}\n", .{ a, b }); // As a shortcut, if the small tags / addresses match, we're done. if (a.tag_if_small_enough == b.tag_if_small_enough) return true; @@ -197,6 +196,71 @@ pub const Type = extern union { } } + pub fn hash(self: Type) u32 { + var hasher = std.hash.Wyhash.init(0); + const zig_type_tag = self.zigTypeTag(); + std.hash.autoHash(&hasher, zig_type_tag); + switch (zig_type_tag) { + .Type, + .Void, + .Bool, + .NoReturn, + .ComptimeFloat, + .ComptimeInt, + .Undefined, + .Null, + => {}, // The zig type tag is all that is needed to distinguish. + + .Pointer => { + // TODO implement more pointer type hashing + }, + .Int => { + // Detect that e.g. u64 != usize, even if the bits match on a particular target. + if (self.isNamedInt()) { + std.hash.autoHash(&hasher, self.tag()); + } else { + // Remaining cases are arbitrary sized integers. + // The target will not be branched upon, because we handled target-dependent cases above. + const info = self.intInfo(@as(Target, undefined)); + std.hash.autoHash(&hasher, info.signed); + std.hash.autoHash(&hasher, info.bits); + } + }, + .Array => { + std.hash.autoHash(&hasher, self.arrayLen()); + std.hash.autoHash(&hasher, self.elemType().hash()); + // TODO hash array sentinel + }, + .Fn => { + std.hash.autoHash(&hasher, self.fnReturnType().hash()); + std.hash.autoHash(&hasher, self.fnCallingConvention()); + const params_len = self.fnParamLen(); + std.hash.autoHash(&hasher, params_len); + var i: usize = 0; + while (i < params_len) : (i += 1) { + std.hash.autoHash(&hasher, self.fnParamType(i).hash()); + } + }, + .Float, + .Struct, + .Optional, + .ErrorUnion, + .ErrorSet, + .Enum, + .Union, + .BoundFn, + .Opaque, + .Frame, + .AnyFrame, + .Vector, + .EnumLiteral, + => { + // TODO implement more type hashing + }, + } + return @truncate(u32, hasher.final()); + } + pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type { if (self.tag_if_small_enough < Tag.no_payload_count) { return Type{ .tag_if_small_enough = self.tag_if_small_enough }; diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 5fb81df051..29043af840 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -211,7 +211,6 @@ pub const Inst = struct { pub fn Type(tag: Tag) type { return switch (tag) { - .arg, .breakpoint, .dbg_stmt, .returnvoid, @@ -268,6 +267,7 @@ pub const Inst = struct { .xor, => BinOp, + .arg => Arg, .block => Block, .@"break" => Break, .breakvoid => BreakVoid, @@ -431,6 +431,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Arg = struct { + pub const base_tag = Tag.arg; + base: Inst, + + positionals: struct { + name: []const u8, + }, + kw_args: struct {}, + }; + pub const Block = struct { pub const base_tag = Tag.block; base: Inst, @@ -1843,7 +1853,6 @@ const EmitZIR = struct { const new_inst = switch (inst.tag) { .constant => unreachable, // excluded from function bodies - .arg => try self.emitNoOp(inst.src, .arg), .breakpoint => try self.emitNoOp(inst.src, .breakpoint), .unreach => try self.emitNoOp(inst.src, .@"unreachable"), .retvoid => try self.emitNoOp(inst.src, .returnvoid), @@ -1886,6 +1895,22 @@ const EmitZIR = struct { break :blk &new_inst.base; }, + .arg => blk: { + const old_inst = inst.castTag(.arg).?; + const new_inst = try self.arena.allocator.create(Inst.Arg); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = .arg, + }, + .positionals = .{ + .name = try self.arena.allocator.dupe(u8, mem.spanZ(old_inst.name)), + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, + .block => blk: { const old_inst = inst.castTag(.block).?; const new_inst = try self.arena.allocator.create(Inst.Block); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index f2ed8abeac..2b2739a308 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -408,7 +408,7 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileE return mod.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); } -fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { +fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { const b = try mod.requireRuntimeBlock(scope, inst.base.src); const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; const param_index = b.instructions.items.len; @@ -420,7 +420,8 @@ fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError! }); } const param_type = fn_ty.fnParamType(param_index); - return mod.addNoOp(b, inst.base.src, param_type, .arg); + const name = try scope.arena().dupeZ(u8, inst.positionals.name); + return mod.addArg(b, inst.base.src, param_type, name); } fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { -- cgit v1.2.3 From 5c1fe5861389462f309e3f0b69096a85f330dc20 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 12 Aug 2020 21:06:29 +0300 Subject: stage2: gen optional types --- src-self-hosted/Module.zig | 18 ++++++++++++++++++ src-self-hosted/astgen.zig | 12 ++++++++++++ src-self-hosted/type.zig | 8 ++++---- src-self-hosted/zir.zig | 18 ++++++++++++++++++ src-self-hosted/zir_sema.zig | 29 +++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6abd4f51e1..110fe05b8e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2476,6 +2476,24 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst } assert(inst.ty.zigTypeTag() != .Undefined); + // null to ?T + if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) { + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = inst.ty.onePossibleValue().? }); + } + + // T to ?T + if (dest_type.zigTypeTag() == .Optional) { + const child_type = dest_type.elemType(); + if (inst.value()) |val| { + if (child_type.eql(inst.ty)) { + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); + } + return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, inst.ty }); + } else if (child_type.eql(inst.ty)) { + return self.fail(scope, inst.src, "TODO optional wrap {}", .{inst.ty}); + } + } + // *[N]T to []T if (inst.ty.isSinglePointer() and dest_type.isSlice() and (!inst.ty.isConstPtr() or dest_type.isConstPtr())) diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 1827d53043..57f399d696 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -105,6 +105,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .UndefinedLiteral => return rlWrap(mod, scope, rl, try undefLiteral(mod, scope, node.castTag(.UndefinedLiteral).?)), .BoolLiteral => return rlWrap(mod, scope, rl, try boolLiteral(mod, scope, node.castTag(.BoolLiteral).?)), .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), + .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } @@ -293,6 +294,17 @@ fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerErr return addZIRUnOp(mod, scope, src, .boolnot, operand); } +fn optionalType(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + const meta_type = try addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_type), + }); + const operand = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + return addZIRUnOp(mod, scope, src, .optional_type, operand); +} + /// Identifier token -> String (allocated in scope.arena()) pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { const tree = scope.tree(); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index fbc34c27bc..eadb91ffea 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -569,16 +569,16 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .const_slice_u8, .array_u8_sentinel_0, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, + => true, // TODO lazy types .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0, .single_const_pointer => self.elemType().hasCodeGenBits(), .single_mut_pointer => self.elemType().hasCodeGenBits(), .int_signed => self.cast(Payload.IntSigned).?.bits == 0, .int_unsigned => self.cast(Payload.IntUnsigned).?.bits == 0, - .optional, - .optional_single_mut_pointer, - .optional_single_const_pointer, - => true, .c_void, .void, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index ba0c05d587..c98fcf4a74 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -212,6 +212,8 @@ pub const Inst = struct { @"unreachable", /// Bitwise XOR. `^` xor, + /// Create an optional type '?T' + optional_type, pub fn Type(tag: Tag) type { return switch (tag) { @@ -240,6 +242,7 @@ pub const Inst = struct { .typeof, .single_const_ptr_type, .single_mut_ptr_type, + .optional_type, => UnOp, .add, @@ -372,6 +375,7 @@ pub const Inst = struct { .subwrap, .typeof, .xor, + .optional_type, => false, .@"break", @@ -2242,6 +2246,20 @@ const EmitZIR = struct { std.debug.panic("TODO implement emitType for {}", .{ty}); } }, + .Optional => { + const inst = try self.arena.allocator.create(Inst.UnOp); + inst.* = .{ + .base = .{ + .src = src, + .tag = .optional_type, + }, + .positionals = .{ + .operand = (try self.emitType(src, ty.elemType())).inst, + }, + .kw_args = .{}, + }; + return self.emitUnnamedDecl(&inst.base); + }, else => std.debug.panic("TODO implement emitType for {}", .{ty}), }, } diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index e36487e8d5..e241caefc9 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -106,6 +106,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false), .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), + .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), } } @@ -620,6 +621,34 @@ fn analyzeInstIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) I return mod.fail(scope, inttype.base.src, "TODO implement inttype", .{}); } +fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp) InnerError!*Inst { + const child_type = try resolveType(mod, scope, optional.positionals.operand); + + return mod.constType(scope, optional.base.src, Type.initPayload(switch (child_type.tag()) { + .single_const_pointer => blk: { + const payload = try scope.arena().create(Type.Payload.OptionalSingleConstPointer); + payload.* = .{ + .pointee_type = child_type.elemType(), + }; + break :blk &payload.base; + }, + .single_mut_pointer => blk: { + const payload = try scope.arena().create(Type.Payload.OptionalSingleMutPointer); + payload.* = .{ + .pointee_type = child_type.elemType(), + }; + break :blk &payload.base; + }, + else => blk: { + const payload = try scope.arena().create(Type.Payload.Optional); + payload.* = .{ + .child_type = child_type, + }; + break :blk &payload.base; + }, + })); +} + fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { const return_type = try resolveType(mod, scope, fntype.positionals.return_type); -- cgit v1.2.3 From 4a40282391f0b92a83a6a8c269c27a32be92884a Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 12 Aug 2020 22:30:14 +0300 Subject: stage2: implement unwrap optional --- src-self-hosted/Module.zig | 26 ++++++++++++++++++++++++-- src-self-hosted/astgen.zig | 12 ++++++++++++ src-self-hosted/codegen.zig | 10 ++++++++++ src-self-hosted/ir.zig | 22 ++++++++++++++++++++++ src-self-hosted/zir.zig | 35 +++++++++++++++++++++++++++++++++++ src-self-hosted/zir_sema.zig | 42 +++++++++++++++++++++++++++++++++++++++++- test/stage2/compare_output.zig | 5 +++++ 7 files changed, 149 insertions(+), 3 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 110fe05b8e..bbbcde9a71 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2016,6 +2016,28 @@ pub fn addCall( return &inst.base; } +pub fn addUnwrapOptional( + self: *Module, + block: *Scope.Block, + src: usize, + ty: Type, + operand: *Inst, + safety_check: bool, +) !*Inst { + const inst = try block.arena.create(Inst.UnwrapOptional); + inst.* = .{ + .base = .{ + .tag = .unwrap_optional, + .ty = ty, + .src = src, + }, + .operand = operand, + .safety_check = safety_check, + }; + try block.instructions.append(self.gpa, &inst.base); + return &inst.base; +} + pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { const const_inst = try scope.arena().create(Inst.Constant); const_inst.* = .{ @@ -2488,9 +2510,9 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst if (child_type.eql(inst.ty)) { return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); } - return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, inst.ty }); + return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, dest_type }); } else if (child_type.eql(inst.ty)) { - return self.fail(scope, inst.src, "TODO optional wrap {}", .{inst.ty}); + return self.fail(scope, inst.src, "TODO optional wrap {}", .{dest_type}); } } diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 57f399d696..ed3f8ab1b6 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -106,6 +106,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .BoolLiteral => return rlWrap(mod, scope, rl, try boolLiteral(mod, scope, node.castTag(.BoolLiteral).?)), .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), + .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } @@ -305,6 +306,17 @@ fn optionalType(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) Inn return addZIRUnOp(mod, scope, src, .optional_type, operand); } +fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.rtoken].start; + + const operand = try expr(mod, scope, .lvalue, node.lhs); + const unwrapped_ptr = try addZIRInst(mod, scope, src, zir.Inst.UnwrapOptional, .{ .operand = operand }, .{}); + if (rl == .lvalue) return unwrapped_ptr; + + return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); +} + /// Identifier token -> String (allocated in scope.arena()) pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { const tree = scope.tree(); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 6e8ab34478..887126ba2b 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -668,6 +668,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, + .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), } } @@ -817,6 +818,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnwrapOptional) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; + switch (arch) { + else => return self.fail(inst.base.src, "TODO implement unwrap optional for {}", .{self.target.cpu.arch}), + } + } + fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue { const elem_ty = inst.base.ty; if (!elem_ty.hasCodeGenBits()) diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index f4262592de..93952a4214 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -82,6 +82,7 @@ pub const Inst = struct { not, floatcast, intcast, + unwrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -124,6 +125,7 @@ pub const Inst = struct { .condbr => CondBr, .constant => Constant, .loop => Loop, + .unwrap_optional => UnwrapOptional, }; } @@ -420,6 +422,26 @@ pub const Inst = struct { } }; + pub const UnwrapOptional = struct { + pub const base_tag = Tag.unwrap_optional; + base: Inst, + + operand: *Inst, + safety_check: bool, + + pub fn operandCount(self: *const UnwrapOptional) usize { + return 1; + } + pub fn getOperand(self: *const UnwrapOptional, index: usize) ?*Inst { + var i = index; + + if (i < 1) + return self.operand; + i -= 1; + + return null; + } + }; }; pub const Body = struct { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index c98fcf4a74..84e9731126 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -214,6 +214,8 @@ pub const Inst = struct { xor, /// Create an optional type '?T' optional_type, + /// Unwraps an optional value 'lhs.?' + unwrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -301,6 +303,7 @@ pub const Inst = struct { .fntype => FnType, .elemptr => ElemPtr, .condbr => CondBr, + .unwrap_optional => UnwrapOptional, }; } @@ -376,6 +379,7 @@ pub const Inst = struct { .typeof, .xor, .optional_type, + .unwrap_optional, => false, .@"break", @@ -816,6 +820,18 @@ pub const Inst = struct { }, kw_args: struct {}, }; + + pub const UnwrapOptional = struct { + pub const base_tag = Tag.unwrap_optional; + base: Inst, + + positionals: struct { + operand: *Inst, + }, + kw_args: struct { + safety_check: bool = true, + }, + }; }; pub const ErrorMsg = struct { @@ -2141,6 +2157,25 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, + + .unwrap_optional => blk: { + const old_inst = inst.castTag(.unwrap_optional).?; + + const new_inst = try self.arena.allocator.create(Inst.UnwrapOptional); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.UnwrapOptional.base_tag, + }, + .positionals = .{ + .operand = try self.resolveInst(new_body, old_inst.operand), + }, + .kw_args = .{ + .safety_check = old_inst.safety_check, + }, + }; + break :blk &new_inst.base; + }, }; try instructions.append(new_inst); try inst_table.put(inst, new_inst); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index e241caefc9..39fbf9221a 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -107,6 +107,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), + .unwrap_optional => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional).?), } } @@ -306,8 +307,19 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const operand = try resolveInst(mod, scope, inst.positionals.operand); - const b = try mod.requireRuntimeBlock(scope, inst.base.src); const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty); + + if (operand.value()) |val| { + const ref_payload = try scope.arena().create(Value.Payload.RefVal); + ref_payload.* = .{ .val = val }; + + return mod.constInst(scope, inst.base.src, .{ + .ty = ptr_type, + .val = Value.initPayload(&ref_payload.base), + }); + } + + const b = try mod.requireRuntimeBlock(scope, inst.base.src); return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand); } @@ -649,6 +661,34 @@ fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp })); } +fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnwrapOptional) InnerError!*Inst { + const operand = try resolveInst(mod, scope, unwrap.positionals.operand); + assert(operand.ty.zigTypeTag() == .Pointer); + + if (operand.ty.elemType().zigTypeTag() != .Optional) { + return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()}); + } + + const child_type = operand.ty.elemType().elemType(); + const child_pointer = if (operand.ty.isConstPtr()) + try mod.singleConstPtrType(scope, unwrap.base.src, child_type) + else + try mod.singleMutPtrType(scope, unwrap.base.src, child_type); + + if (operand.value()) |val| { + if (val.tag() == .null_value) { + return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{}); + } + return mod.constInst(scope, unwrap.base.src, .{ + .ty = child_pointer, + .val = val, + }); + } + + const b = try mod.requireRuntimeBlock(scope, unwrap.base.src); + return mod.addUnwrapOptional(b, unwrap.base.src, child_pointer, operand, unwrap.kw_args.safety_check); +} + fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { const return_type = try resolveType(mod, scope, fntype.positionals.return_type); diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index bb3e542f13..477b5d92b1 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -31,6 +31,11 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn _start() noreturn { \\ print(); \\ + \\ const a: u32 = 2; + \\ const b: ?u32 = a; + \\ const c = b.?; + \\ if (c != 2) unreachable; + \\ \\ exit(); \\} \\ -- cgit v1.2.3 From 6b2ce9d1e99902d429d9946bcc57e8cb02f4d224 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 13 Aug 2020 14:18:44 +0300 Subject: stage2: split unwrap_optional to safe and unsafe verions --- src-self-hosted/Module.zig | 24 +---------------------- src-self-hosted/astgen.zig | 2 +- src-self-hosted/codegen.zig | 5 +++-- src-self-hosted/ir.zig | 27 ++++---------------------- src-self-hosted/type.zig | 10 ++++++++-- src-self-hosted/zir.zig | 43 +++++++++--------------------------------- src-self-hosted/zir_sema.zig | 10 +++++++--- test/stage2/compare_output.zig | 29 +++++++++++++++++++++++----- 8 files changed, 57 insertions(+), 93 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index bbbcde9a71..66da42cadf 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2016,28 +2016,6 @@ pub fn addCall( return &inst.base; } -pub fn addUnwrapOptional( - self: *Module, - block: *Scope.Block, - src: usize, - ty: Type, - operand: *Inst, - safety_check: bool, -) !*Inst { - const inst = try block.arena.create(Inst.UnwrapOptional); - inst.* = .{ - .base = .{ - .tag = .unwrap_optional, - .ty = ty, - .src = src, - }, - .operand = operand, - .safety_check = safety_check, - }; - try block.instructions.append(self.gpa, &inst.base); - return &inst.base; -} - pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { const const_inst = try scope.arena().create(Inst.Constant); const_inst.* = .{ @@ -2500,7 +2478,7 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst // null to ?T if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) { - return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = inst.ty.onePossibleValue().? }); + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = Value.initTag(.null_value) }); } // T to ?T diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index ed3f8ab1b6..95c4e63c92 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -311,7 +311,7 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si const src = tree.token_locs[node.rtoken].start; const operand = try expr(mod, scope, .lvalue, node.lhs); - const unwrapped_ptr = try addZIRInst(mod, scope, src, zir.Inst.UnwrapOptional, .{ .operand = operand }, .{}); + const unwrapped_ptr = try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand); if (rl == .lvalue) return unwrapped_ptr; return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 887126ba2b..37e775b4b3 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -668,7 +668,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, - .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), + .unwrap_optional_safe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_safe).?, true), + .unwrap_optional_unsafe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_unsafe).?, false), } } @@ -818,7 +819,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnwrapOptional) !MCValue { + fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp, safety_check: bool) !MCValue { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 93952a4214..307dcfe62e 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -82,7 +82,8 @@ pub const Inst = struct { not, floatcast, intcast, - unwrap_optional, + unwrap_optional_safe, + unwrap_optional_unsafe, pub fn Type(tag: Tag) type { return switch (tag) { @@ -103,6 +104,8 @@ pub const Inst = struct { .floatcast, .intcast, .load, + .unwrap_optional_safe, + .unwrap_optional_unsafe, => UnOp, .add, @@ -125,7 +128,6 @@ pub const Inst = struct { .condbr => CondBr, .constant => Constant, .loop => Loop, - .unwrap_optional => UnwrapOptional, }; } @@ -421,27 +423,6 @@ pub const Inst = struct { return null; } }; - - pub const UnwrapOptional = struct { - pub const base_tag = Tag.unwrap_optional; - base: Inst, - - operand: *Inst, - safety_check: bool, - - pub fn operandCount(self: *const UnwrapOptional) usize { - return 1; - } - pub fn getOperand(self: *const UnwrapOptional, index: usize) ?*Inst { - var i = index; - - if (i < 1) - return self.operand; - i -= 1; - - return null; - } - }; }; pub const Body = struct { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index eadb91ffea..ef78b046e6 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -185,8 +185,6 @@ pub const Type = extern union { return true; }, .Optional => { - if (a.tag() != b.tag()) - return false; return a.elemType().eql(b.elemType()); }, .Float, @@ -662,6 +660,10 @@ pub const Type = extern union { .optional => { const child_type = self.cast(Payload.Optional).?.child_type; if (!child_type.hasCodeGenBits()) return 1; + + if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr()) + return @divExact(target.cpu.arch.ptrBitWidth(), 8); + return child_type.abiAlignment(target); }, @@ -750,6 +752,10 @@ pub const Type = extern union { .optional => { const child_type = self.cast(Payload.Optional).?.child_type; if (!child_type.hasCodeGenBits()) return 1; + + if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr()) + return @divExact(target.cpu.arch.ptrBitWidth(), 8); + // Optional types are represented as a struct with the child type as the first // field and a boolean as the second. Since the child type's abi alignment is // guaranteed to be >= that of bool's (1 byte) the added size is exactly equal diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 84e9731126..10fb419440 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -215,7 +215,9 @@ pub const Inst = struct { /// Create an optional type '?T' optional_type, /// Unwraps an optional value 'lhs.?' - unwrap_optional, + unwrap_optional_safe, + /// Same as previous, but without safety checks. Used for orelse, if and while + unwrap_optional_unsafe, pub fn Type(tag: Tag) type { return switch (tag) { @@ -245,6 +247,8 @@ pub const Inst = struct { .single_const_ptr_type, .single_mut_ptr_type, .optional_type, + .unwrap_optional_safe, + .unwrap_optional_unsafe, => UnOp, .add, @@ -303,7 +307,6 @@ pub const Inst = struct { .fntype => FnType, .elemptr => ElemPtr, .condbr => CondBr, - .unwrap_optional => UnwrapOptional, }; } @@ -379,7 +382,8 @@ pub const Inst = struct { .typeof, .xor, .optional_type, - .unwrap_optional, + .unwrap_optional_safe, + .unwrap_optional_unsafe, => false, .@"break", @@ -820,18 +824,6 @@ pub const Inst = struct { }, kw_args: struct {}, }; - - pub const UnwrapOptional = struct { - pub const base_tag = Tag.unwrap_optional; - base: Inst, - - positionals: struct { - operand: *Inst, - }, - kw_args: struct { - safety_check: bool = true, - }, - }; }; pub const ErrorMsg = struct { @@ -1935,6 +1927,8 @@ const EmitZIR = struct { .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), + .unwrap_optional_safe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_safe).?, .unwrap_optional_safe), + .unwrap_optional_unsafe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_unsafe).?, .unwrap_optional_unsafe), .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), @@ -2157,25 +2151,6 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, - - .unwrap_optional => blk: { - const old_inst = inst.castTag(.unwrap_optional).?; - - const new_inst = try self.arena.allocator.create(Inst.UnwrapOptional); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.UnwrapOptional.base_tag, - }, - .positionals = .{ - .operand = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{ - .safety_check = old_inst.safety_check, - }, - }; - break :blk &new_inst.base; - }, }; try instructions.append(new_inst); try inst_table.put(inst, new_inst); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 39fbf9221a..b217752494 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -107,7 +107,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), - .unwrap_optional => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional).?), + .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true), + .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false), } } @@ -661,7 +662,7 @@ fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp })); } -fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnwrapOptional) InnerError!*Inst { +fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst { const operand = try resolveInst(mod, scope, unwrap.positionals.operand); assert(operand.ty.zigTypeTag() == .Pointer); @@ -686,7 +687,10 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.Unwr } const b = try mod.requireRuntimeBlock(scope, unwrap.base.src); - return mod.addUnwrapOptional(b, unwrap.base.src, child_pointer, operand, unwrap.kw_args.safety_check); + return if (safety_check) + mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_safe, operand) + else + mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_unsafe, operand); } fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 477b5d92b1..29312a8f53 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -31,11 +31,6 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn _start() noreturn { \\ print(); \\ - \\ const a: u32 = 2; - \\ const b: ?u32 = a; - \\ const c = b.?; - \\ if (c != 2) unreachable; - \\ \\ exit(); \\} \\ @@ -446,5 +441,29 @@ pub fn addCases(ctx: *TestContext) !void { , "", ); + + // Optionals + case.addCompareOutput( + \\export fn _start() noreturn { + \\ const a: u32 = 2; + \\ const b: ?u32 = a; + \\ const c = b.?; + \\ if (c != 2) unreachable; + \\ + \\ exit(); + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); } } -- cgit v1.2.3 From 16d118a8d9ce48143ef406ff7b3a1f9d62021d98 Mon Sep 17 00:00:00 2001 From: heidezomp Date: Thu, 13 Aug 2020 17:14:15 +0200 Subject: update std and src-self-hosted for std.log breaking change --- lib/std/heap/general_purpose_allocator.zig | 9 +++--- src-self-hosted/Module.zig | 18 +++++------ src-self-hosted/link.zig | 50 +++++++++++++++--------------- src-self-hosted/liveness.zig | 2 +- src-self-hosted/main.zig | 2 +- 5 files changed, 41 insertions(+), 40 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index 7b885bca36..91a01bb837 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -93,6 +93,7 @@ //! in a `std.HashMap` using the backing allocator. const std = @import("std"); +const log = std.log.scoped(.std); const math = std.math; const assert = std.debug.assert; const mem = std.mem; @@ -288,7 +289,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { if (is_used) { const slot_index = @intCast(SlotIndex, used_bits_byte * 8 + bit_index); const stack_trace = bucketStackTrace(bucket, size_class, slot_index, .alloc); - std.log.err(.std, "Memory leak detected: {}", .{stack_trace}); + log.err("Memory leak detected: {}", .{stack_trace}); leaks = true; } if (bit_index == math.maxInt(u3)) @@ -315,7 +316,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { } } for (self.large_allocations.items()) |*large_alloc| { - std.log.err(.std, "Memory leak detected: {}", .{large_alloc.value.getStackTrace()}); + log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()}); leaks = true; } return leaks; @@ -450,7 +451,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { .index = 0, }; std.debug.captureStackTrace(ret_addr, &free_stack_trace); - std.log.err(.std, "Allocation size {} bytes does not match free size {}. Allocation: {} Free: {}", .{ + log.err("Allocation size {} bytes does not match free size {}. Allocation: {} Free: {}", .{ entry.value.bytes.len, old_mem.len, entry.value.getStackTrace(), @@ -533,7 +534,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { .index = 0, }; std.debug.captureStackTrace(ret_addr, &second_free_stack_trace); - std.log.err(.std, "Double free detected. Allocation: {} First free: {} Second free: {}", .{ + log.err("Double free detected. Allocation: {} First free: {} Second free: {}", .{ alloc_stack_trace, free_stack_trace, second_free_stack_trace, diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6abd4f51e1..972b7960c8 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -6,7 +6,7 @@ const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); const assert = std.debug.assert; -const log = std.log; +const log = std.log.scoped(.module); const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; const Target = std.Target; @@ -1055,7 +1055,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { // lifetime annotations in the ZIR. var decl_arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; - std.log.debug(.module, "analyze liveness of {}\n", .{decl.name}); + log.debug("analyze liveness of {}\n", .{decl.name}); try liveness.analyze(self.gpa, &decl_arena.allocator, payload.func.analysis.success); } @@ -1117,7 +1117,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .complete => return, .outdated => blk: { - log.debug(.module, "re-analyzing {}\n", .{decl.name}); + log.debug("re-analyzing {}\n", .{decl.name}); // The exports this Decl performs will be re-discovered, so we remove them here // prior to re-analysis. @@ -1563,7 +1563,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { // Handle explicitly deleted decls from the source code. Not to be confused // with when we delete decls because they are no longer referenced. for (deleted_decls.items()) |entry| { - log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name}); + log.debug("noticed '{}' deleted from source\n", .{entry.key.name}); try self.deleteDecl(entry.key); } } @@ -1616,7 +1616,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { // Handle explicitly deleted decls from the source code. Not to be confused // with when we delete decls because they are no longer referenced. for (deleted_decls.items()) |entry| { - log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name}); + log.debug("noticed '{}' deleted from source\n", .{entry.key.name}); try self.deleteDecl(entry.key); } } @@ -1628,7 +1628,7 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { // not be present in the set, and this does nothing. decl.scope.removeDecl(decl); - log.debug(.module, "deleting decl '{}'\n", .{decl.name}); + log.debug("deleting decl '{}'\n", .{decl.name}); const name_hash = decl.fullyQualifiedNameHash(); self.decl_table.removeAssertDiscard(name_hash); // Remove itself from its dependencies, because we are about to destroy the decl pointer. @@ -1715,17 +1715,17 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { const fn_zir = func.analysis.queued; defer fn_zir.arena.promote(self.gpa).deinit(); func.analysis = .{ .in_progress = {} }; - log.debug(.module, "set {} to in_progress\n", .{decl.name}); + log.debug("set {} to in_progress\n", .{decl.name}); try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body); const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); func.analysis = .{ .success = .{ .instructions = instructions } }; - log.debug(.module, "set {} to success\n", .{decl.name}); + log.debug("set {} to success\n", .{decl.name}); } fn markOutdatedDecl(self: *Module, decl: *Decl) !void { - log.debug(.module, "mark {} outdated\n", .{decl.name}); + log.debug("mark {} outdated\n", .{decl.name}); try self.work_queue.writeItem(.{ .analyze_decl = decl }); if (self.failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 7c5e645fb5..007cc62726 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -8,7 +8,7 @@ const fs = std.fs; const elf = std.elf; const codegen = @import("codegen.zig"); const c_codegen = @import("codegen/c.zig"); -const log = std.log; +const log = std.log.scoped(.link); const DW = std.dwarf; const trace = @import("tracy.zig").trace; const leb128 = std.debug.leb; @@ -726,7 +726,7 @@ pub const File = struct { const file_size = self.base.options.program_code_size_hint; const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); - log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.program_headers.append(self.base.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, @@ -747,7 +747,7 @@ pub const File = struct { // page align. const p_align = if (self.base.options.target.os.tag == .linux) 0x1000 else @as(u16, ptr_size); const off = self.findFreeSpace(file_size, p_align); - log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. // we'll need to re-use that function anyway, in case the GOT grows and overlaps something // else in virtual memory. @@ -769,7 +769,7 @@ pub const File = struct { assert(self.shstrtab.items.len == 0); try self.shstrtab.append(self.base.allocator, 0); // need a 0 at position 0 const off = self.findFreeSpace(self.shstrtab.items.len, 1); - log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); + log.debug("found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); try self.sections.append(self.base.allocator, .{ .sh_name = try self.makeString(".shstrtab"), .sh_type = elf.SHT_STRTAB, @@ -827,7 +827,7 @@ pub const File = struct { const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); const file_size = self.base.options.symbol_count_hint * each_size; const off = self.findFreeSpace(file_size, min_align); - log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + log.debug("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.sections.append(self.base.allocator, .{ .sh_name = try self.makeString(".symtab"), @@ -869,7 +869,7 @@ pub const File = struct { const file_size_hint = 200; const p_align = 1; const off = self.findFreeSpace(file_size_hint, p_align); - log.debug(.link, "found .debug_info free space 0x{x} to 0x{x}\n", .{ + log.debug("found .debug_info free space 0x{x} to 0x{x}\n", .{ off, off + file_size_hint, }); @@ -894,7 +894,7 @@ pub const File = struct { const file_size_hint = 128; const p_align = 1; const off = self.findFreeSpace(file_size_hint, p_align); - log.debug(.link, "found .debug_abbrev free space 0x{x} to 0x{x}\n", .{ + log.debug("found .debug_abbrev free space 0x{x} to 0x{x}\n", .{ off, off + file_size_hint, }); @@ -919,7 +919,7 @@ pub const File = struct { const file_size_hint = 160; const p_align = 16; const off = self.findFreeSpace(file_size_hint, p_align); - log.debug(.link, "found .debug_aranges free space 0x{x} to 0x{x}\n", .{ + log.debug("found .debug_aranges free space 0x{x} to 0x{x}\n", .{ off, off + file_size_hint, }); @@ -944,7 +944,7 @@ pub const File = struct { const file_size_hint = 250; const p_align = 1; const off = self.findFreeSpace(file_size_hint, p_align); - log.debug(.link, "found .debug_line free space 0x{x} to 0x{x}\n", .{ + log.debug("found .debug_line free space 0x{x} to 0x{x}\n", .{ off, off + file_size_hint, }); @@ -1071,7 +1071,7 @@ pub const File = struct { debug_abbrev_sect.sh_offset = self.findFreeSpace(needed_size, 1); } debug_abbrev_sect.sh_size = needed_size; - log.debug(.link, ".debug_abbrev start=0x{x} end=0x{x}\n", .{ + log.debug(".debug_abbrev start=0x{x} end=0x{x}\n", .{ debug_abbrev_sect.sh_offset, debug_abbrev_sect.sh_offset + needed_size, }); @@ -1218,7 +1218,7 @@ pub const File = struct { debug_aranges_sect.sh_offset = self.findFreeSpace(needed_size, 16); } debug_aranges_sect.sh_size = needed_size; - log.debug(.link, ".debug_aranges start=0x{x} end=0x{x}\n", .{ + log.debug(".debug_aranges start=0x{x} end=0x{x}\n", .{ debug_aranges_sect.sh_offset, debug_aranges_sect.sh_offset + needed_size, }); @@ -1386,7 +1386,7 @@ pub const File = struct { shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); } shstrtab_sect.sh_size = needed_size; - log.debug(.link, "writing shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); + log.debug("writing shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); try self.base.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); if (!self.shdr_table_dirty) { @@ -1407,7 +1407,7 @@ pub const File = struct { debug_strtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); } debug_strtab_sect.sh_size = needed_size; - log.debug(.link, "debug_strtab start=0x{x} end=0x{x}\n", .{ debug_strtab_sect.sh_offset, debug_strtab_sect.sh_offset + needed_size }); + log.debug("debug_strtab start=0x{x} end=0x{x}\n", .{ debug_strtab_sect.sh_offset, debug_strtab_sect.sh_offset + needed_size }); try self.base.file.?.pwriteAll(self.debug_strtab.items, debug_strtab_sect.sh_offset); if (!self.shdr_table_dirty) { @@ -1441,7 +1441,7 @@ pub const File = struct { for (buf) |*shdr, i| { shdr.* = sectHeaderTo32(self.sections.items[i]); - std.log.debug(.link, "writing section {}\n", .{shdr.*}); + log.debug("writing section {}\n", .{shdr.*}); if (foreign_endian) { bswapAllFields(elf.Elf32_Shdr, shdr); } @@ -1454,7 +1454,7 @@ pub const File = struct { for (buf) |*shdr, i| { shdr.* = self.sections.items[i]; - log.debug(.link, "writing section {}\n", .{shdr.*}); + log.debug("writing section {}\n", .{shdr.*}); if (foreign_endian) { bswapAllFields(elf.Elf64_Shdr, shdr); } @@ -1465,10 +1465,10 @@ pub const File = struct { self.shdr_table_dirty = false; } if (self.entry_addr == null and self.base.options.output_mode == .Exe) { - log.debug(.link, "flushing. no_entry_point_found = true\n", .{}); + log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { - log.debug(.link, "flushing. no_entry_point_found = false\n", .{}); + log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; try self.writeElfHeader(); } @@ -1797,10 +1797,10 @@ pub const File = struct { try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); if (self.local_symbol_free_list.popOrNull()) |i| { - log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name }); + log.debug("reusing symbol index {} for {}\n", .{ i, decl.name }); decl.link.elf.local_sym_index = i; } else { - log.debug(.link, "allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); + log.debug("allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); decl.link.elf.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1993,11 +1993,11 @@ pub const File = struct { !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link.elf, code.len, required_alignment); - log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); if (vaddr != local_sym.st_value) { local_sym.st_value = vaddr; - log.debug(.link, " (writing new offset table entry)\n", .{}); + log.debug(" (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.elf.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.elf.offset_table_index); } @@ -2015,7 +2015,7 @@ pub const File = struct { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const vaddr = try self.allocateTextBlock(&decl.link.elf, code.len, required_alignment); - log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); errdefer self.freeTextBlock(&decl.link.elf); local_sym.* = .{ @@ -2125,7 +2125,7 @@ pub const File = struct { if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) { const new_offset = self.findFreeSpace(needed_size, 1); const existing_size = last_src_fn.off; - log.debug(.link, "moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{ + log.debug("moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{ existing_size, debug_line_sect.sh_offset, new_offset, @@ -2204,7 +2204,7 @@ pub const File = struct { try dbg_info_buffer.writer().print("{}\x00", .{ty}); }, else => { - log.err(.compiler, "TODO implement .debug_info for type '{}'", .{ty}); + std.log.scoped(.compiler).err("TODO implement .debug_info for type '{}'", .{ty}); try dbg_info_buffer.append(abbrev_pad1); }, } @@ -2276,7 +2276,7 @@ pub const File = struct { if (needed_size > self.allocatedSize(debug_info_sect.sh_offset)) { const new_offset = self.findFreeSpace(needed_size, 1); const existing_size = last_decl.dbg_info_off; - log.debug(.link, "moving .debug_info section: {} bytes from 0x{x} to 0x{x}\n", .{ + log.debug("moving .debug_info section: {} bytes from 0x{x} to 0x{x}\n", .{ existing_size, debug_info_sect.sh_offset, new_offset, diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index e8f80f30d5..6d2af35f32 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -114,5 +114,5 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void @panic("Handle liveness analysis for instructions with many parameters"); } - std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{ base.tag, base.deaths }); + std.log.scoped(.liveness).debug("analyze {}: 0b{b}\n", .{ base.tag, base.deaths }); } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 76d8651646..61b32e0ea5 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -560,7 +560,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo }); } } else { - std.log.info(.compiler, "Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms}); + std.log.scoped(.compiler).info("Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms}); } if (zir_out_path) |zop| { -- cgit v1.2.3 From 13e472aa2a8113df6417c09727297a6106127f8e Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 13 Aug 2020 16:06:42 +0300 Subject: translate-c: add return if one is needed --- lib/std/hash/auto_hash.zig | 2 +- src-self-hosted/Module.zig | 4 +-- src-self-hosted/translate_c.zig | 40 +++++++++++++++++++++++++++ test/run_translated_c.zig | 1 - test/translate_c.zig | 61 +++++++++++++++++++++++++++++------------ 5 files changed, 86 insertions(+), 22 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index 85f8e4b0d2..996d6ede38 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -129,7 +129,7 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { } }, - .Union => |info| blk: { + .Union => |info| { if (info.tag_type) |tag_type| { const tag = meta.activeTag(key); const s = hash(hasher, tag, strat); diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6abd4f51e1..88e59b5a2f 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2798,7 +2798,7 @@ pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: val_payload.* = .{ .val = lhs_val + rhs_val }; break :blk &val_payload.base; }, - 128 => blk: { + 128 => { return self.fail(scope, src, "TODO Implement addition for big floats", .{}); }, else => unreachable, @@ -2832,7 +2832,7 @@ pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: val_payload.* = .{ .val = lhs_val - rhs_val }; break :blk &val_payload.base; }, - 128 => blk: { + 128 => { return self.fail(scope, src, "TODO Implement substraction for big floats", .{}); }, else => unreachable, diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index de8f633076..2382375fc5 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -628,6 +628,46 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { error.UnsupportedType, => return failDecl(c, fn_decl_loc, fn_name, "unable to translate function", .{}), }; + // add return statement if the function didn't have one + blk: { + const fn_ty = @ptrCast(*const ZigClangFunctionType, fn_type); + + if (ZigClangFunctionType_getNoReturnAttr(fn_ty)) break :blk; + const return_qt = ZigClangFunctionType_getReturnType(fn_ty); + if (isCVoid(return_qt)) break :blk; + + if (block_scope.statements.items.len > 0) { + var last = block_scope.statements.items[block_scope.statements.items.len - 1]; + while (true) { + switch (last.tag) { + .Block => { + const stmts = last.castTag(.Block).?.statements(); + if (stmts.len == 0) break; + + last = stmts[stmts.len - 1]; + }, + // no extra return needed + .Return => break :blk, + else => break, + } + } + } + + const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{ + .ltoken = try appendToken(rp.c, .Keyword_return, "return"), + .tag = .Return, + }, .{ + .rhs = transZeroInitExpr(rp, scope, fn_decl_loc, ZigClangQualType_getTypePtr(return_qt)) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.UnsupportedTranslation, + error.UnsupportedType, + => return failDecl(c, fn_decl_loc, fn_name, "unable to create a return value for function", .{}), + }, + }); + _ = try appendToken(rp.c, .Semicolon, ";"); + try block_scope.statements.append(&return_expr.base); + } + const body_node = try block_scope.complete(rp.c); proto_node.setTrailer("body_node", &body_node.base); return addTopLevelDecl(c, fn_name, &proto_node.base); diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index efdc9702a4..3fa183ce3b 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -15,7 +15,6 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ } \\ if (s0 != 1) abort(); \\ if (s1 != 10) abort(); - \\ return 0; \\} , ""); diff --git a/test/translate_c.zig b/test/translate_c.zig index c632700bc5..f7e983276e 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3,12 +3,33 @@ const std = @import("std"); const CrossTarget = std.zig.CrossTarget; pub fn addCases(cases: *tests.TranslateCContext) void { + cases.add("missing return stmt", + \\int foo() {} + \\int bar() { + \\ int a = 2; + \\} + \\int baz() { + \\ return 0; + \\} + , &[_][]const u8{ + \\pub export fn foo() c_int { + \\ return 0; + \\} + \\pub export fn bar() c_int { + \\ var a: c_int = 2; + \\ return 0; + \\} + \\pub export fn baz() c_int { + \\ return 0; + \\} + }); + cases.add("alignof", - \\int main() { + \\void main() { \\ int a = _Alignof(int); \\} , &[_][]const u8{ - \\pub export fn main() c_int { + \\pub export fn main() void { \\ var a: c_int = @bitCast(c_int, @truncate(c_uint, @alignOf(c_int))); \\} }); @@ -539,6 +560,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ c = (a * b); \\ c = @divTrunc(a, b); \\ c = @rem(a, b); + \\ return 0; \\} \\pub export fn u() c_uint { \\ var a: c_uint = undefined; @@ -549,6 +571,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ c = (a *% b); \\ c = (a / b); \\ c = (a % b); + \\ return 0; \\} }); @@ -1596,13 +1619,13 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.add("worst-case assign", - \\int foo() { + \\void foo() { \\ int a; \\ int b; \\ a = b = 2; \\} , &[_][]const u8{ - \\pub export fn foo() c_int { + \\pub export fn foo() void { \\ var a: c_int = undefined; \\ var b: c_int = undefined; \\ a = blk: { @@ -1650,11 +1673,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ a = 7; \\ if (!true) break; \\ } + \\ return 0; \\} }); cases.add("for loops", - \\int foo() { + \\void foo() { \\ for (int i = 2, b = 4; i + 2; i = 2) { \\ int a = 2; \\ a = 6, 5, 7; @@ -1662,7 +1686,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ char i = 2; \\} , &[_][]const u8{ - \\pub export fn foo() c_int { + \\pub export fn foo() void { \\ { \\ var i: c_int = 2; \\ var b: c_int = 4; @@ -1712,7 +1736,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.add("switch on int", - \\int switch_fn(int i) { + \\void switch_fn(int i) { \\ int res = 0; \\ switch (i) { \\ case 0: @@ -1727,7 +1751,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ } \\} , &[_][]const u8{ - \\pub export fn switch_fn(arg_i: c_int) c_int { + \\pub export fn switch_fn(arg_i: c_int) void { \\ var i = arg_i; \\ var res: c_int = 0; \\ @"switch": { @@ -1787,13 +1811,13 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.add("assign", - \\int max(int a) { + \\void max(int a) { \\ int tmp; \\ tmp = a; \\ a = tmp; \\} , &[_][]const u8{ - \\pub export fn max(arg_a: c_int) c_int { + \\pub export fn max(arg_a: c_int) void { \\ var a = arg_a; \\ var tmp: c_int = undefined; \\ tmp = a; @@ -2082,7 +2106,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ int b; \\}a; \\float b = 2.0f; - \\int foo(void) { + \\void foo(void) { \\ struct Foo *c; \\ a.b; \\ c->b; @@ -2093,7 +2117,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\}; \\pub extern var a: struct_Foo; \\pub export var b: f32 = 2; - \\pub export fn foo() c_int { + \\pub export fn foo() void { \\ var c: [*c]struct_Foo = undefined; \\ _ = a.b; \\ _ = c.*.b; @@ -2204,11 +2228,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ if (a < b) return b; \\ if (a < b) return b else return a; \\ if (a < b) {} else {} + \\ return 0; \\} }); cases.add("if statements", - \\int foo() { + \\void foo() { \\ if (2) { \\ int a = 2; \\ } @@ -2217,7 +2242,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ } \\} , &[_][]const u8{ - \\pub export fn foo() c_int { + \\pub export fn foo() void { \\ if (true) { \\ var a: c_int = 2; \\ } @@ -2811,12 +2836,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.add("arg name aliasing decl which comes after", - \\int foo(int bar) { + \\void foo(int bar) { \\ bar = 2; \\} \\int bar = 4; , &[_][]const u8{ - \\pub export fn foo(arg_bar_1: c_int) c_int { + \\pub export fn foo(arg_bar_1: c_int) void { \\ var bar_1 = arg_bar_1; \\ bar_1 = 2; \\} @@ -2824,12 +2849,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.add("arg name aliasing macro which comes after", - \\int foo(int bar) { + \\void foo(int bar) { \\ bar = 2; \\} \\#define bar 4 , &[_][]const u8{ - \\pub export fn foo(arg_bar_1: c_int) c_int { + \\pub export fn foo(arg_bar_1: c_int) void { \\ var bar_1 = arg_bar_1; \\ bar_1 = 2; \\} -- cgit v1.2.3 From ec4953504a07f3025d5f32344180dd9b6a4de8ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 13 Aug 2020 10:04:46 -0700 Subject: stage2: implement safety checks at the zir_sema level --- src-self-hosted/Module.zig | 72 +++++++++++++++++++++++++++++++++++++++++--- src-self-hosted/codegen.zig | 5 ++- src-self-hosted/ir.zig | 6 ++-- src-self-hosted/zir.zig | 3 +- src-self-hosted/zir_sema.zig | 36 +++++++++++----------- 5 files changed, 91 insertions(+), 31 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 66da42cadf..2e847519f3 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2219,11 +2219,6 @@ pub fn wantSafety(self: *Module, scope: *Scope) bool { }; } -pub fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, src); - return self.addNoOp(b, src, Type.initTag(.noreturn), .unreach); -} - pub fn analyzeIsNull( self: *Module, scope: *Scope, @@ -2902,3 +2897,70 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { }); } } + +pub const PanicId = enum { + unreach, + unwrap_null, +}; + +pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void { + const block_inst = try parent_block.arena.create(Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = Inst.Block.base_tag, + .ty = Type.initTag(.void), + .src = ok.src, + }, + .body = .{ + .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the condbr. + }, + }; + + const ok_body: ir.Body = .{ + .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the brvoid. + }; + const brvoid = try parent_block.arena.create(Inst.BrVoid); + brvoid.* = .{ + .base = .{ + .tag = .brvoid, + .ty = Type.initTag(.noreturn), + .src = ok.src, + }, + .block = block_inst, + }; + ok_body.instructions[0] = &brvoid.base; + + var fail_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + }; + defer fail_block.instructions.deinit(mod.gpa); + + _ = try mod.safetyPanic(&fail_block, ok.src, panic_id); + + const fail_body: ir.Body = .{ .instructions = try parent_block.arena.dupe(*Inst, fail_block.instructions.items) }; + + const condbr = try parent_block.arena.create(Inst.CondBr); + condbr.* = .{ + .base = .{ + .tag = .condbr, + .ty = Type.initTag(.noreturn), + .src = ok.src, + }, + .condition = ok, + .then_body = ok_body, + .else_body = fail_body, + }; + block_inst.body.instructions[0] = &condbr.base; + + try parent_block.instructions.append(mod.gpa, &block_inst.base); +} + +pub fn safetyPanic(mod: *Module, block: *Scope.Block, src: usize, panic_id: PanicId) !*Inst { + // TODO Once we have a panic function to call, call it here instead of breakpoint. + _ = try mod.addNoOp(block, src, Type.initTag(.void), .breakpoint); + return mod.addNoOp(block, src, Type.initTag(.noreturn), .unreach); +} diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 37e775b4b3..d25fa65f47 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -668,8 +668,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, - .unwrap_optional_safe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_safe).?, true), - .unwrap_optional_unsafe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_unsafe).?, false), + .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), } } @@ -819,7 +818,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp, safety_check: bool) !MCValue { + fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 307dcfe62e..81f99f53ba 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -82,8 +82,7 @@ pub const Inst = struct { not, floatcast, intcast, - unwrap_optional_safe, - unwrap_optional_unsafe, + unwrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -104,8 +103,7 @@ pub const Inst = struct { .floatcast, .intcast, .load, - .unwrap_optional_safe, - .unwrap_optional_unsafe, + .unwrap_optional, => UnOp, .add, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 10fb419440..2e638b0755 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1927,8 +1927,7 @@ const EmitZIR = struct { .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), - .unwrap_optional_safe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_safe).?, .unwrap_optional_safe), - .unwrap_optional_unsafe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_unsafe).?, .unwrap_optional_unsafe), + .unwrap_optional => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional).?, .unwrap_optional_unsafe), .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index b217752494..74e57b7735 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -68,8 +68,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?), .as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?), .@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?), - .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?), - .unreach_nocheck => return analyzeInstUnreachNoChk(mod, scope, old_inst.castTag(.unreach_nocheck).?), + .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?, true), + .unreach_nocheck => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.unreach_nocheck).?, false), .@"return" => return analyzeInstRet(mod, scope, old_inst.castTag(.@"return").?), .returnvoid => return analyzeInstRetVoid(mod, scope, old_inst.castTag(.returnvoid).?), .@"fn" => return analyzeInstFn(mod, scope, old_inst.castTag(.@"fn").?), @@ -313,7 +313,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! if (operand.value()) |val| { const ref_payload = try scope.arena().create(Value.Payload.RefVal); ref_payload.* = .{ .val = val }; - + return mod.constInst(scope, inst.base.src, .{ .ty = ptr_type, .val = Value.initPayload(&ref_payload.base), @@ -677,7 +677,7 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp try mod.singleMutPtrType(scope, unwrap.base.src, child_type); if (operand.value()) |val| { - if (val.tag() == .null_value) { + if (val.isNull()) { return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{}); } return mod.constInst(scope, unwrap.base.src, .{ @@ -687,10 +687,11 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp } const b = try mod.requireRuntimeBlock(scope, unwrap.base.src); - return if (safety_check) - mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_safe, operand) - else - mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_unsafe, operand); + if (safety_check and mod.wantSafety(scope)) { + const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .isnonnull, operand); + try mod.addSafetyCheck(b, is_non_null, .unwrap_null); + } + return mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional, operand); } fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { @@ -1167,18 +1168,19 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body); } -fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { - return mod.analyzeUnreach(scope, unreach.base.src); -} - -fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { +fn analyzeInstUnreachable( + mod: *Module, + scope: *Scope, + unreach: *zir.Inst.NoOp, + safety_check: bool, +) InnerError!*Inst { const b = try mod.requireRuntimeBlock(scope, unreach.base.src); // TODO Add compile error for @optimizeFor occurring too late in a scope. - if (mod.wantSafety(scope)) { - // TODO Once we have a panic function to call, call it here instead of this. - _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); + if (safety_check and mod.wantSafety(scope)) { + return mod.safetyPanic(b, unreach.base.src, .unreach); + } else { + return mod.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach); } - return mod.analyzeUnreach(scope, unreach.base.src); } fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { -- cgit v1.2.3 From 28a9da8bfc1a791e0eaf8c643827da88ea70f7d1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 13 Aug 2020 20:27:25 -0700 Subject: stage2: implement while loops (bool condition) * introduce a dump() function on Module.Fn which helpfully prints to stderr the ZIR representation of a function (can be called before attempting to codegen it). This is a debugging tool. * implement x86 codegen for loops * liveness: fix analysis of conditional branches. The logic was buggy in a couple ways: - it never actually saved the results into the IR instruction (fixed now) - it incorrectly labeled operands as dying when their true death was after the conditional branch ended (fixed now) * zir rendering is enhanced to show liveness analysis results. this helps when debugging liveness analysis. * fix bug in zir rendering not numbering instructions correctly closes #6021 --- lib/std/math.zig | 1 + src-self-hosted/Module.zig | 17 +++ src-self-hosted/codegen.zig | 33 +++++- src-self-hosted/ir.zig | 16 ++- src-self-hosted/link.zig | 2 + src-self-hosted/liveness.zig | 125 +++++++++++++------- src-self-hosted/zir.zig | 259 ++++++++++++++++++++++++++++------------- test/stage2/compare_output.zig | 39 +++++++ 8 files changed, 355 insertions(+), 137 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/lib/std/math.zig b/lib/std/math.zig index 17237ea9f0..2c9065a89e 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -747,6 +747,7 @@ test "math.negateCast" { /// Cast an integer to a different integer type. If the value doesn't fit, /// return an error. +/// TODO make this an optional not an error. pub fn cast(comptime T: type, x: anytype) (error{Overflow}!T) { comptime assert(@typeInfo(T) == .Int); // must pass an integer comptime assert(@typeInfo(@TypeOf(x)) == .Int); // must pass an integer diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 8084c8ff42..03cb66d544 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -301,6 +301,23 @@ pub const Fn = struct { body: zir.Module.Body, arena: std.heap.ArenaAllocator.State, }; + + /// For debugging purposes. + pub fn dump(self: *Fn, mod: Module) void { + std.debug.print("Module.Function(name={}) ", .{self.owner_decl.name}); + switch (self.analysis) { + .queued => { + std.debug.print("queued\n", .{}); + }, + .in_progress => { + std.debug.print("in_progress\n", .{}); + }, + else => { + std.debug.print("\n", .{}); + zir.dumpFn(mod, self); + }, + } + } }; pub const Scope = struct { diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index d25fa65f47..29bcf1bd01 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -23,8 +23,6 @@ pub const BlockData = struct { relocs: std.ArrayListUnmanaged(Reloc) = .{}, }; -pub const LoopData = struct { }; - pub const Reloc = union(enum) { /// The value is an offset into the `Function` `code` from the beginning. /// To perform the reloc, write 32-bit signed little-endian integer @@ -556,7 +554,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn genBody(self: *Self, body: ir.Body) InnerError!void { - const inst_table = &self.branch_stack.items[0].inst_table; + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + const inst_table = &branch.inst_table; for (body.instructions) |inst| { const new_inst = try self.genFuncInst(inst); try inst_table.putNoClobber(self.gpa, inst, new_inst); @@ -1284,6 +1283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue { + // TODO Rework this so that the arch-independent logic isn't buried and duplicated. switch (arch) { .x86_64 => { try self.code.ensureCapacity(self.code.items.len + 6); @@ -1336,6 +1336,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn genX86CondBr(self: *Self, inst: *ir.Inst.CondBr, opcode: u8) !MCValue { + // TODO deal with liveness / deaths condbr's then_entry_deaths and else_entry_deaths self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode }); const reloc = Reloc{ .rel32 = self.code.items.len }; self.code.items.len += 4; @@ -1360,14 +1361,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue { - return self.fail(inst.base.src, "TODO codegen loop", .{}); + // A loop is a setup to be able to jump back to the beginning. + const start_index = self.code.items.len; + try self.genBody(inst.body); + try self.jump(inst.base.src, start_index); + return MCValue.unreach; + } + + /// Send control flow to the `index` of `self.code`. + fn jump(self: *Self, src: usize, index: usize) !void { + switch (arch) { + .i386, .x86_64 => { + try self.code.ensureCapacity(self.code.items.len + 5); + if (math.cast(i8, @intCast(i32, index) - (@intCast(i32, self.code.items.len + 2)))) |delta| { + self.code.appendAssumeCapacity(0xeb); // jmp rel8 + self.code.appendAssumeCapacity(@bitCast(u8, delta)); + } else |_| { + const delta = @intCast(i32, index) - (@intCast(i32, self.code.items.len + 5)); + self.code.appendAssumeCapacity(0xe9); // jmp rel32 + mem.writeIntLittle(i32, self.code.addManyAsArrayAssumeCapacity(4), delta); + } + }, + else => return self.fail(src, "TODO implement jump for {}", .{self.target.cpu.arch}), + } } fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue { if (inst.base.ty.hasCodeGenBits()) { return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); } - // A block is nothing but a setup to be able to jump to the end. + // A block is a setup to be able to jump to the end. defer inst.codegen.relocs.deinit(self.gpa); try self.genBody(inst.body); diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 81f99f53ba..9ae9518efe 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -372,11 +372,11 @@ pub const Inst = struct { then_body: Body, else_body: Body, /// Set of instructions whose lifetimes end at the start of one of the branches. - /// The `true` branch is first: `deaths[0..true_death_count]`. - /// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`. + /// The `then` branch is first: `deaths[0..then_death_count]`. + /// The `else` branch is next: `(deaths + then_death_count)[0..else_death_count]`. deaths: [*]*Inst = undefined, - true_death_count: u32 = 0, - false_death_count: u32 = 0, + then_death_count: u32 = 0, + else_death_count: u32 = 0, pub fn operandCount(self: *const CondBr) usize { return 1; @@ -390,6 +390,12 @@ pub const Inst = struct { return null; } + pub fn thenDeaths(self: *const CondBr) []*Inst { + return self.deaths[0..self.then_death_count]; + } + pub fn elseDeaths(self: *const CondBr) []*Inst { + return (self.deaths + self.then_death_count)[0..self.else_death_count]; + } }; pub const Constant = struct { @@ -411,8 +417,6 @@ pub const Inst = struct { base: Inst, body: Body, - /// This memory is reserved for codegen code to do whatever it needs to here. - codegen: codegen.LoopData = .{}, pub fn operandCount(self: *const Loop) usize { return 0; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 7c5e645fb5..a84edb65cf 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1887,6 +1887,8 @@ pub const File = struct { else => false, }; if (is_fn) { + //typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); + // For functions we need to add a prologue to the debug line program. try dbg_line_buffer.ensureCapacity(26); diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index e8f80f30d5..886d480e19 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -16,20 +16,42 @@ pub fn analyze( var table = std.AutoHashMap(*ir.Inst, void).init(gpa); defer table.deinit(); try table.ensureCapacity(body.instructions.len); - try analyzeWithTable(arena, &table, body); + try analyzeWithTable(arena, &table, null, body); } -fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), body: ir.Body) error{OutOfMemory}!void { +fn analyzeWithTable( + arena: *std.mem.Allocator, + table: *std.AutoHashMap(*ir.Inst, void), + new_set: ?*std.AutoHashMap(*ir.Inst, void), + body: ir.Body, +) error{OutOfMemory}!void { var i: usize = body.instructions.len; - while (i != 0) { - i -= 1; - const base = body.instructions[i]; - try analyzeInst(arena, table, base); + if (new_set) |ns| { + // We are only interested in doing this for instructions which are born + // before a conditional branch, so after obtaining the new set for + // each branch we prune the instructions which were born within. + while (i != 0) { + i -= 1; + const base = body.instructions[i]; + _ = ns.remove(base); + try analyzeInst(arena, table, new_set, base); + } + } else { + while (i != 0) { + i -= 1; + const base = body.instructions[i]; + try analyzeInst(arena, table, new_set, base); + } } } -fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void { +fn analyzeInst( + arena: *std.mem.Allocator, + table: *std.AutoHashMap(*ir.Inst, void), + new_set: ?*std.AutoHashMap(*ir.Inst, void), + base: *ir.Inst, +) error{OutOfMemory}!void { if (table.contains(base)) { base.deaths = 0; } else { @@ -42,56 +64,70 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void .constant => return, .block => { const inst = base.castTag(.block).?; - try analyzeWithTable(arena, table, inst.body); + try analyzeWithTable(arena, table, new_set, inst.body); // We let this continue so that it can possibly mark the block as // unreferenced below. }, + .loop => { + const inst = base.castTag(.loop).?; + try analyzeWithTable(arena, table, new_set, inst.body); + return; // Loop has no operands and it is always unreferenced. + }, .condbr => { const inst = base.castTag(.condbr).?; - var true_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); - defer true_table.deinit(); - try true_table.ensureCapacity(inst.then_body.instructions.len); - try analyzeWithTable(arena, &true_table, inst.then_body); - - var false_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); - defer false_table.deinit(); - try false_table.ensureCapacity(inst.else_body.instructions.len); - try analyzeWithTable(arena, &false_table, inst.else_body); // Each death that occurs inside one branch, but not the other, needs // to be added as a death immediately upon entering the other branch. - // During the iteration of the table, we additionally propagate the - // deaths to the parent table. - var true_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); - defer true_entry_deaths.deinit(); - var false_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); - defer false_entry_deaths.deinit(); - { - var it = false_table.iterator(); - while (it.next()) |entry| { - const false_death = entry.key; - if (!true_table.contains(false_death)) { - try true_entry_deaths.append(false_death); - // Here we are only adding to the parent table if the following iteration - // would miss it. - try table.putNoClobber(false_death, {}); - } + + var then_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); + defer then_table.deinit(); + try analyzeWithTable(arena, table, &then_table, inst.then_body); + + // Reset the table back to its state from before the branch. + for (then_table.items()) |entry| { + table.removeAssertDiscard(entry.key); + } + + var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); + defer else_table.deinit(); + try analyzeWithTable(arena, table, &else_table, inst.else_body); + + var then_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); + defer then_entry_deaths.deinit(); + var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); + defer else_entry_deaths.deinit(); + + for (else_table.items()) |entry| { + const else_death = entry.key; + if (!then_table.contains(else_death)) { + try then_entry_deaths.append(else_death); + } + } + // This loop is the same, except it's for the then branch, and it additionally + // has to put its items back into the table to undo the reset. + for (then_table.items()) |entry| { + const then_death = entry.key; + if (!else_table.contains(then_death)) { + try else_entry_deaths.append(then_death); } + _ = try table.put(then_death, {}); } - { - var it = true_table.iterator(); - while (it.next()) |entry| { - const true_death = entry.key; - try table.putNoClobber(true_death, {}); - if (!false_table.contains(true_death)) { - try false_entry_deaths.append(true_death); - } + // Now we have to correctly populate new_set. + if (new_set) |ns| { + try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len); + for (then_table.items()) |entry| { + _ = ns.putAssumeCapacity(entry.key, {}); + } + for (else_table.items()) |entry| { + _ = ns.putAssumeCapacity(entry.key, {}); } } - inst.true_death_count = std.math.cast(@TypeOf(inst.true_death_count), true_entry_deaths.items.len) catch return error.OutOfMemory; - inst.false_death_count = std.math.cast(@TypeOf(inst.false_death_count), false_entry_deaths.items.len) catch return error.OutOfMemory; - const allocated_slice = try arena.alloc(*ir.Inst, true_entry_deaths.items.len + false_entry_deaths.items.len); + inst.then_death_count = std.math.cast(@TypeOf(inst.then_death_count), then_entry_deaths.items.len) catch return error.OutOfMemory; + inst.else_death_count = std.math.cast(@TypeOf(inst.else_death_count), else_entry_deaths.items.len) catch return error.OutOfMemory; + const allocated_slice = try arena.alloc(*ir.Inst, then_entry_deaths.items.len + else_entry_deaths.items.len); inst.deaths = allocated_slice.ptr; + std.mem.copy(*ir.Inst, inst.thenDeaths(), then_entry_deaths.items); + std.mem.copy(*ir.Inst, inst.elseDeaths(), else_entry_deaths.items); // Continue on with the instruction analysis. The following code will find the condition // instruction, and the deaths flag for the CondBr instruction will indicate whether the @@ -108,6 +144,7 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void if (prev == null) { // Death. base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; + if (new_set) |ns| try ns.putNoClobber(operand, {}); } } } else { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 974e6f9ef6..ef9351047b 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -822,6 +822,16 @@ pub const Module = struct { decls: []*Decl, arena: std.heap.ArenaAllocator, error_msg: ?ErrorMsg = null, + metadata: std.AutoHashMap(*Inst, MetaData), + body_metadata: std.AutoHashMap(*Body, BodyMetaData), + + pub const MetaData = struct { + deaths: ir.Inst.DeathsInt, + }; + + pub const BodyMetaData = struct { + deaths: []*Inst, + }; pub const Body = struct { instructions: []*Inst, @@ -878,6 +888,7 @@ pub const Module = struct { .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator), .arena = std.heap.ArenaAllocator.init(allocator), .indent = 2, + .next_instr_index = undefined, }; defer write.arena.deinit(); defer write.inst_table.deinit(); @@ -889,15 +900,10 @@ pub const Module = struct { for (self.decls) |decl, decl_i| { try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); - - if (decl.inst.cast(Inst.Fn)) |fn_inst| { - for (fn_inst.positionals.body.instructions) |inst, inst_i| { - try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); - } - } } for (self.decls) |decl, i| { + write.next_instr_index = 0; try stream.print("@{} ", .{decl.name}); try write.writeInstToStream(stream, decl.inst); try stream.writeByte('\n'); @@ -914,6 +920,7 @@ const Writer = struct { loop_table: std.AutoHashMap(*Inst.Loop, []const u8), arena: std.heap.ArenaAllocator, indent: usize, + next_instr_index: usize, fn writeInstToStream( self: *Writer, @@ -944,7 +951,7 @@ const Writer = struct { if (i != 0) { try stream.writeAll(", "); } - try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name)); + try self.writeParamToStream(stream, &@field(inst.positionals, arg_field.name)); } comptime var need_comma = pos_fields.len != 0; @@ -954,13 +961,13 @@ const Writer = struct { if (@field(inst.kw_args, arg_field.name)) |non_optional| { if (need_comma) try stream.writeAll(", "); try stream.print("{}=", .{arg_field.name}); - try self.writeParamToStream(stream, non_optional); + try self.writeParamToStream(stream, &non_optional); need_comma = true; } } else { if (need_comma) try stream.writeAll(", "); try stream.print("{}=", .{arg_field.name}); - try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name)); + try self.writeParamToStream(stream, &@field(inst.kw_args, arg_field.name)); need_comma = true; } } @@ -968,7 +975,8 @@ const Writer = struct { try stream.writeByte(')'); } - fn writeParamToStream(self: *Writer, stream: anytype, param: anytype) !void { + fn writeParamToStream(self: *Writer, stream: anytype, param_ptr: anytype) !void { + const param = param_ptr.*; if (@typeInfo(@TypeOf(param)) == .Enum) { return stream.writeAll(@tagName(param)); } @@ -986,18 +994,36 @@ const Writer = struct { }, Module.Body => { try stream.writeAll("{\n"); - for (param.instructions) |inst, i| { + if (self.module.body_metadata.get(param_ptr)) |metadata| { + if (metadata.deaths.len > 0) { + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("; deaths={"); + for (metadata.deaths) |death, i| { + if (i != 0) try stream.writeAll(", "); + try self.writeInstParamToStream(stream, death); + } + try stream.writeAll("}\n"); + } + } + + for (param.instructions) |inst| { + const my_i = self.next_instr_index; + self.next_instr_index += 1; + try self.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = undefined }); try stream.writeByteNTimes(' ', self.indent); - try stream.print("%{} ", .{i}); + try stream.print("%{} ", .{my_i}); if (inst.cast(Inst.Block)) |block| { - const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{i}); + const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{my_i}); try self.block_table.put(block, name); } else if (inst.cast(Inst.Loop)) |loop| { - const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{i}); + const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{my_i}); try self.loop_table.put(loop, name); } self.indent += 2; try self.writeInstToStream(stream, inst); + if (self.module.metadata.get(inst)) |metadata| { + try stream.print(" ; deaths=0b{b}", .{metadata.deaths}); + } self.indent -= 2; try stream.writeByte('\n'); } @@ -1070,6 +1096,8 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module .decls = parser.decls.toOwnedSlice(allocator), .arena = parser.arena, .error_msg = parser.error_msg, + .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), + .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), }; } @@ -1478,7 +1506,11 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { .indent = 0, .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), + .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), + .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), }; + defer ctx.metadata.deinit(); + defer ctx.body_metadata.deinit(); defer ctx.block_table.deinit(); defer ctx.loop_table.deinit(); defer ctx.decls.deinit(allocator); @@ -1491,7 +1523,50 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { return Module{ .decls = ctx.decls.toOwnedSlice(allocator), .arena = ctx.arena, + .metadata = ctx.metadata, + .body_metadata = ctx.body_metadata, + }; +} + +/// For debugging purposes, prints a function representation to stderr. +pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { + const allocator = old_module.gpa; + var ctx: EmitZIR = .{ + .allocator = allocator, + .decls = .{}, + .arena = std.heap.ArenaAllocator.init(allocator), + .old_module = &old_module, + .next_auto_name = 0, + .names = std.StringHashMap(void).init(allocator), + .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), + .indent = 0, + .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), + .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), + .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), + .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), + }; + defer ctx.metadata.deinit(); + defer ctx.body_metadata.deinit(); + defer ctx.block_table.deinit(); + defer ctx.loop_table.deinit(); + defer ctx.decls.deinit(allocator); + defer ctx.names.deinit(); + defer ctx.primitive_table.deinit(); + defer ctx.arena.deinit(); + + const fn_ty = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; + _ = ctx.emitFn(module_fn, 0, fn_ty) catch |err| { + std.debug.print("unable to dump function: {}\n", .{err}); + return; + }; + var module = Module{ + .decls = ctx.decls.items, + .arena = ctx.arena, + .metadata = ctx.metadata, + .body_metadata = ctx.body_metadata, }; + + module.dump(); } const EmitZIR = struct { @@ -1505,6 +1580,8 @@ const EmitZIR = struct { indent: usize, block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block), loop_table: std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop), + metadata: std.AutoHashMap(*Inst, Module.MetaData), + body_metadata: std.AutoHashMap(*Module.Body, Module.BodyMetaData), fn emit(self: *EmitZIR) !void { // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced @@ -1604,7 +1681,7 @@ const EmitZIR = struct { } else blk: { break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; }; - try new_body.inst_table.putNoClobber(inst, new_inst); + _ = try new_body.inst_table.put(inst, new_inst); return new_inst; } else { return new_body.inst_table.get(inst).?; @@ -1655,6 +1732,70 @@ const EmitZIR = struct { return &declref_inst.base; } + fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl { + var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); + defer inst_table.deinit(); + + var instructions = std.ArrayList(*Inst).init(self.allocator); + defer instructions.deinit(); + + switch (module_fn.analysis) { + .queued => unreachable, + .in_progress => unreachable, + .success => |body| { + try self.emitBody(body, &inst_table, &instructions); + }, + .sema_failure => { + const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; + const fail_inst = try self.arena.allocator.create(Inst.CompileError); + fail_inst.* = .{ + .base = .{ + .src = src, + .tag = Inst.CompileError.base_tag, + }, + .positionals = .{ + .msg = try self.arena.allocator.dupe(u8, err_msg.msg), + }, + .kw_args = .{}, + }; + try instructions.append(&fail_inst.base); + }, + .dependency_failure => { + const fail_inst = try self.arena.allocator.create(Inst.CompileError); + fail_inst.* = .{ + .base = .{ + .src = src, + .tag = Inst.CompileError.base_tag, + }, + .positionals = .{ + .msg = try self.arena.allocator.dupe(u8, "depends on another failed Decl"), + }, + .kw_args = .{}, + }; + try instructions.append(&fail_inst.base); + }, + } + + const fn_type = try self.emitType(src, ty); + + const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); + mem.copy(*Inst, arena_instrs, instructions.items); + + const fn_inst = try self.arena.allocator.create(Inst.Fn); + fn_inst.* = .{ + .base = .{ + .src = src, + .tag = Inst.Fn.base_tag, + }, + .positionals = .{ + .fn_type = fn_type.inst, + .body = .{ .instructions = arena_instrs }, + }, + .kw_args = .{}, + }; + return self.emitUnnamedDecl(&fn_inst.base); + } + fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl { const allocator = &self.arena.allocator; if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| { @@ -1718,68 +1859,7 @@ const EmitZIR = struct { }, .Fn => { const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; - - var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); - defer inst_table.deinit(); - - var instructions = std.ArrayList(*Inst).init(self.allocator); - defer instructions.deinit(); - - switch (module_fn.analysis) { - .queued => unreachable, - .in_progress => unreachable, - .success => |body| { - try self.emitBody(body, &inst_table, &instructions); - }, - .sema_failure => { - const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; - const fail_inst = try self.arena.allocator.create(Inst.CompileError); - fail_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.CompileError.base_tag, - }, - .positionals = .{ - .msg = try self.arena.allocator.dupe(u8, err_msg.msg), - }, - .kw_args = .{}, - }; - try instructions.append(&fail_inst.base); - }, - .dependency_failure => { - const fail_inst = try self.arena.allocator.create(Inst.CompileError); - fail_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.CompileError.base_tag, - }, - .positionals = .{ - .msg = try self.arena.allocator.dupe(u8, "depends on another failed Decl"), - }, - .kw_args = .{}, - }; - try instructions.append(&fail_inst.base); - }, - } - - const fn_type = try self.emitType(src, typed_value.ty); - - const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); - mem.copy(*Inst, arena_instrs, instructions.items); - - const fn_inst = try self.arena.allocator.create(Inst.Fn); - fn_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Fn.base_tag, - }, - .positionals = .{ - .fn_type = fn_type.inst, - .body = .{ .instructions = arena_instrs }, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&fn_inst.base); + return self.emitFn(module_fn, src, typed_value.ty); }, .Array => { // TODO more checks to make sure this can be emitted as a string literal @@ -1810,7 +1890,7 @@ const EmitZIR = struct { } } - fn emitNoOp(self: *EmitZIR, src: usize, tag: Inst.Tag) Allocator.Error!*Inst { + fn emitNoOp(self: *EmitZIR, src: usize, old_inst: *ir.Inst.NoOp, tag: Inst.Tag) Allocator.Error!*Inst { const new_inst = try self.arena.allocator.create(Inst.NoOp); new_inst.* = .{ .base = .{ @@ -1902,10 +1982,10 @@ const EmitZIR = struct { const new_inst = switch (inst.tag) { .constant => unreachable, // excluded from function bodies - .breakpoint => try self.emitNoOp(inst.src, .breakpoint), - .unreach => try self.emitNoOp(inst.src, .@"unreachable"), - .retvoid => try self.emitNoOp(inst.src, .returnvoid), - .dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt), + .breakpoint => try self.emitNoOp(inst.src, inst.castTag(.breakpoint).?, .breakpoint), + .unreach => try self.emitNoOp(inst.src, inst.castTag(.unreach).?, .unreach_nocheck), + .retvoid => try self.emitNoOp(inst.src, inst.castTag(.retvoid).?, .returnvoid), + .dbg_stmt => try self.emitNoOp(inst.src, inst.castTag(.dbg_stmt).?, .dbg_stmt), .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot), .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"), @@ -2119,10 +2199,24 @@ const EmitZIR = struct { defer then_body.deinit(); defer else_body.deinit(); + const then_deaths = try self.arena.allocator.alloc(*Inst, old_inst.thenDeaths().len); + const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len); + + for (old_inst.thenDeaths()) |death, i| { + then_deaths[i] = try self.resolveInst(new_body, death); + } + for (old_inst.elseDeaths()) |death, i| { + else_deaths[i] = try self.resolveInst(new_body, death); + } + try self.emitBody(old_inst.then_body, inst_table, &then_body); try self.emitBody(old_inst.else_body, inst_table, &else_body); const new_inst = try self.arena.allocator.create(Inst.CondBr); + + try self.body_metadata.put(&new_inst.positionals.then_body, .{ .deaths = then_deaths }); + try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths }); + new_inst.* = .{ .base = .{ .src = inst.src, @@ -2138,6 +2232,7 @@ const EmitZIR = struct { break :blk &new_inst.base; }, }; + try self.metadata.put(new_inst, .{ .deaths = inst.deaths }); try instructions.append(new_inst); try inst_table.put(inst, new_inst); } diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 29312a8f53..f151880225 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -465,5 +465,44 @@ pub fn addCases(ctx: *TestContext) !void { , "", ); + + // While loops + case.addCompareOutput( + \\export fn _start() noreturn { + \\ var i: u32 = 0; + \\ while (i < 4) : (i += 1) print(); + \\ assert(i == 4); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (1), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("hello\n")), + \\ [arg3] "{rdx}" (6) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "hello\nhello\nhello\nhello\n", + ); } } -- cgit v1.2.3 From 5f7c7191ab16c4c9320c28652a0d4c4e53af0024 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2020 11:28:40 -0700 Subject: stage2: astgen for non-labeled blocks --- src-self-hosted/Module.zig | 2 +- src-self-hosted/astgen.zig | 18 ++++++++++++++++-- src-self-hosted/link.zig | 4 +++- 3 files changed, 20 insertions(+), 4 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 03cb66d544..2d17765ccb 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1343,7 +1343,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const body_block = body_node.cast(ast.Node.Block).?; - try astgen.blockExpr(self, params_scope, body_block); + _ = try astgen.blockExpr(self, params_scope, .none, body_block); if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index d34c2dc458..3c149cd3dd 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -107,11 +107,17 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), + .Block => return blockExpr(mod, scope, rl, node.castTag(.Block).?), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } -pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block) !void { +pub fn blockExpr( + mod: *Module, + parent_scope: *Scope, + rl: ResultLoc, + block_node: *ast.Node.Block, +) InnerError!*zir.Inst { const tracy = trace(@src()); defer tracy.end(); @@ -122,9 +128,11 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block var block_arena = std.heap.ArenaAllocator.init(mod.gpa); defer block_arena.deinit(); + const tree = parent_scope.tree(); + var scope = parent_scope; for (block_node.statements()) |statement| { - const src = scope.tree().token_locs[statement.firstToken()].start; + const src = tree.token_locs[statement.firstToken()].start; _ = try addZIRNoOp(mod, scope, src, .dbg_stmt); switch (statement.tag) { .VarDecl => { @@ -154,6 +162,12 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block }, } } + + const src = tree.token_locs[block_node.firstToken()].start; + return addZIRInstConst(mod, parent_scope, src, .{ + .ty = Type.initTag(.void), + .val = Value.initTag(.void_value), + }); } fn varDecl( diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index a84edb65cf..1faec19b60 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1887,7 +1887,9 @@ pub const File = struct { else => false, }; if (is_fn) { - //typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); + //if (mem.eql(u8, mem.spanZ(decl.name), "add")) { + // typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); + //} // For functions we need to add a prologue to the debug line program. try dbg_line_buffer.ensureCapacity(26); -- cgit v1.2.3 From 7a39a038dbe99b5189591378eef89ae5c023806d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2020 13:08:41 -0700 Subject: stage2: proper semantic analysis of improper returning of implicit void --- src-self-hosted/Module.zig | 4 ++-- src-self-hosted/zir_sema.zig | 22 ++++++++++++++++++++-- test/stage2/cbe.zig | 18 ++++++++++++++---- test/stage2/compare_output.zig | 5 +++++ 4 files changed, 41 insertions(+), 8 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 2d17765ccb..a172a4b679 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1345,8 +1345,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { _ = try astgen.blockExpr(self, params_scope, .none, body_block); - if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or - !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) + if (gen_scope.instructions.items.len == 0 or + !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()) { const src = tree.token_locs[body_block.rbrace].start; _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 97c47db9b6..862df4ec9a 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -112,8 +112,17 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! } pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void { - for (body.instructions) |src_inst| { - src_inst.analyzed_inst = try analyzeInst(mod, scope, src_inst); + for (body.instructions) |src_inst, i| { + const analyzed_inst = try analyzeInst(mod, scope, src_inst); + src_inst.analyzed_inst = analyzed_inst; + if (analyzed_inst.ty.zigTypeTag() == .NoReturn) { + for (body.instructions[i..]) |unreachable_inst| { + if (unreachable_inst.castTag(.dbg_stmt)) |dbg_stmt| { + return mod.fail(scope, dbg_stmt.base.src, "unreachable code", .{}); + } + } + break; + } } } @@ -1216,6 +1225,15 @@ fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { const b = try mod.requireRuntimeBlock(scope, inst.base.src); + if (b.func) |func| { + // Need to emit a compile error if returning void is not allowed. + const void_inst = try mod.constVoid(scope, inst.base.src); + const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty; + const casted_void = try mod.coerce(scope, fn_ty.fnReturnType(), void_inst); + if (casted_void.ty.zigTypeTag() != .Void) { + return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, casted_void); + } + } return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 5e2d56b5ed..0608221866 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -10,13 +10,19 @@ const linux_x64 = std.zig.CrossTarget{ pub fn addCases(ctx: *TestContext) !void { ctx.c("empty start function", linux_x64, - \\export fn _start() noreturn {} + \\export fn _start() noreturn { + \\ unreachable; + \\} , - \\zig_noreturn void _start(void) {} + \\zig_noreturn void _start(void) { + \\ zig_unreachable(); + \\} \\ ); ctx.c("less empty start function", linux_x64, - \\fn main() noreturn {} + \\fn main() noreturn { + \\ unreachable; + \\} \\ \\export fn _start() noreturn { \\ main(); @@ -28,7 +34,9 @@ pub fn addCases(ctx: *TestContext) !void { \\ main(); \\} \\ - \\zig_noreturn void main(void) {} + \\zig_noreturn void main(void) { + \\ zig_unreachable(); + \\} \\ ); // TODO: implement return values @@ -40,6 +48,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ : [number] "{rax}" (231), \\ [arg1] "{rdi}" (0) \\ ); + \\ unreachable; \\} \\ \\export fn _start() noreturn { @@ -62,6 +71,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ register size_t rax_constant __asm__("rax") = 231; \\ register size_t rdi_constant __asm__("rdi") = 0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ zig_unreachable(); \\} \\ ); diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index f151880225..4664d001fd 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -26,6 +26,11 @@ pub fn addCases(ctx: *TestContext) !void { case.addError("", &[_][]const u8{":1:1: error: no entry point found"}); + case.addError( + \\export fn _start() noreturn { + \\} + , &[_][]const u8{":2:1: error: expected noreturn, found void"}); + // Regular old hello world case.addCompareOutput( \\export fn _start() noreturn { -- cgit v1.2.3 From 9a5a1013a833229e1d12588615c1a05644f76cc5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2020 15:27:48 -0700 Subject: std.zig.ast: extract out Node.LabeledBlock from Node.Block This is part of an ongoing effort to reduce size of in-memory AST. This enum flattening pattern is widespread throughout the self-hosted compiler. This is a API breaking change for consumers of the self-hosted parser. --- lib/std/zig/ast.zig | 92 +++++++++++++++++++++++++++++++++++------ lib/std/zig/parse.zig | 66 ++++++++++++++++------------- lib/std/zig/render.zig | 39 +++++++++++++---- src-self-hosted/Module.zig | 2 +- src-self-hosted/astgen.zig | 40 +++++++++++------- src-self-hosted/translate_c.zig | 90 ++++++++++++++++++++-------------------- 6 files changed, 220 insertions(+), 109 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 6b2b8b4cf2..9258fc58d0 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -526,6 +526,7 @@ pub const Node = struct { Comptime, Nosuspend, Block, + LabeledBlock, // Misc DocComment, @@ -654,6 +655,7 @@ pub const Node = struct { .Comptime => Comptime, .Nosuspend => Nosuspend, .Block => Block, + .LabeledBlock => LabeledBlock, .DocComment => DocComment, .SwitchCase => SwitchCase, .SwitchElse => SwitchElse, @@ -666,6 +668,13 @@ pub const Node = struct { .FieldInitializer => FieldInitializer, }; } + + pub fn isBlock(tag: Tag) bool { + return switch (tag) { + .Block, .LabeledBlock => true, + else => false, + }; + } }; /// Prefer `castTag` to this. @@ -729,6 +738,7 @@ pub const Node = struct { .Root, .ContainerField, .Block, + .LabeledBlock, .Payload, .PointerPayload, .PointerIndexPayload, @@ -739,6 +749,7 @@ pub const Node = struct { .DocComment, .TestDecl, => return false, + .While => { const while_node = @fieldParentPtr(While, "base", n); if (while_node.@"else") |@"else"| { @@ -746,7 +757,7 @@ pub const Node = struct { continue; } - return while_node.body.tag != .Block; + return !while_node.body.tag.isBlock(); }, .For => { const for_node = @fieldParentPtr(For, "base", n); @@ -755,7 +766,7 @@ pub const Node = struct { continue; } - return for_node.body.tag != .Block; + return !for_node.body.tag.isBlock(); }, .If => { const if_node = @fieldParentPtr(If, "base", n); @@ -764,7 +775,7 @@ pub const Node = struct { continue; } - return if_node.body.tag != .Block; + return !if_node.body.tag.isBlock(); }, .Else => { const else_node = @fieldParentPtr(Else, "base", n); @@ -773,29 +784,40 @@ pub const Node = struct { }, .Defer => { const defer_node = @fieldParentPtr(Defer, "base", n); - return defer_node.expr.tag != .Block; + return !defer_node.expr.tag.isBlock(); }, .Comptime => { const comptime_node = @fieldParentPtr(Comptime, "base", n); - return comptime_node.expr.tag != .Block; + return !comptime_node.expr.tag.isBlock(); }, .Suspend => { const suspend_node = @fieldParentPtr(Suspend, "base", n); if (suspend_node.body) |body| { - return body.tag != .Block; + return !body.tag.isBlock(); } return true; }, .Nosuspend => { const nosuspend_node = @fieldParentPtr(Nosuspend, "base", n); - return nosuspend_node.expr.tag != .Block; + return !nosuspend_node.expr.tag.isBlock(); }, else => return true, } } } + /// Asserts the node is a Block or LabeledBlock and returns the statements slice. + pub fn blockStatements(base: *Node) []*Node { + if (base.castTag(.Block)) |block| { + return block.statements(); + } else if (base.castTag(.LabeledBlock)) |labeled_block| { + return labeled_block.statements(); + } else { + unreachable; + } + } + pub fn dump(self: *Node, indent: usize) void { { var i: usize = 0; @@ -1460,7 +1482,6 @@ pub const Node = struct { statements_len: NodeIndex, lbrace: TokenIndex, rbrace: TokenIndex, - label: ?TokenIndex, /// After this the caller must initialize the statements list. pub fn alloc(allocator: *mem.Allocator, statements_len: NodeIndex) !*Block { @@ -1483,10 +1504,6 @@ pub const Node = struct { } pub fn firstToken(self: *const Block) TokenIndex { - if (self.label) |label| { - return label; - } - return self.lbrace; } @@ -1509,6 +1526,57 @@ pub const Node = struct { } }; + /// The statements of the block follow LabeledBlock directly in memory. + pub const LabeledBlock = struct { + base: Node = Node{ .tag = .LabeledBlock }, + statements_len: NodeIndex, + lbrace: TokenIndex, + rbrace: TokenIndex, + label: TokenIndex, + + /// After this the caller must initialize the statements list. + pub fn alloc(allocator: *mem.Allocator, statements_len: NodeIndex) !*LabeledBlock { + const bytes = try allocator.alignedAlloc(u8, @alignOf(LabeledBlock), sizeInBytes(statements_len)); + return @ptrCast(*LabeledBlock, bytes.ptr); + } + + pub fn free(self: *LabeledBlock, allocator: *mem.Allocator) void { + const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.statements_len)]; + allocator.free(bytes); + } + + pub fn iterate(self: *const LabeledBlock, index: usize) ?*Node { + var i = index; + + if (i < self.statements_len) return self.statementsConst()[i]; + i -= self.statements_len; + + return null; + } + + pub fn firstToken(self: *const LabeledBlock) TokenIndex { + return self.label; + } + + pub fn lastToken(self: *const LabeledBlock) TokenIndex { + return self.rbrace; + } + + pub fn statements(self: *LabeledBlock) []*Node { + const decls_start = @ptrCast([*]u8, self) + @sizeOf(LabeledBlock); + return @ptrCast([*]*Node, decls_start)[0..self.statements_len]; + } + + pub fn statementsConst(self: *const LabeledBlock) []const *Node { + const decls_start = @ptrCast([*]const u8, self) + @sizeOf(LabeledBlock); + return @ptrCast([*]const *Node, decls_start)[0..self.statements_len]; + } + + fn sizeInBytes(statements_len: NodeIndex) usize { + return @sizeOf(LabeledBlock) + @sizeOf(*Node) * @as(usize, statements_len); + } + }; + pub const Defer = struct { base: Node = Node{ .tag = .Defer }, defer_token: TokenIndex, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 70a305007b..e0eb2dd895 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -364,9 +364,10 @@ const Parser = struct { const name_node = try p.expectNode(parseStringLiteralSingle, .{ .ExpectedStringLiteral = .{ .token = p.tok_i }, }); - const block_node = try p.expectNode(parseBlock, .{ - .ExpectedLBrace = .{ .token = p.tok_i }, - }); + const block_node = (try p.parseBlock(null)) orelse { + try p.errors.append(p.gpa, .{ .ExpectedLBrace = .{ .token = p.tok_i } }); + return error.ParseError; + }; const test_node = try p.arena.allocator.create(Node.TestDecl); test_node.* = .{ @@ -540,12 +541,14 @@ const Parser = struct { if (p.eatToken(.Semicolon)) |_| { break :blk null; } - break :blk try p.expectNodeRecoverable(parseBlock, .{ + const body_block = (try p.parseBlock(null)) orelse { // Since parseBlock only return error.ParseError on // a missing '}' we can assume this function was // supposed to end here. - .ExpectedSemiOrLBrace = .{ .token = p.tok_i }, - }); + try p.errors.append(p.gpa, .{ .ExpectedSemiOrLBrace = .{ .token = p.tok_i } }); + break :blk null; + }; + break :blk body_block; }, .as_type => null, }; @@ -823,10 +826,7 @@ const Parser = struct { var colon: TokenIndex = undefined; const label_token = p.parseBlockLabel(&colon); - if (try p.parseBlock()) |node| { - node.cast(Node.Block).?.label = label_token; - return node; - } + if (try p.parseBlock(label_token)) |node| return node; if (try p.parseLoopStatement()) |node| { if (node.cast(Node.For)) |for_node| { @@ -1003,14 +1003,13 @@ const Parser = struct { fn parseBlockExpr(p: *Parser) Error!?*Node { var colon: TokenIndex = undefined; const label_token = p.parseBlockLabel(&colon); - const block_node = (try p.parseBlock()) orelse { + const block_node = (try p.parseBlock(label_token)) orelse { if (label_token) |label| { p.putBackToken(label + 1); // ":" p.putBackToken(label); // IDENTIFIER } return null; }; - block_node.cast(Node.Block).?.label = label_token; return block_node; } @@ -1177,7 +1176,7 @@ const Parser = struct { p.putBackToken(token); // IDENTIFIER } - if (try p.parseBlock()) |node| return node; + if (try p.parseBlock(null)) |node| return node; if (try p.parseCurlySuffixExpr()) |node| return node; return null; @@ -1189,7 +1188,7 @@ const Parser = struct { } /// Block <- LBRACE Statement* RBRACE - fn parseBlock(p: *Parser) !?*Node { + fn parseBlock(p: *Parser, label_token: ?TokenIndex) !?*Node { const lbrace = p.eatToken(.LBrace) orelse return null; var statements = std.ArrayList(*Node).init(p.gpa); @@ -1211,16 +1210,26 @@ const Parser = struct { const statements_len = @intCast(NodeIndex, statements.items.len); - const block_node = try Node.Block.alloc(&p.arena.allocator, statements_len); - block_node.* = .{ - .label = null, - .lbrace = lbrace, - .statements_len = statements_len, - .rbrace = rbrace, - }; - std.mem.copy(*Node, block_node.statements(), statements.items); - - return &block_node.base; + if (label_token) |label| { + const block_node = try Node.LabeledBlock.alloc(&p.arena.allocator, statements_len); + block_node.* = .{ + .label = label, + .lbrace = lbrace, + .statements_len = statements_len, + .rbrace = rbrace, + }; + std.mem.copy(*Node, block_node.statements(), statements.items); + return &block_node.base; + } else { + const block_node = try Node.Block.alloc(&p.arena.allocator, statements_len); + block_node.* = .{ + .lbrace = lbrace, + .statements_len = statements_len, + .rbrace = rbrace, + }; + std.mem.copy(*Node, block_node.statements(), statements.items); + return &block_node.base; + } } /// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr) @@ -1658,11 +1667,8 @@ const Parser = struct { var colon: TokenIndex = undefined; const label = p.parseBlockLabel(&colon); - if (label) |token| { - if (try p.parseBlock()) |node| { - node.cast(Node.Block).?.label = token; - return node; - } + if (label) |label_token| { + if (try p.parseBlock(label_token)) |node| return node; } if (try p.parseLoopTypeExpr()) |node| { @@ -3440,6 +3446,7 @@ const Parser = struct { } } + /// TODO Delete this function. I don't like the inversion of control. fn expectNode( p: *Parser, parseFn: NodeParseFn, @@ -3449,6 +3456,7 @@ const Parser = struct { return (try p.expectNodeRecoverable(parseFn, err)) orelse return error.ParseError; } + /// TODO Delete this function. I don't like the inversion of control. fn expectNodeRecoverable( p: *Parser, parseFn: NodeParseFn, diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index c516250a17..7b9036f259 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -392,28 +392,50 @@ fn renderExpression( return renderToken(tree, stream, any_type.token, indent, start_col, space); }, - .Block => { - const block = @fieldParentPtr(ast.Node.Block, "base", base); + .Block, .LabeledBlock => { + const block: struct { + label: ?ast.TokenIndex, + statements: []*ast.Node, + lbrace: ast.TokenIndex, + rbrace: ast.TokenIndex, + } = b: { + if (base.castTag(.Block)) |block| { + break :b .{ + .label = null, + .statements = block.statements(), + .lbrace = block.lbrace, + .rbrace = block.rbrace, + }; + } else if (base.castTag(.LabeledBlock)) |block| { + break :b .{ + .label = block.label, + .statements = block.statements(), + .lbrace = block.lbrace, + .rbrace = block.rbrace, + }; + } else { + unreachable; + } + }; if (block.label) |label| { try renderToken(tree, stream, label, indent, start_col, Space.None); try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); } - if (block.statements_len == 0) { + if (block.statements.len == 0) { try renderToken(tree, stream, block.lbrace, indent + indent_delta, start_col, Space.None); return renderToken(tree, stream, block.rbrace, indent, start_col, space); } else { const block_indent = indent + indent_delta; try renderToken(tree, stream, block.lbrace, block_indent, start_col, Space.Newline); - const block_statements = block.statements(); - for (block_statements) |statement, i| { + for (block.statements) |statement, i| { try stream.writeByteNTimes(' ', block_indent); try renderStatement(allocator, stream, tree, block_indent, start_col, statement); - if (i + 1 < block_statements.len) { - try renderExtraNewline(tree, stream, start_col, block_statements[i + 1]); + if (i + 1 < block.statements.len) { + try renderExtraNewline(tree, stream, start_col, block.statements[i + 1]); } } @@ -1841,7 +1863,7 @@ fn renderExpression( const rparen = tree.nextToken(for_node.array_expr.lastToken()); - const body_is_block = for_node.body.tag == .Block; + const body_is_block = for_node.body.tag.isBlock(); const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken()); const body_on_same_line = body_is_block or src_one_line_to_body; @@ -2578,6 +2600,7 @@ fn renderDocCommentsToken( fn nodeIsBlock(base: *const ast.Node) bool { return switch (base.tag) { .Block, + .LabeledBlock, .If, .For, .While, diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index a172a4b679..6c0e942cc6 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1343,7 +1343,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const body_block = body_node.cast(ast.Node.Block).?; - _ = try astgen.blockExpr(self, params_scope, .none, body_block); + try astgen.blockExpr(self, params_scope, body_block); if (gen_scope.instructions.items.len == 0 or !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()) diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 3c149cd3dd..41d48c46fc 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -107,31 +107,46 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), - .Block => return blockExpr(mod, scope, rl, node.castTag(.Block).?), + .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)), + .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } -pub fn blockExpr( +pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block) InnerError!void { + const tracy = trace(@src()); + defer tracy.end(); + + try blockExprStmts(mod, parent_scope, &block_node.base, block_node.statements()); +} + +fn labeledBlockExpr( mod: *Module, parent_scope: *Scope, rl: ResultLoc, - block_node: *ast.Node.Block, + block_node: *ast.Node.LabeledBlock, ) InnerError!*zir.Inst { const tracy = trace(@src()); defer tracy.end(); - if (block_node.label) |label| { - return mod.failTok(parent_scope, label, "TODO implement labeled blocks", .{}); + const statements = block_node.statements(); + + if (statements.len == 0) { + // Hot path for `{}`. + return rlWrapVoid(mod, parent_scope, rl, &block_node.base, {}); } - var block_arena = std.heap.ArenaAllocator.init(mod.gpa); - defer block_arena.deinit(); + return mod.failNode(parent_scope, &block_node.base, "TODO implement labeled blocks", .{}); +} +fn blockExprStmts(mod: *Module, parent_scope: *Scope, node: *ast.Node, statements: []*ast.Node) !void { const tree = parent_scope.tree(); + var block_arena = std.heap.ArenaAllocator.init(mod.gpa); + defer block_arena.deinit(); + var scope = parent_scope; - for (block_node.statements()) |statement| { + for (statements) |statement| { const src = tree.token_locs[statement.firstToken()].start; _ = try addZIRNoOp(mod, scope, src, .dbg_stmt); switch (statement.tag) { @@ -162,12 +177,6 @@ pub fn blockExpr( }, } } - - const src = tree.token_locs[block_node.firstToken()].start; - return addZIRInstConst(mod, parent_scope, src, .{ - .ty = Type.initTag(.void), - .val = Value.initTag(.void_value), - }); } fn varDecl( @@ -1184,6 +1193,7 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool { .Slice, .Deref, .ArrayAccess, + .Block, => return false, // Forward the question to a sub-expression. @@ -1210,11 +1220,11 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool { .Switch, .Call, .BuiltinCall, // TODO some of these can return false + .LabeledBlock, => return true, // Depending on AST properties, they may need memory locations. .If => return node.castTag(.If).?.@"else" != null, - .Block => return node.castTag(.Block).?.label != null, } } } diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 2382375fc5..98cb68f059 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -118,19 +118,31 @@ const Scope = struct { self.* = undefined; } - fn complete(self: *Block, c: *Context) !*ast.Node.Block { + fn complete(self: *Block, c: *Context) !*ast.Node { // We reserve 1 extra statement if the parent is a Loop. This is in case of // do while, we want to put `if (cond) break;` at the end. const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .Loop); - const node = try ast.Node.Block.alloc(c.arena, alloc_len); - node.* = .{ - .statements_len = self.statements.items.len, - .lbrace = self.lbrace, - .rbrace = try appendToken(c, .RBrace, "}"), - .label = self.label, - }; - mem.copy(*ast.Node, node.statements(), self.statements.items); - return node; + const rbrace = try appendToken(c, .RBrace, "}"); + if (self.label) |label| { + const node = try ast.Node.LabeledBlock.alloc(c.arena, alloc_len); + node.* = .{ + .statements_len = self.statements.items.len, + .lbrace = self.lbrace, + .rbrace = rbrace, + .label = label, + }; + mem.copy(*ast.Node, node.statements(), self.statements.items); + return &node.base; + } else { + const node = try ast.Node.Block.alloc(c.arena, alloc_len); + node.* = .{ + .statements_len = self.statements.items.len, + .lbrace = self.lbrace, + .rbrace = rbrace, + }; + mem.copy(*ast.Node, node.statements(), self.statements.items); + return &node.base; + } } /// Given the desired name, return a name that does not shadow anything from outer scopes. @@ -320,15 +332,9 @@ pub const Context = struct { return node; } - fn createBlock(c: *Context, label: ?[]const u8, statements_len: ast.NodeIndex) !*ast.Node.Block { - const label_node = if (label) |l| blk: { - const ll = try appendIdentifier(c, l); - _ = try appendToken(c, .Colon, ":"); - break :blk ll; - } else null; + fn createBlock(c: *Context, statements_len: ast.NodeIndex) !*ast.Node.Block { const block_node = try ast.Node.Block.alloc(c.arena, statements_len); block_node.* = .{ - .label = label_node, .lbrace = try appendToken(c, .LBrace, "{"), .statements_len = statements_len, .rbrace = undefined, @@ -640,8 +646,8 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { var last = block_scope.statements.items[block_scope.statements.items.len - 1]; while (true) { switch (last.tag) { - .Block => { - const stmts = last.castTag(.Block).?.statements(); + .Block, .LabeledBlock => { + const stmts = last.blockStatements(); if (stmts.len == 0) break; last = stmts[stmts.len - 1]; @@ -669,7 +675,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { } const body_node = try block_scope.complete(rp.c); - proto_node.setTrailer("body_node", &body_node.base); + proto_node.setTrailer("body_node", body_node); return addTopLevelDecl(c, fn_name, &proto_node.base); } @@ -1275,7 +1281,7 @@ fn transStmt( .WhileStmtClass => return transWhileLoop(rp, scope, @ptrCast(*const ZigClangWhileStmt, stmt)), .DoStmtClass => return transDoWhileLoop(rp, scope, @ptrCast(*const ZigClangDoStmt, stmt)), .NullStmtClass => { - const block = try rp.c.createBlock(null, 0); + const block = try rp.c.createBlock(0); block.rbrace = try appendToken(rp.c, .RBrace, "}"); return &block.base; }, @@ -1356,7 +1362,7 @@ fn transBinaryOperator( const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen, - .expr = &block_node.base, + .expr = block_node, .rparen = rparen, }; return maybeSuppressResult(rp, scope, result_used, &grouped_expr.base); @@ -1521,8 +1527,7 @@ fn transCompoundStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCompo var block_scope = try Scope.Block.init(rp.c, scope, false); defer block_scope.deinit(); try transCompoundStmtInline(rp, &block_scope.base, stmt, &block_scope); - const node = try block_scope.complete(rp.c); - return &node.base; + return try block_scope.complete(rp.c); } fn transCStyleCastExprClass( @@ -2589,7 +2594,7 @@ fn transDoWhileLoop( // zig: if (!cond) break; // zig: } const node = try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value); - break :blk node.cast(ast.Node.Block).?; + break :blk node.castTag(.Block).?; } else blk: { // the C statement is without a block, so we need to create a block to contain it. // c: do @@ -2600,7 +2605,7 @@ fn transDoWhileLoop( // zig: if (!cond) break; // zig: } new = true; - const block = try rp.c.createBlock(null, 2); + const block = try rp.c.createBlock(2); block.statements_len = 1; // over-allocated so we can add another below block.statements()[0] = try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value); break :blk block; @@ -2659,8 +2664,7 @@ fn transForLoop( while_node.body = try transStmt(rp, &loop_scope, ZigClangForStmt_getBody(stmt), .unused, .r_value); if (block_scope) |*bs| { try bs.statements.append(&while_node.base); - const node = try bs.complete(rp.c); - return &node.base; + return try bs.complete(rp.c); } else { _ = try appendToken(rp.c, .Semicolon, ";"); return &while_node.base; @@ -2768,7 +2772,7 @@ fn transSwitch( const result_node = try switch_scope.pending_block.complete(rp.c); switch_scope.pending_block.deinit(); - return &result_node.base; + return result_node; } fn transCase( @@ -2820,7 +2824,7 @@ fn transCase( switch_scope.pending_block.deinit(); switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false); - try switch_scope.pending_block.statements.append(&pending_node.base); + try switch_scope.pending_block.statements.append(pending_node); return transStmt(rp, scope, ZigClangCaseStmt_getSubStmt(stmt), .unused, .r_value); } @@ -2857,7 +2861,7 @@ fn transDefault( const pending_node = try switch_scope.pending_block.complete(rp.c); switch_scope.pending_block.deinit(); switch_scope.pending_block = try Scope.Block.init(rp.c, scope, false); - try switch_scope.pending_block.statements.append(&pending_node.base); + try switch_scope.pending_block.statements.append(pending_node); return transStmt(rp, scope, ZigClangDefaultStmt_getSubStmt(stmt), .unused, .r_value); } @@ -2972,7 +2976,7 @@ fn transStmtExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStmtExpr, const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen, - .expr = &block_node.base, + .expr = block_node, .rparen = rparen, }; return maybeSuppressResult(rp, scope, used, &grouped_expr.base); @@ -3304,7 +3308,7 @@ fn transCreatePreCrement( const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), - .expr = &block_node.base, + .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return &grouped_expr.base; @@ -3398,7 +3402,7 @@ fn transCreatePostCrement( const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), - .expr = &block_node.base, + .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return &grouped_expr.base; @@ -3589,7 +3593,7 @@ fn transCreateCompoundAssign( const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), - .expr = &block_node.base, + .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return &grouped_expr.base; @@ -3748,7 +3752,7 @@ fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); grouped_expr.* = .{ .lparen = lparen, - .expr = &block_node.base, + .expr = block_node, .rparen = try appendToken(rp.c, .RParen, ")"), }; return maybeSuppressResult(rp, scope, used, &grouped_expr.base); @@ -4191,7 +4195,7 @@ fn transCreateNodeAssign( const block_node = try block_scope.complete(rp.c); // semicolon must immediately follow rbrace because it is the last token in a block _ = try appendToken(rp.c, .Semicolon, ";"); - return &block_node.base; + return block_node; } fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []const u8) !*ast.Node { @@ -4484,7 +4488,6 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a const block = try ast.Node.Block.alloc(c.arena, 1); block.* = .{ - .label = null, .lbrace = block_lbrace, .statements_len = 1, .rbrace = try appendToken(c, .RBrace, "}"), @@ -5475,9 +5478,9 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { if (last != .Eof and last != .Nl) return m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(last)}); _ = try appendToken(c, .Semicolon, ";"); - const type_of_arg = if (expr.tag != .Block) expr else blk: { - const blk = @fieldParentPtr(ast.Node.Block, "base", expr); - const blk_last = blk.statements()[blk.statements_len - 1]; + const type_of_arg = if (!expr.tag.isBlock()) expr else blk: { + const stmts = expr.blockStatements(); + const blk_last = stmts[stmts.len - 1]; const br = blk_last.cast(ast.Node.ControlFlowExpression).?; break :blk br.getRHS().?; }; @@ -5500,7 +5503,7 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { .visib_token = pub_tok, .extern_export_inline_token = inline_tok, .name_token = name_tok, - .body_node = &block_node.base, + .body_node = block_node, }); mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); @@ -5555,8 +5558,7 @@ fn parseCExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!*ast.Node { const break_node = try transCreateNodeBreak(c, block_scope.label, last); try block_scope.statements.append(&break_node.base); - const block_node = try block_scope.complete(c); - return &block_node.base; + return try block_scope.complete(c); }, else => { m.i -= 1; -- cgit v1.2.3 From b49d3672f3fc722925a6dad4070f7faa92dd1878 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Aug 2020 16:42:36 -0700 Subject: stage2 astgen for LabeledBlock --- src-self-hosted/Module.zig | 1 + src-self-hosted/astgen.zig | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6c0e942cc6..9ae477312a 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -737,6 +737,7 @@ pub const Scope = struct { arena: *Allocator, /// The first N instructions in a function body ZIR are arg instructions. instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, + label: ?ast.TokenIndex = null, }; /// This is always a `const` local and importantly the `inst` is a value type, not a pointer. diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 41d48c46fc..6cf2ff184d 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -129,14 +129,24 @@ fn labeledBlockExpr( const tracy = trace(@src()); defer tracy.end(); - const statements = block_node.statements(); + var block_scope: Scope.GenZIR = .{ + .parent = parent_scope, + .decl = parent_scope.decl().?, + .arena = parent_scope.arena(), + .instructions = .{}, + .label = block_node.label, + }; + defer block_scope.instructions.deinit(mod.gpa); - if (statements.len == 0) { - // Hot path for `{}`. - return rlWrapVoid(mod, parent_scope, rl, &block_node.base, {}); - } + try blockExprStmts(mod, &block_scope.base, &block_node.base, block_node.statements()); - return mod.failNode(parent_scope, &block_node.base, "TODO implement labeled blocks", .{}); + const tree = parent_scope.tree(); + const src = tree.token_locs[block_node.lbrace].start; + const block = try addZIRInstBlock(mod, parent_scope, src, .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + }); + + return &block.base; } fn blockExprStmts(mod: *Module, parent_scope: *Scope, node: *ast.Node, statements: []*ast.Node) !void { -- cgit v1.2.3 From 0f3f96c85095876e7e6f3f00e60915ec41f63700 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 15 Aug 2020 00:52:25 -0700 Subject: stage2: astgen for labeled blocks and labeled breaks --- src-self-hosted/Module.zig | 8 +++- src-self-hosted/astgen.zig | 102 ++++++++++++++++++++++++++++++++++++++----- src-self-hosted/zir_sema.zig | 2 +- 3 files changed, 98 insertions(+), 14 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 9ae477312a..2fad7e7a00 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -737,7 +737,13 @@ pub const Scope = struct { arena: *Allocator, /// The first N instructions in a function body ZIR are arg instructions. instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, - label: ?ast.TokenIndex = null, + label: ?Label = null, + + pub const Label = struct { + token: ast.TokenIndex, + block_inst: *zir.Inst.Block, + result_loc: astgen.ResultLoc, + }; }; /// This is always a `const` local and importantly the `inst` is a value type, not a pointer. diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 4dec87f364..1747d77df3 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -121,6 +121,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)), .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?), + .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)), + .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), .Catch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Catch", .{}), .BoolAnd => return mod.failNode(scope, node, "TODO implement astgen.expr for .BoolAnd", .{}), @@ -150,7 +152,6 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .For => return mod.failNode(scope, node, "TODO implement astgen.expr for .For", .{}), .Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}), .Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}), - .Break => return mod.failNode(scope, node, "TODO implement astgen.expr for .Break", .{}), .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}), .ErrorType => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorType", .{}), .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}), @@ -167,6 +168,55 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr } } +fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { + const tree = parent_scope.tree(); + const src = tree.token_locs[node.ltoken].start; + + if (node.getLabel()) |break_label| { + // Look for the label in the scope. + var scope = parent_scope; + while (true) { + switch (scope.tag) { + .gen_zir => { + const gen_zir = scope.cast(Scope.GenZIR).?; + if (gen_zir.label) |label| { + if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) { + if (node.getRHS()) |rhs| { + // Most result location types can be forwarded directly; however + // if we need to write to a pointer which has an inferred type, + // proper type inference requires peer type resolution on the block's + // break operand expressions. + const branch_rl: ResultLoc = switch (label.result_loc) { + .discard, .none, .ty, .ptr, .lvalue => label.result_loc, + .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = label.block_inst }, + }; + const operand = try expr(mod, parent_scope, branch_rl, rhs); + return try addZIRInst(mod, scope, src, zir.Inst.Break, .{ + .block = label.block_inst, + .operand = operand, + }, .{}); + } else { + return try addZIRInst(mod, scope, src, zir.Inst.BreakVoid, .{ + .block = label.block_inst, + }, .{}); + } + } + } + scope = gen_zir.parent; + }, + .local_val => scope = scope.cast(Scope.LocalVal).?.parent, + .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + else => { + const label_name = try identifierTokenString(mod, parent_scope, break_label); + return mod.failTok(parent_scope, break_label, "label not found: '{}'", .{label_name}); + }, + } + } + } else { + return mod.failNode(parent_scope, &node.base, "TODO implement break from loop", .{}); + } +} + pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block) InnerError!void { const tracy = trace(@src()); defer tracy.end(); @@ -183,24 +233,44 @@ fn labeledBlockExpr( const tracy = trace(@src()); defer tracy.end(); + const tree = parent_scope.tree(); + const src = tree.token_locs[block_node.lbrace].start; + + // Create the Block ZIR instruction so that we can put it into the GenZIR struct + // so that break statements can reference it. + const gen_zir = parent_scope.getGenZIR(); + const block_inst = try gen_zir.arena.create(zir.Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = .block, + .src = src, + }, + .positionals = .{ + .body = .{ .instructions = undefined }, + }, + .kw_args = .{}, + }; + var block_scope: Scope.GenZIR = .{ .parent = parent_scope, .decl = parent_scope.decl().?, - .arena = parent_scope.arena(), + .arena = gen_zir.arena, .instructions = .{}, - .label = block_node.label, + // TODO @as here is working around a stage1 miscompilation bug :( + .label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{ + .token = block_node.label, + .block_inst = block_inst, + .result_loc = rl, + }), }; defer block_scope.instructions.deinit(mod.gpa); try blockExprStmts(mod, &block_scope.base, &block_node.base, block_node.statements()); - const tree = parent_scope.tree(); - const src = tree.token_locs[block_node.lbrace].start; - const block = try addZIRInstBlock(mod, parent_scope, src, .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), - }); + block_inst.positionals.body.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items); + try gen_zir.instructions.append(mod.gpa, &block_inst.base); - return &block.base; + return &block_inst.base; } fn blockExprStmts(mod: *Module, parent_scope: *Scope, node: *ast.Node, statements: []*ast.Node) !void { @@ -344,7 +414,7 @@ fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) Inne if (infix_node.lhs.castTag(.Identifier)) |ident| { // This intentionally does not support @"_" syntax. const ident_name = scope.tree().tokenSlice(ident.token); - if (std.mem.eql(u8, ident_name, "_")) { + if (mem.eql(u8, ident_name, "_")) { _ = try expr(mod, scope, .discard, infix_node.rhs); return; } @@ -404,12 +474,20 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); } +/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. +/// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used. +fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool { + const ident_name_1 = try identifierTokenString(mod, scope, token1); + const ident_name_2 = try identifierTokenString(mod, scope, token2); + return mem.eql(u8, ident_name_1, ident_name_2); +} + /// Identifier token -> String (allocated in scope.arena()) -pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { +fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { const tree = scope.tree(); const ident_name = tree.tokenSlice(token); - if (std.mem.startsWith(u8, ident_name, "@")) { + if (mem.startsWith(u8, ident_name, "@")) { const raw_string = ident_name[1..]; var bad_index: usize = undefined; return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) { diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 862df4ec9a..2fe9e5cfba 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -504,7 +504,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr .decl = parent_block.decl, .instructions = .{}, .arena = parent_block.arena, - // TODO @as here is working around a miscompilation compiler bug :( + // TODO @as here is working around a stage1 miscompilation bug :( .label = @as(?Scope.Block.Label, Scope.Block.Label{ .zir_block = inst, .results = .{}, -- cgit v1.2.3 From 93619a5e4e9ad0e29e521c2c30fc1e39f47a6ba8 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 13 Aug 2020 12:35:17 -0400 Subject: Module: panic when encountering unimplemented node --- src-self-hosted/Module.zig | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 2fad7e7a00..6272ef6d98 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1580,6 +1580,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { } } } + } else { + std.debug.panic("TODO: analyzeRootSrcFile {}", .{src_decl.tag}); } // TODO also look for global variable declarations // TODO also look for comptime blocks and exported globals -- cgit v1.2.3 From 012fac255f35b9cdbe18c14753a195de89d07d28 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 14 Aug 2020 20:25:06 +0300 Subject: stage2: fix optimization causing wrong optional child types --- src-self-hosted/Module.zig | 117 ++++++++++++++++++++++------------------ src-self-hosted/astgen.zig | 2 +- src-self-hosted/codegen.zig | 36 ++++++++++++- src-self-hosted/ir.zig | 2 + src-self-hosted/type.zig | 124 +++++++++++++++++++++++++++---------------- src-self-hosted/zir.zig | 4 +- src-self-hosted/zir_sema.zig | 29 +++++----- 7 files changed, 199 insertions(+), 115 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 2fad7e7a00..2584066499 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2200,8 +2200,11 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn }; const decl_tv = try decl.typedValue(); - const ty_payload = try scope.arena().create(Type.Payload.SingleConstPointer); - ty_payload.* = .{ .pointee_type = decl_tv.ty }; + const ty_payload = try scope.arena().create(Type.Payload.Pointer); + ty_payload.* = .{ + .base = .{ .tag = .single_const_pointer }, + .pointee_type = decl_tv.ty, + }; const val_payload = try scope.arena().create(Value.Payload.DeclRef); val_payload.* = .{ .decl = decl }; @@ -2425,6 +2428,16 @@ pub fn cmpNumeric( return self.addBinOp(b, src, Type.initTag(.bool), Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs); } +fn wrapOptional(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { + if (inst.value()) |val| { + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); + } + + // TODO how do we get the result location + const b = try self.requireRuntimeBlock(scope, inst.src); + return self.addUnOp(b, inst.src, dest_type, .wrap_optional, inst); +} + fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { if (signed) { const int_payload = try scope.arena().create(Type.Payload.IntSigned); @@ -2502,14 +2515,12 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst // T to ?T if (dest_type.zigTypeTag() == .Optional) { - const child_type = dest_type.elemType(); - if (inst.value()) |val| { - if (child_type.eql(inst.ty)) { - return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); - } - return self.fail(scope, inst.src, "TODO optional wrap {} to {}", .{ val, dest_type }); - } else if (child_type.eql(inst.ty)) { - return self.fail(scope, inst.src, "TODO optional wrap {}", .{dest_type}); + var buf: Type.Payload.Pointer = undefined; + const child_type = dest_type.optionalChild(&buf); + if (child_type.eql(inst.ty)) { + return self.wrapOptional(scope, dest_type, inst); + } else if (try self.coerceNum(scope, child_type, inst)) |some| { + return self.wrapOptional(scope, dest_type, some); } } @@ -2527,39 +2538,8 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst } // comptime known number to other number - if (inst.value()) |val| { - const src_zig_tag = inst.ty.zigTypeTag(); - const dst_zig_tag = dest_type.zigTypeTag(); - - if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) { - if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - if (val.floatHasFraction()) { - return self.fail(scope, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty }); - } - return self.fail(scope, inst.src, "TODO float to int", .{}); - } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { - if (!val.intFitsInType(dest_type, self.target())) { - return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val }); - } - return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); - } - } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) { - if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - const res = val.floatCast(scope.arena(), dest_type, self.target()) catch |err| switch (err) { - error.Overflow => return self.fail( - scope, - inst.src, - "cast of value {} to type '{}' loses information", - .{ val, dest_type }, - ), - error.OutOfMemory => return error.OutOfMemory, - }; - return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = res }); - } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { - return self.fail(scope, inst.src, "TODO int to float", .{}); - } - } - } + if (try self.coerceNum(scope, dest_type, inst)) |some| + return some; // integer widening if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) { @@ -2591,6 +2571,42 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst return self.fail(scope, inst.src, "expected {}, found {}", .{ dest_type, inst.ty }); } +pub fn coerceNum(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !?*Inst { + const val = inst.value() orelse return null; + const src_zig_tag = inst.ty.zigTypeTag(); + const dst_zig_tag = dest_type.zigTypeTag(); + + if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) { + if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { + if (val.floatHasFraction()) { + return self.fail(scope, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty }); + } + return self.fail(scope, inst.src, "TODO float to int", .{}); + } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { + if (!val.intFitsInType(dest_type, self.target())) { + return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val }); + } + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); + } + } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) { + if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { + const res = val.floatCast(scope.arena(), dest_type, self.target()) catch |err| switch (err) { + error.Overflow => return self.fail( + scope, + inst.src, + "cast of value {} to type '{}' loses information", + .{ val, dest_type }, + ), + error.OutOfMemory => return error.OutOfMemory, + }; + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = res }); + } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { + return self.fail(scope, inst.src, "TODO int to float", .{}); + } + } + return null; +} + pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst { if (ptr.ty.isConstPtr()) return self.fail(scope, src, "cannot assign to constant", .{}); @@ -2878,15 +2894,12 @@ pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: return Value.initPayload(val_payload); } -pub fn singleMutPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type { - const type_payload = try scope.arena().create(Type.Payload.SingleMutPointer); - type_payload.* = .{ .pointee_type = elem_ty }; - return Type.initPayload(&type_payload.base); -} - -pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type { - const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); - type_payload.* = .{ .pointee_type = elem_ty }; +pub fn singlePtrType(self: *Module, scope: *Scope, src: usize, mutable: bool, elem_ty: Type) error{OutOfMemory}!Type { + const type_payload = try scope.arena().create(Type.Payload.Pointer); + type_payload.* = .{ + .base = .{ .tag = if (mutable) .single_mut_pointer else .single_const_pointer }, + .pointee_type = elem_ty, + }; return Type.initPayload(&type_payload.base); } diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 795aeda5ed..fd4c5c2864 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -870,7 +870,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo const int_type_payload = try scope.arena().create(Value.Payload.IntType); int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; const result = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.comptime_int), + .ty = Type.initTag(.type), .val = Value.initPayload(&int_type_payload.base), }); return rlWrap(mod, scope, rl, result); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 12d3884308..58b7c97f7b 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -682,6 +682,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .sub => return self.genSub(inst.castTag(.sub).?), .unreach => return MCValue{ .unreach = {} }, .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), + .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?), } } @@ -840,6 +841,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } + fn genWrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue { + const optional_ty = inst.base.ty; + + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; + + // Optional type is just a boolean true + if (optional_ty.abiSize(self.target.*) == 1) + return MCValue{ .immediate = 1 }; + + switch (arch) { + else => return self.fail(inst.base.src, "TODO implement wrap optional for {}", .{self.target.cpu.arch}), + } + } + fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue { const elem_ty = inst.base.ty; if (!elem_ty.hasCodeGenBits()) @@ -2028,9 +2045,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return mcv; } - fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue { + fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) error{ CodegenFail, OutOfMemory }!MCValue { if (typed_value.val.isUndef()) - return MCValue.undef; + return MCValue{ .undef = {} }; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); switch (typed_value.ty.zigTypeTag()) { @@ -2055,6 +2072,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .ComptimeInt => unreachable, // semantic analysis prevents this .ComptimeFloat => unreachable, // semantic analysis prevents this + .Optional => { + if (typed_value.ty.isPtrLikeOptional()) { + if (typed_value.val.isNull()) + return MCValue{ .immediate = 0 }; + + var buf: Type.Payload.Pointer = undefined; + return self.genTypedValue(src, .{ + .ty = typed_value.ty.optionalChild(&buf), + .val = typed_value.val, + }); + } else if (typed_value.ty.abiSize(self.target.*) == 1) { + return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) }; + } + return self.fail(src, "TODO non pointer optionals", .{}); + }, else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}), } } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 9ae9518efe..4f83fa7030 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -83,6 +83,7 @@ pub const Inst = struct { floatcast, intcast, unwrap_optional, + wrap_optional, pub fn Type(tag: Tag) type { return switch (tag) { @@ -104,6 +105,7 @@ pub const Inst = struct { .intcast, .load, .unwrap_optional, + .wrap_optional, => UnOp, .add, diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index ef78b046e6..cc3f665c4c 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -107,6 +107,17 @@ pub const Type = extern union { return @fieldParentPtr(T, "base", self.ptr_otherwise); } + pub fn castPointer(self: Type) ?*Payload.Pointer { + return switch (self.tag()) { + .single_const_pointer, + .single_mut_pointer, + .optional_single_const_pointer, + .optional_single_mut_pointer, + => @fieldParentPtr(Payload.Pointer, "base", self.ptr_otherwise), + else => null, + }; + } + pub fn eql(a: Type, b: Type) bool { // As a shortcut, if the small tags / addresses match, we're done. if (a.tag_if_small_enough == b.tag_if_small_enough) @@ -126,8 +137,8 @@ pub const Type = extern union { .Null => return true, .Pointer => { // Hot path for common case: - if (a.cast(Payload.SingleConstPointer)) |a_payload| { - if (b.cast(Payload.SingleConstPointer)) |b_payload| { + if (a.castPointer()) |a_payload| { + if (b.castPointer()) |b_payload| { return eql(a_payload.pointee_type, b_payload.pointee_type); } } @@ -185,7 +196,9 @@ pub const Type = extern union { return true; }, .Optional => { - return a.elemType().eql(b.elemType()); + var buf_a: Payload.Pointer = undefined; + var buf_b: Payload.Pointer = undefined; + return a.optionalChild(&buf_a).eql(b.optionalChild(&buf_b)); }, .Float, .Struct, @@ -249,7 +262,8 @@ pub const Type = extern union { } }, .Optional => { - std.hash.autoHash(&hasher, self.elemType().hash()); + var buf: Payload.Pointer = undefined; + std.hash.autoHash(&hasher, self.optionalChild(&buf).hash()); }, .Float, .Struct, @@ -326,8 +340,6 @@ pub const Type = extern union { }; return Type{ .ptr_otherwise = &new_payload.base }; }, - .single_const_pointer => return self.copyPayloadSingleField(allocator, Payload.SingleConstPointer, "pointee_type"), - .single_mut_pointer => return self.copyPayloadSingleField(allocator, Payload.SingleMutPointer, "pointee_type"), .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned), .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned), .function => { @@ -346,8 +358,11 @@ pub const Type = extern union { return Type{ .ptr_otherwise = &new_payload.base }; }, .optional => return self.copyPayloadSingleField(allocator, Payload.Optional, "child_type"), - .optional_single_mut_pointer => return self.copyPayloadSingleField(allocator, Payload.OptionalSingleMutPointer, "pointee_type"), - .optional_single_const_pointer => return self.copyPayloadSingleField(allocator, Payload.OptionalSingleConstPointer, "pointee_type"), + .single_const_pointer, + .single_mut_pointer, + .optional_single_mut_pointer, + .optional_single_const_pointer, + => return self.copyPayloadSingleField(allocator, Payload.Pointer, "pointee_type"), } } @@ -441,13 +456,13 @@ pub const Type = extern union { continue; }, .single_const_pointer => { - const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", ty.ptr_otherwise); + const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); try out_stream.writeAll("*const "); ty = payload.pointee_type; continue; }, .single_mut_pointer => { - const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise); + const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); try out_stream.writeAll("*"); ty = payload.pointee_type; continue; @@ -467,13 +482,13 @@ pub const Type = extern union { continue; }, .optional_single_const_pointer => { - const payload = @fieldParentPtr(Payload.OptionalSingleConstPointer, "base", ty.ptr_otherwise); + const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); try out_stream.writeAll("?*const "); ty = payload.pointee_type; continue; }, .optional_single_mut_pointer => { - const payload = @fieldParentPtr(Payload.OptionalSingleMutPointer, "base", ty.ptr_otherwise); + const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); try out_stream.writeAll("?*"); ty = payload.pointee_type; continue; @@ -658,7 +673,8 @@ pub const Type = extern union { }, .optional => { - const child_type = self.cast(Payload.Optional).?.child_type; + var buf: Payload.Pointer = undefined; + const child_type = self.optionalChild(&buf); if (!child_type.hasCodeGenBits()) return 1; if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr()) @@ -750,7 +766,8 @@ pub const Type = extern union { }, .optional => { - const child_type = self.cast(Payload.Optional).?.child_type; + var buf: Payload.Pointer = undefined; + const child_type = self.optionalChild(&buf); if (!child_type.hasCodeGenBits()) return 1; if (child_type.zigTypeTag() == .Pointer and !child_type.isCPtr()) @@ -990,7 +1007,23 @@ pub const Type = extern union { }; } - /// Asserts the type is a pointer, optional or array type. + /// Asserts that the type is an optional + pub fn isPtrLikeOptional(self: Type) bool { + switch (self.tag()) { + .optional_single_const_pointer, .optional_single_mut_pointer => return true, + .optional => { + var buf: Payload.Pointer = undefined; + const child_type = self.optionalChild(&buf); + // optionals of zero sized pointers behave like bools + if (!child_type.hasCodeGenBits()) return false; + + return child_type.zigTypeTag() == .Pointer and !child_type.isCPtr(); + }, + else => unreachable, + } + } + + /// Asserts the type is a pointer or array type. pub fn elemType(self: Type) Type { return switch (self.tag()) { .u8, @@ -1033,16 +1066,38 @@ pub const Type = extern union { .function, .int_unsigned, .int_signed, + .optional, + .optional_single_const_pointer, + .optional_single_mut_pointer, => unreachable, .array => self.cast(Payload.Array).?.elem_type, - .single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type, - .single_mut_pointer => self.cast(Payload.SingleMutPointer).?.pointee_type, + .single_const_pointer => self.castPointer().?.pointee_type, + .single_mut_pointer => self.castPointer().?.pointee_type, .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8), .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), + }; + } + + /// Asserts that the type is an optional. + pub fn optionalChild(self: Type, buf: *Payload.Pointer) Type { + return switch (self.tag()) { .optional => self.cast(Payload.Optional).?.child_type, - .optional_single_mut_pointer => self.cast(Payload.OptionalSingleMutPointer).?.pointee_type, - .optional_single_const_pointer => self.cast(Payload.OptionalSingleConstPointer).?.pointee_type, + .optional_single_mut_pointer => { + buf.* = .{ + .base = .{ .tag = .single_mut_pointer }, + .pointee_type = self.castPointer().?.pointee_type + }; + return Type.initPayload(&buf.base); + }, + .optional_single_const_pointer => { + buf.* = .{ + .base = .{ .tag = .single_const_pointer }, + .pointee_type = self.castPointer().?.pointee_type + }; + return Type.initPayload(&buf.base); + }, + else => unreachable, }; } @@ -1901,13 +1956,8 @@ pub const Type = extern union { ty = array.elem_type; continue; }, - .single_const_pointer => { - const ptr = ty.cast(Payload.SingleConstPointer).?; - ty = ptr.pointee_type; - continue; - }, - .single_mut_pointer => { - const ptr = ty.cast(Payload.SingleMutPointer).?; + .single_const_pointer, .single_mut_pointer => { + const ptr = ty.castPointer().?; ty = ptr.pointee_type; continue; }, @@ -2049,14 +2099,8 @@ pub const Type = extern union { len: u64, }; - pub const SingleConstPointer = struct { - base: Payload = Payload{ .tag = .single_const_pointer }, - - pointee_type: Type, - }; - - pub const SingleMutPointer = struct { - base: Payload = Payload{ .tag = .single_mut_pointer }, + pub const Pointer = struct { + base: Payload, pointee_type: Type, }; @@ -2086,18 +2130,6 @@ pub const Type = extern union { child_type: Type, }; - - pub const OptionalSingleConstPointer = struct { - base: Payload = Payload{ .tag = .optional_single_const_pointer }, - - pointee_type: Type, - }; - - pub const OptionalSingleMutPointer = struct { - base: Payload = Payload{ .tag = .optional_single_mut_pointer }, - - pointee_type: Type, - }; }; }; diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 25b3b171b4..695cf0013f 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -2017,6 +2017,7 @@ const EmitZIR = struct { .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), .unwrap_optional => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional).?, .unwrap_optional_unsafe), + .wrap_optional => try self.emitCast(inst.src, new_body, inst.castTag(.wrap_optional).?, .as), .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), @@ -2360,6 +2361,7 @@ const EmitZIR = struct { } }, .Optional => { + var buf: Type.Payload.Pointer = undefined; const inst = try self.arena.allocator.create(Inst.UnOp); inst.* = .{ .base = .{ @@ -2367,7 +2369,7 @@ const EmitZIR = struct { .tag = .optional_type, }, .positionals = .{ - .operand = (try self.emitType(src, ty.elemType())).inst, + .operand = (try self.emitType(src, ty.optionalChild(&buf))).inst, }, .kw_args = .{}, }; diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 5047c61687..281bc75b1d 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -317,7 +317,7 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const operand = try resolveInst(mod, scope, inst.positionals.operand); - const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty); + const ptr_type = try mod.singlePtrType(scope, inst.base.src, false, operand.ty); if (operand.value()) |val| { const ref_payload = try scope.arena().create(Value.Payload.RefVal); @@ -358,7 +358,7 @@ fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst. fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const var_type = try resolveType(mod, scope, inst.positionals.operand); - const ptr_type = try mod.singleMutPtrType(scope, inst.base.src, var_type); + const ptr_type = try mod.singlePtrType(scope, inst.base.src, true, var_type); const b = try mod.requireRuntimeBlock(scope, inst.base.src); return mod.addNoOp(b, inst.base.src, ptr_type, .alloc); } @@ -674,15 +674,17 @@ fn analyzeInstOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp return mod.constType(scope, optional.base.src, Type.initPayload(switch (child_type.tag()) { .single_const_pointer => blk: { - const payload = try scope.arena().create(Type.Payload.OptionalSingleConstPointer); + const payload = try scope.arena().create(Type.Payload.Pointer); payload.* = .{ + .base = .{ .tag = .optional_single_const_pointer }, .pointee_type = child_type.elemType(), }; break :blk &payload.base; }, .single_mut_pointer => blk: { - const payload = try scope.arena().create(Type.Payload.OptionalSingleMutPointer); + const payload = try scope.arena().create(Type.Payload.Pointer); payload.* = .{ + .base = .{ .tag = .optional_single_mut_pointer }, .pointee_type = child_type.elemType(), }; break :blk &payload.base; @@ -705,11 +707,9 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()}); } - const child_type = operand.ty.elemType().elemType(); - const child_pointer = if (operand.ty.isConstPtr()) - try mod.singleConstPtrType(scope, unwrap.base.src, child_type) - else - try mod.singleMutPtrType(scope, unwrap.base.src, child_type); + var buf: Type.Payload.Pointer = undefined; + const child_type = try operand.ty.elemType().optionalChild(&buf).copy(scope.arena()); + const child_pointer = try mod.singlePtrType(scope, unwrap.base.src, operand.ty.isConstPtr(), child_type); if (operand.value()) |val| { if (val.isNull()) { @@ -913,8 +913,11 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne // required a larger index. const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64)); - const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); - type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; + const type_payload = try scope.arena().create(Type.Payload.Pointer); + type_payload.* = .{ + .base = .{ .tag = .single_const_pointer }, + .pointee_type = array_ptr.ty.elemType().elemType(), + }; return mod.constInst(scope, inst.base.src, .{ .ty = Type.initPayload(&type_payload.base), @@ -1279,13 +1282,13 @@ fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerErr fn analyzeInstSingleConstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const elem_type = try resolveType(mod, scope, inst.positionals.operand); - const ty = try mod.singleConstPtrType(scope, inst.base.src, elem_type); + const ty = try mod.singlePtrType(scope, inst.base.src, false, elem_type); return mod.constType(scope, inst.base.src, ty); } fn analyzeInstSingleMutPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const elem_type = try resolveType(mod, scope, inst.positionals.operand); - const ty = try mod.singleMutPtrType(scope, inst.base.src, elem_type); + const ty = try mod.singlePtrType(scope, inst.base.src, true, elem_type); return mod.constType(scope, inst.base.src, ty); } -- cgit v1.2.3 From e23fc3905f65d65a743cb60783830b85199ed83b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 16 Aug 2020 18:11:10 +0200 Subject: Add skeleton for MachO support in stage2 This commit adds an empty skeleton for MachO format support in stage2. --- src-self-hosted/Module.zig | 5 ++ src-self-hosted/link.zig | 114 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6272ef6d98..b23d446925 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1568,6 +1568,9 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { // in `Decl` to notice that the line number did not change. self.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); }, + .macho => { + // TODO Implement for MachO + }, .c => {}, } } @@ -1776,10 +1779,12 @@ fn allocateNewDecl( .contents_hash = contents_hash, .link = switch (self.bin_file.tag) { .elf => .{ .elf = link.File.Elf.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .c => .{ .c = {} }, }, .fn_link = switch (self.bin_file.tag) { .elf => .{ .elf = link.File.Elf.SrcFn.empty }, + .macho => .{ .macho = link.File.MachO.SrcFn.empty }, .c => .{ .c = {} }, }, .generation = 0, diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 76d889c581..fcdf556065 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -44,11 +44,13 @@ pub const Options = struct { pub const File = struct { pub const LinkBlock = union { elf: Elf.TextBlock, + macho: MachO.TextBlock, c: void, }; pub const LinkFn = union { elf: Elf.SrcFn, + macho: MachO.SrcFn, c: void, }; @@ -66,7 +68,7 @@ pub const File = struct { .unknown => unreachable, .coff => return error.TODOImplementCoff, .elf => return Elf.openPath(allocator, dir, sub_path, options), - .macho => return error.TODOImplementMacho, + .macho => return MachO.openPath(allocator, dir, sub_path, options), .wasm => return error.TODOImplementWasm, .c => return C.openPath(allocator, dir, sub_path, options), .hex => return error.TODOImplementHex, @@ -83,7 +85,7 @@ pub const File = struct { pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { switch (base.tag) { - .elf => { + .elf, .macho => { if (base.file != null) return; base.file = try dir.createFile(sub_path, .{ .truncate = false, @@ -106,6 +108,7 @@ pub const File = struct { pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { switch (base.tag) { .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), + .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), } } @@ -113,6 +116,7 @@ pub const File = struct { pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void { switch (base.tag) { .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), + .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), .c => {}, } } @@ -120,6 +124,7 @@ pub const File = struct { pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { switch (base.tag) { .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), + .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), .c => {}, } } @@ -128,6 +133,7 @@ pub const File = struct { if (base.file) |f| f.close(); switch (base.tag) { .elf => @fieldParentPtr(Elf, "base", base).deinit(), + .macho => @fieldParentPtr(MachO, "base", base).deinit(), .c => @fieldParentPtr(C, "base", base).deinit(), } } @@ -139,6 +145,11 @@ pub const File = struct { parent.deinit(); base.allocator.destroy(parent); }, + .macho => { + const parent = @fieldParentPtr(MachO, "base", base); + parent.deinit(); + base.allocator.destroy(parent); + }, .c => { const parent = @fieldParentPtr(C, "base", base); parent.deinit(); @@ -153,6 +164,7 @@ pub const File = struct { try switch (base.tag) { .elf => @fieldParentPtr(Elf, "base", base).flush(), + .macho => @fieldParentPtr(MachO, "base", base).flush(), .c => @fieldParentPtr(C, "base", base).flush(), }; } @@ -160,6 +172,7 @@ pub const File = struct { pub fn freeDecl(base: *File, decl: *Module.Decl) void { switch (base.tag) { .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), + .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), .c => unreachable, } } @@ -167,6 +180,7 @@ pub const File = struct { pub fn errorFlags(base: *File) ErrorFlags { return switch (base.tag) { .elf => @fieldParentPtr(Elf, "base", base).error_flags, + .macho => @fieldParentPtr(MachO, "base", base).error_flags, .c => return .{ .no_entry_point_found = false }, }; } @@ -180,12 +194,14 @@ pub const File = struct { ) !void { switch (base.tag) { .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), + .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), .c => return {}, } } pub const Tag = enum { elf, + macho, c, }; @@ -2815,6 +2831,100 @@ pub const File = struct { } }; + + pub const MachO = struct { + pub const base_tag: Tag = .macho; + + base: File, + + ptr_width: enum { p32, p64 }, + + error_flags: ErrorFlags = ErrorFlags{}, + + pub const TextBlock = struct { + pub const empty = TextBlock{}; + }; + + pub const SrcFn = struct { + pub const empty = SrcFn{}; + }; + + pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File { + assert(options.object_format == .macho); + + const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); + errdefer file.close(); + + var macho_file = try allocator.create(MachO); + errdefer allocator.destroy(macho_file); + + macho_file.* = openFile(allocator, file, options) catch |err| switch (err) { + error.IncrFailed => try createFile(allocator, file, options), + else => |e| return e, + }; + + return &macho_file.base; + } + + /// Returns error.IncrFailed if incremental update could not be performed. + fn openFile(allocator: *Allocator, file: fs.File, options: Options) !MachO { + switch (options.output_mode) { + .Exe => {}, + .Obj => {}, + .Lib => return error.IncrFailed, + } + var self: MachO = .{ + .base = .{ + .file = file, + .tag = .macho, + .options = options, + .allocator = allocator, + }, + .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { + 32 => .p32, + 64 => .p64, + else => return error.UnsupportedELFArchitecture, + }, + }; + errdefer self.deinit(); + + // TODO implement reading the macho file + return error.IncrFailed; + //try self.populateMissingMetadata(); + //return self; + } + + /// Truncates the existing file contents and overwrites the contents. + /// Returns an error if `file` is not already open with +read +write +seek abilities. + fn createFile(allocator: *Allocator, file: fs.File, options: Options) !MachO { + switch (options.output_mode) { + .Exe => return error.TODOImplementWritingMachOExeFiles, + .Obj => return error.TODOImplementWritingMachOObjFiles, + .Lib => return error.TODOImplementWritingLibFiles, + } + } + + /// Commit pending changes and write headers. + pub fn flush(self: *MachO) !void {} + + pub fn deinit(self: *MachO) void {} + + pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {} + + pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {} + + pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} + + /// Must be called only after a successful call to `updateDecl`. + pub fn updateDeclExports( + self: *MachO, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, + ) !void {} + + pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} + }; }; /// Saturating multiplication -- cgit v1.2.3 From 13b2f1e90ba9e373c655fc881836209c4fa381fa Mon Sep 17 00:00:00 2001 From: Vexu Date: Sun, 16 Aug 2020 21:51:05 +0300 Subject: address review feedback --- src-self-hosted/Module.zig | 1 - src-self-hosted/astgen.zig | 25 ++++++++++++------------- src-self-hosted/codegen.zig | 4 ++-- src-self-hosted/type.zig | 28 +++++++++++++++++++++++++--- src-self-hosted/zir_sema.zig | 3 +-- 5 files changed, 40 insertions(+), 21 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 2584066499..8eabe97c31 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2433,7 +2433,6 @@ fn wrapOptional(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*In return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); } - // TODO how do we get the result location const b = try self.requireRuntimeBlock(scope, inst.src); return self.addUnOp(b, inst.src, dest_type, .wrap_optional, inst); } diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 5335504ed2..2c772f3887 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -453,8 +453,6 @@ fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerErr } fn addressOf(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { - const tree = scope.tree(); - const src = tree.token_locs[node.op_token].start; return expr(mod, scope, .lvalue, node.rhs); } @@ -490,8 +488,6 @@ fn ptrType(mod: *Module, scope: *Scope, node: *ast.Node.PtrType) InnerError!*zir .single_const_ptr_type, child_type); } - const child_type = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); - var kw_args: std.meta.fieldInfo(zir.Inst.PtrType, "kw_args").field_type = .{}; kw_args.@"allowzero" = node.ptr_info.allowzero_token != null; if (node.ptr_info.align_info) |some| { @@ -504,7 +500,12 @@ fn ptrType(mod: *Module, scope: *Scope, node: *ast.Node.PtrType) InnerError!*zir kw_args.@"const" = node.ptr_info.const_token != null; kw_args.@"volatile" = node.ptr_info.volatile_token != null; if (node.ptr_info.sentinel) |some| { - kw_args.sentinel = try expr(mod, scope, .{ .ty = child_type }, some); + kw_args.sentinel = try expr(mod, scope, .none, some); + } + + const child_type = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + if (kw_args.sentinel) |some| { + kw_args.sentinel = try addZIRBinOp(mod, scope, some.src, .as, child_type, some); } return addZIRInst(mod, scope, src, zir.Inst.PtrType, .{ .child_type = child_type }, kw_args); @@ -629,7 +630,9 @@ const CondKind = union(enum) { const payload = payload_node.?.castTag(.PointerPayload).?; const is_ptr = payload.ptr_token != null; const ident_node = payload.value_symbol.castTag(.Identifier).?; - const ident_name = try identifierTokenString(mod, &then_scope.base, ident_node.token); + + // This intentionally does not support @"_" syntax. + const ident_name = then_scope.base.tree().tokenSlice(ident_node.token); if (mem.eql(u8, ident_name, "_")) { if (is_ptr) return mod.failTok(&then_scope.base, payload.ptr_token.?, "pointer modifier invalid on discard", .{}); @@ -646,7 +649,9 @@ const CondKind = union(enum) { const payload = payload_node.?.castTag(.Payload).?; const ident_node = payload.error_symbol.castTag(.Identifier).?; - const ident_name = try identifierTokenString(mod, &else_scope.base, ident_node.token); + + // This intentionally does not support @"_" syntax. + const ident_name = else_scope.base.tree().tokenSlice(ident_node.token); if (mem.eql(u8, ident_name, "_")) { return &else_scope.base; } @@ -660,9 +665,6 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn if (if_node.payload) |_| cond_kind = .{ .optional = null }; if (if_node.@"else") |else_node| { if (else_node.payload) |payload| { - if (cond_kind != .optional) { - return mod.failNode(scope, payload, "else payload invalid on bool conditions", .{}); - } cond_kind = .{ .err_union = null }; } } @@ -760,9 +762,6 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W if (while_node.payload) |_| cond_kind = .{ .optional = null }; if (while_node.@"else") |else_node| { if (else_node.payload) |payload| { - if (cond_kind != .optional) { - return mod.failNode(scope, payload, "else payload invalid on bool conditions", .{}); - } cond_kind = .{ .err_union = null }; } } diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index d37145a275..f49d3b41fd 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -2052,7 +2052,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return mcv; } - fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) error{ CodegenFail, OutOfMemory }!MCValue { + fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) InnerError!MCValue { if (typed_value.val.isUndef()) return MCValue{ .undef = {} }; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); @@ -2199,7 +2199,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }; } - fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { + fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) InnerError { @setCold(true); assert(self.err_msg == null); self.err_msg = try ErrorMsg.create(self.bin_file.base.allocator, src, format, args); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index cc3f665c4c..faba784f90 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -362,7 +362,7 @@ pub const Type = extern union { .single_mut_pointer, .optional_single_mut_pointer, .optional_single_const_pointer, - => return self.copyPayloadSingleField(allocator, Payload.Pointer, "pointee_type"), + => return self.copyPayloadSingleField(allocator, Payload.Pointer, "pointee_type"), } } @@ -1086,14 +1086,14 @@ pub const Type = extern union { .optional_single_mut_pointer => { buf.* = .{ .base = .{ .tag = .single_mut_pointer }, - .pointee_type = self.castPointer().?.pointee_type + .pointee_type = self.castPointer().?.pointee_type, }; return Type.initPayload(&buf.base); }, .optional_single_const_pointer => { buf.* = .{ .base = .{ .tag = .single_const_pointer }, - .pointee_type = self.castPointer().?.pointee_type + .pointee_type = self.castPointer().?.pointee_type, }; return Type.initPayload(&buf.base); }, @@ -1101,6 +1101,28 @@ pub const Type = extern union { }; } + /// Asserts that the type is an optional. + /// Same as `optionalChild` but allocates the buffer if needed. + pub fn optionalChildAlloc(self: Type, allocator: *Allocator) !Type { + return switch (self.tag()) { + .optional => self.cast(Payload.Optional).?.child_type, + .optional_single_mut_pointer, .optional_single_const_pointer => { + const payload = try allocator.create(Payload.Pointer); + payload.* = .{ + .base = .{ + .tag = if (self.tag() == .optional_single_const_pointer) + .single_const_pointer + else + .single_mut_pointer, + }, + .pointee_type = self.castPointer().?.pointee_type, + }; + return Type.initPayload(&payload.base); + }, + else => unreachable, + }; + } + /// Asserts the type is an array or vector. pub fn arrayLen(self: Type) u64 { return switch (self.tag()) { diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 362dbe7909..31ffb2cc0d 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -710,8 +710,7 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()}); } - var buf: Type.Payload.Pointer = undefined; - const child_type = try operand.ty.elemType().optionalChild(&buf).copy(scope.arena()); + const child_type = try operand.ty.elemType().optionalChildAlloc(scope.arena()); const child_pointer = try mod.singlePtrType(scope, unwrap.base.src, operand.ty.isConstPtr(), child_type); if (operand.value()) |val| { -- cgit v1.2.3 From 3370b5f1097d0610048e29b322b4cbf81ff0a431 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 7 Aug 2020 00:53:55 +0200 Subject: stage2/wasm: implement basic container generation Thus far, we only generate the type, function, export, and code sections. These are sufficient to generate and export simple functions. Codegen is currently hardcoded to `i32.const 42`, the main goal of this commit is to create infrastructure for the container format which will work with incremental compilation. --- src-self-hosted/Module.zig | 4 +- src-self-hosted/codegen/wasm.zig | 70 ++++++ src-self-hosted/link.zig | 24 ++- src-self-hosted/link/Wasm.zig | 445 +++++++++++++++++++++++++++++++++++++++ src-self-hosted/main.zig | 1 + 5 files changed, 539 insertions(+), 5 deletions(-) create mode 100644 src-self-hosted/codegen/wasm.zig create mode 100644 src-self-hosted/link/Wasm.zig (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index df7c1986f3..5e22058acf 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1571,7 +1571,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { .macho => { // TODO Implement for MachO }, - .c => {}, + .c, .wasm => {}, } } } else { @@ -1781,11 +1781,13 @@ fn allocateNewDecl( .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .c => .{ .c = {} }, + .wasm => .{ .wasm = {} }, }, .fn_link = switch (self.bin_file.tag) { .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, .c => .{ .c = {} }, + .wasm => .{ .wasm = null }, }, .generation = 0, }; diff --git a/src-self-hosted/codegen/wasm.zig b/src-self-hosted/codegen/wasm.zig new file mode 100644 index 0000000000..78d8d22ded --- /dev/null +++ b/src-self-hosted/codegen/wasm.zig @@ -0,0 +1,70 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const assert = std.debug.assert; +const leb = std.debug.leb; + +const Decl = @import("../Module.zig").Decl; +const Type = @import("../type.zig").Type; + +fn genValtype(ty: Type) u8 { + return switch (ty.tag()) { + .u32, .i32 => 0x7F, + .u64, .i64 => 0x7E, + .f32 => 0x7D, + .f64 => 0x7C, + else => @panic("TODO: Implement more types for wasm."), + }; +} + +pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void { + const ty = decl.typed_value.most_recent.typed_value.ty; + const writer = buf.writer(); + + // functype magic + try writer.writeByte(0x60); + + // param types + try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); + if (ty.fnParamLen() != 0) { + const params = try buf.allocator.alloc(Type, ty.fnParamLen()); + defer buf.allocator.free(params); + ty.fnParamTypes(params); + for (params) |param_type| try writer.writeByte(genValtype(param_type)); + } + + // return type + const return_type = ty.fnReturnType(); + switch (return_type.tag()) { + .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), + else => { + try leb.writeULEB128(writer, @as(u32, 1)); + try writer.writeByte(genValtype(return_type)); + }, + } +} + +pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { + assert(buf.items.len == 0); + const writer = buf.writer(); + + // Reserve space to write the size after generating the code + try writer.writeAll(&([1]u8{undefined} ** 5)); + + // Write the size of the locals vec + // TODO: implement locals + try leb.writeULEB128(writer, @as(u32, 0)); + + // Write instructions + + // TODO: actually implement codegen + try writer.writeByte(0x41); // i32.const + try leb.writeILEB128(writer, @as(i32, 42)); + + // Write 'end' opcode + try writer.writeByte(0x0B); + + // Fill in the size of the generated code to the reserved space at the + // beginning of the buffer. + leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, buf.items.len - 5)); +} diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index a835cc6b7c..6a51138785 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -46,12 +46,14 @@ pub const File = struct { elf: Elf.TextBlock, macho: MachO.TextBlock, c: void, + wasm: void, }; pub const LinkFn = union { elf: Elf.SrcFn, macho: MachO.SrcFn, c: void, + wasm: ?Wasm.FnData, }; tag: Tag, @@ -69,7 +71,7 @@ pub const File = struct { .coff => return error.TODOImplementCoff, .elf => return Elf.openPath(allocator, dir, sub_path, options), .macho => return MachO.openPath(allocator, dir, sub_path, options), - .wasm => return error.TODOImplementWasm, + .wasm => return Wasm.openPath(allocator, dir, sub_path, options), .c => return C.openPath(allocator, dir, sub_path, options), .hex => return error.TODOImplementHex, .raw => return error.TODOImplementRaw, @@ -93,7 +95,7 @@ pub const File = struct { .mode = determineMode(base.options), }); }, - .c => {}, + .c, .wasm => {}, } } @@ -102,6 +104,7 @@ pub const File = struct { if (base.file) |f| { f.close(); base.file = null; + } } @@ -110,6 +113,7 @@ pub const File = struct { .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), + .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl), } } @@ -117,7 +121,7 @@ pub const File = struct { switch (base.tag) { .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), - .c => {}, + .c, .wasm => {}, } } @@ -125,7 +129,7 @@ pub const File = struct { switch (base.tag) { .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), - .c => {}, + .c, .wasm => {}, } } @@ -135,6 +139,7 @@ pub const File = struct { .elf => @fieldParentPtr(Elf, "base", base).deinit(), .macho => @fieldParentPtr(MachO, "base", base).deinit(), .c => @fieldParentPtr(C, "base", base).deinit(), + .wasm => @fieldParentPtr(Wasm, "base", base).deinit(), } } @@ -155,6 +160,11 @@ pub const File = struct { parent.deinit(); base.allocator.destroy(parent); }, + .wasm => { + const parent = @fieldParentPtr(Wasm, "base", base); + parent.deinit(); + base.allocator.destroy(parent); + }, } } @@ -167,6 +177,7 @@ pub const File = struct { .elf => @fieldParentPtr(Elf, "base", base).flush(), .macho => @fieldParentPtr(MachO, "base", base).flush(), .c => @fieldParentPtr(C, "base", base).flush(), + .wasm => @fieldParentPtr(Wasm, "base", base).flush(), }; } @@ -175,6 +186,7 @@ pub const File = struct { .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), .c => unreachable, + .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), } } @@ -183,6 +195,7 @@ pub const File = struct { .elf => @fieldParentPtr(Elf, "base", base).error_flags, .macho => @fieldParentPtr(MachO, "base", base).error_flags, .c => return .{ .no_entry_point_found = false }, + .wasm => return ErrorFlags{}, }; } @@ -197,6 +210,7 @@ pub const File = struct { .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), .c => return {}, + .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), } } @@ -204,6 +218,7 @@ pub const File = struct { elf, macho, c, + wasm, }; pub const ErrorFlags = struct { @@ -2832,6 +2847,7 @@ pub const File = struct { }; pub const MachO = @import("link/MachO.zig"); + const Wasm = @import("link/Wasm.zig"); }; /// Saturating multiplication diff --git a/src-self-hosted/link/Wasm.zig b/src-self-hosted/link/Wasm.zig new file mode 100644 index 0000000000..a62cff15ee --- /dev/null +++ b/src-self-hosted/link/Wasm.zig @@ -0,0 +1,445 @@ +const Wasm = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const fs = std.fs; +const leb = std.debug.leb; + +const Module = @import("../Module.zig"); +const codegen = @import("../codegen/wasm.zig"); +const link = @import("../link.zig"); + +/// Various magic numbers defined by the wasm spec +const spec = struct { + const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm + const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1 + + const custom_id = 0; + const types_id = 1; + const imports_id = 2; + const funcs_id = 3; + const tables_id = 4; + const memories_id = 5; + const globals_id = 6; + const exports_id = 7; + const start_id = 8; + const elements_id = 9; + const code_id = 10; + const data_id = 11; +}; + +pub const base_tag = link.File.Tag.wasm; + +pub const FnData = struct { + funcidx: u32, + typeidx: u32, +}; + +base: link.File, + +types: Types, +funcs: Funcs, +exports: Exports, + +/// Array over the section structs used in the various sections above to +/// allow iteration when shifting sections to make space. +/// TODO: this should eventually be size 11 when we use all the sections. +sections: [4]*Section, + +pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File { + assert(options.object_format == .wasm); + + // TODO: read the file and keep vaild parts instead of truncating + const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true }); + errdefer file.close(); + + const wasm = try allocator.create(Wasm); + errdefer allocator.destroy(wasm); + + try file.writeAll(&(spec.magic ++ spec.version)); + + wasm.base = .{ + .tag = .wasm, + .options = options, + .file = file, + .allocator = allocator, + }; + + // TODO: this should vary depending on the section and be less arbitrary + const size = 1024; + const offset = @sizeOf(@TypeOf(spec.magic ++ spec.version)); + + wasm.types = try Types.init(file, offset, size); + wasm.funcs = try Funcs.init(file, offset + size, size, offset + 3 * size, size); + wasm.exports = try Exports.init(file, offset + 2 * size, size); + try file.setEndPos(offset + 4 * size); + + wasm.sections = [_]*Section{ + &wasm.types.typesec.section, + &wasm.funcs.funcsec, + &wasm.exports.exportsec, + &wasm.funcs.codesec.section, + }; + + return &wasm.base; +} + +pub fn deinit(self: *Wasm) void { + if (self.base.file) |f| f.close(); + self.types.deinit(); + self.funcs.deinit(); +} + +pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { + if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn) + return error.TODOImplementNonFnDeclsForWasm; + + if (decl.fn_link.wasm) |fn_data| { + self.types.free(fn_data.typeidx); + self.funcs.free(fn_data.funcidx); + } + + var buf = std.ArrayList(u8).init(self.base.allocator); + defer buf.deinit(); + + try codegen.genFunctype(&buf, decl); + const typeidx = try self.types.new(buf.items); + buf.items.len = 0; + + try codegen.genCode(&buf, decl); + const funcidx = try self.funcs.new(typeidx, buf.items); + + decl.fn_link.wasm = .{ .typeidx = typeidx, .funcidx = funcidx }; + + try self.exports.writeAll(module); +} + +pub fn updateDeclExports( + self: *Wasm, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, +) !void { + // TODO: updateDeclExports() may currently be called before updateDecl, + // presumably due to a bug. For now just rely on the following call + // being made in updateDecl(). + + //try self.exports.writeAll(module); +} + +pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { + // TODO: remove this assert when non-function Decls are implemented + assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn); + if (decl.fn_link.wasm) |fn_data| { + self.types.free(fn_data.typeidx); + self.funcs.free(fn_data.funcidx); + decl.fn_link.wasm = null; + } +} + +pub fn flush(self: *Wasm) !void {} + +/// This struct describes the location of a named section + custom section +/// padding in the output file. This is all the data we need to allow for +/// shifting sections around when padding runs out. +const Section = struct { + /// The size of a section header: 1 byte section id + 5 bytes + /// for the fixed-width ULEB128 encoded contents size. + const header_size = 1 + 5; + /// Offset of the section id byte from the start of the file. + offset: u64, + /// Size of the section, including the header and directly + /// following custom section used for padding if any. + size: u64, + + /// Resize the usable part of the section, handling the following custom + /// section used for padding. If there is not enough padding left, shift + /// all following sections to make space. Takes the current and target + /// contents sizes of the section as arguments. + fn resize(self: *Section, file: fs.File, current: u32, target: u32) !void { + // Section header + target contents size + custom section header + // + custom section name + empty custom section > owned chunk of the file + if (header_size + target + header_size + 1 + 0 > self.size) + return error.TODOImplementSectionShifting; + + const new_custom_start = self.offset + header_size + target; + const new_custom_contents_size = self.size - target - 2 * header_size; + assert(new_custom_contents_size >= 1); + // +1 for the name of the custom section, which we set to an empty string + var custom_header: [header_size + 1]u8 = undefined; + custom_header[0] = spec.custom_id; + leb.writeUnsignedFixed(5, custom_header[1..header_size], @intCast(u32, new_custom_contents_size)); + custom_header[header_size] = 0; + try file.pwriteAll(&custom_header, new_custom_start); + } +}; + +/// This can be used to manage the contents of any section which uses a vector +/// of contents. This interface maintains index stability while allowing for +/// reuse of "dead" indexes. +const VecSection = struct { + /// Represents a single entry in the vector (e.g. a type in the type section) + const Entry = struct { + /// Offset from the start of the section contents in bytes + offset: u32, + /// Size in bytes of the entry + size: u32, + }; + section: Section, + /// Size in bytes of the contents of the section. Does not include + /// the "header" containing the section id and this value. + contents_size: u32, + /// List of all entries in the contents of the section. + entries: std.ArrayListUnmanaged(Entry) = std.ArrayListUnmanaged(Entry){}, + /// List of indexes of unreferenced entries which may be + /// overwritten and reused. + dead_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, + + /// Write the headers of the section and custom padding section + fn init(comptime section_id: u8, file: fs.File, offset: u64, initial_size: u64) !VecSection { + // section id, section size, empty vector, custom section id, + // custom section size, empty custom section name + var initial_data: [1 + 5 + 5 + 1 + 5 + 1]u8 = undefined; + + assert(initial_size >= initial_data.len); + + comptime var i = 0; + initial_data[i] = section_id; + i += 1; + leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], 5); + i += 5; + leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], 0); + i += 5; + initial_data[i] = spec.custom_id; + i += 1; + leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], @intCast(u32, initial_size - @sizeOf(@TypeOf(initial_data)))); + i += 5; + initial_data[i] = 0; + + try file.pwriteAll(&initial_data, offset); + + return VecSection{ + .section = .{ + .offset = offset, + .size = initial_size, + }, + .contents_size = 5, + }; + } + + fn deinit(self: *VecSection, allocator: *Allocator) void { + self.entries.deinit(allocator); + self.dead_list.deinit(allocator); + } + + /// Write a new entry into the file, returning the index used. + fn addEntry(self: *VecSection, file: fs.File, allocator: *Allocator, data: []const u8) !u32 { + // First look for a dead entry we can reuse + for (self.dead_list.items) |dead_idx, i| { + const dead_entry = &self.entries.items[dead_idx]; + if (dead_entry.size == data.len) { + // Found a dead entry of the right length, overwrite it + try file.pwriteAll(data, self.section.offset + Section.header_size + dead_entry.offset); + _ = self.dead_list.swapRemove(i); + return dead_idx; + } + } + + // TODO: We can be more efficient if we special-case one or + // more consecutive dead entries at the end of the vector. + + // We failed to find a dead entry to reuse, so write the new + // entry to the end of the section. + try self.section.resize(file, self.contents_size, self.contents_size + @intCast(u32, data.len)); + try file.pwriteAll(data, self.section.offset + Section.header_size + self.contents_size); + try self.entries.append(allocator, .{ + .offset = self.contents_size, + .size = @intCast(u32, data.len), + }); + self.contents_size += @intCast(u32, data.len); + // Make sure the dead list always has enough space to store all free'd + // entries. This makes it so that delEntry() cannot fail. + // TODO: figure out a better way that doesn't waste as much memory + try self.dead_list.ensureCapacity(allocator, self.entries.items.len); + + // Update the size in the section header and the item count of + // the contents vector. + var size_and_count: [10]u8 = undefined; + leb.writeUnsignedFixed(5, size_and_count[0..5], self.contents_size); + leb.writeUnsignedFixed(5, size_and_count[5..], @intCast(u32, self.entries.items.len)); + try file.pwriteAll(&size_and_count, self.section.offset + 1); + + return @intCast(u32, self.entries.items.len - 1); + } + + /// Mark the type referenced by the given index as dead. + fn delEntry(self: *VecSection, index: u32) void { + self.dead_list.appendAssumeCapacity(index); + } +}; + +const Types = struct { + typesec: VecSection, + + fn init(file: fs.File, offset: u64, initial_size: u64) !Types { + return Types{ .typesec = try VecSection.init(spec.types_id, file, offset, initial_size) }; + } + + fn deinit(self: *Types) void { + const wasm = @fieldParentPtr(Wasm, "types", self); + self.typesec.deinit(wasm.base.allocator); + } + + fn new(self: *Types, data: []const u8) !u32 { + const wasm = @fieldParentPtr(Wasm, "types", self); + return self.typesec.addEntry(wasm.base.file.?, wasm.base.allocator, data); + } + + fn free(self: *Types, typeidx: u32) void { + self.typesec.delEntry(typeidx); + } +}; + +const Funcs = struct { + /// This section needs special handling to keep the indexes matching with + /// the codesec, so we cant just use a VecSection. + funcsec: Section, + /// Number of functions listed in the funcsec. Must be kept in sync with + /// codesec.entries.items.len. + funcs_count: u32, + codesec: VecSection, + + fn init(file: fs.File, funcs_offset: u64, funcs_size: u64, code_offset: u64, code_size: u64) !Funcs { + return Funcs{ + .funcsec = (try VecSection.init(spec.funcs_id, file, funcs_offset, funcs_size)).section, + .funcs_count = 0, + .codesec = try VecSection.init(spec.code_id, file, code_offset, code_size), + }; + } + + fn deinit(self: *Funcs) void { + const wasm = @fieldParentPtr(Wasm, "funcs", self); + self.codesec.deinit(wasm.base.allocator); + } + + /// Add a new function to the binary, first finding space for and writing + /// the code then writing the typeidx to the corresponding index in the + /// funcsec. Returns the function index used. + fn new(self: *Funcs, typeidx: u32, code: []const u8) !u32 { + const wasm = @fieldParentPtr(Wasm, "funcs", self); + const file = wasm.base.file.?; + const allocator = wasm.base.allocator; + + assert(self.funcs_count == self.codesec.entries.items.len); + + // TODO: consider nop-padding the code if there is a close but not perfect fit + const funcidx = try self.codesec.addEntry(file, allocator, code); + + if (self.funcs_count < self.codesec.entries.items.len) { + // u32 vector length + funcs_count u32s in the vector + const current = 5 + self.funcs_count * 5; + try self.funcsec.resize(file, current, current + 5); + self.funcs_count += 1; + + // Update the size in the section header and the item count of + // the contents vector. + var size_and_count: [10]u8 = undefined; + leb.writeUnsignedFixed(5, size_and_count[0..5], 5 + self.funcs_count * 5); + leb.writeUnsignedFixed(5, size_and_count[5..], self.funcs_count); + try file.pwriteAll(&size_and_count, self.funcsec.offset + 1); + } + assert(self.funcs_count == self.codesec.entries.items.len); + + var typeidx_leb: [5]u8 = undefined; + leb.writeUnsignedFixed(5, &typeidx_leb, typeidx); + try file.pwriteAll(&typeidx_leb, self.funcsec.offset + Section.header_size + 5 + funcidx * 5); + + return funcidx; + } + + fn free(self: *Funcs, funcidx: u32) void { + self.codesec.delEntry(funcidx); + } +}; + +/// Exports are tricky. We can't leave dead entries in the binary as they +/// would obviously be visible from the execution environment. The simplest +/// way to work around this is to re-emit the export section whenever +/// something changes. This also makes it easier to ensure exported function +/// and global indexes are updated as they change. +const Exports = struct { + exportsec: Section, + /// Size in bytes of the contents of the section. Does not include + /// the "header" containing the section id and this value. + contents_size: u32, + + fn init(file: fs.File, offset: u64, initial_size: u64) !Exports { + return Exports{ + .exportsec = (try VecSection.init(spec.exports_id, file, offset, initial_size)).section, + .contents_size = 5, + }; + } + + fn writeAll(self: *Exports, module: *Module) !void { + const wasm = @fieldParentPtr(Wasm, "exports", self); + const file = wasm.base.file.?; + var buf: [5]u8 = undefined; + + // First ensure the section is the right size + var export_count: u32 = 0; + var new_contents_size: u32 = 5; + for (module.decl_exports.entries.items) |entry| { + for (entry.value) |e| { + export_count += 1; + new_contents_size += calcSize(e); + } + } + if (new_contents_size != self.contents_size) { + try self.exportsec.resize(file, self.contents_size, new_contents_size); + leb.writeUnsignedFixed(5, &buf, new_contents_size); + try file.pwriteAll(&buf, self.exportsec.offset + 1); + } + + try file.seekTo(self.exportsec.offset + Section.header_size); + const writer = file.writer(); + + // Length of the exports vec + leb.writeUnsignedFixed(5, &buf, export_count); + try writer.writeAll(&buf); + + for (module.decl_exports.entries.items) |entry| + for (entry.value) |e| try writeExport(writer, e); + } + + /// Return the total number of bytes an export will take. + /// TODO: fixed-width LEB128 is currently used for simplicity, but should + /// be replaced with proper variable-length LEB128 as it is inefficient. + fn calcSize(e: *Module.Export) u32 { + // LEB128 name length + name bytes + export type + LEB128 index + return 5 + @intCast(u32, e.options.name.len) + 1 + 5; + } + + /// Write the data for a single export to the given file at a given offset. + /// TODO: fixed-width LEB128 is currently used for simplicity, but should + /// be replaced with proper variable-length LEB128 as it is inefficient. + fn writeExport(writer: anytype, e: *Module.Export) !void { + var buf: [5]u8 = undefined; + + // Export name length + name + leb.writeUnsignedFixed(5, &buf, @intCast(u32, e.options.name.len)); + try writer.writeAll(&buf); + try writer.writeAll(e.options.name); + + switch (e.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => { + // Type of the export + try writer.writeByte(0x00); + // Exported function index + leb.writeUnsignedFixed(5, &buf, e.exported_decl.fn_link.wasm.?.funcidx); + try writer.writeAll(&buf); + }, + else => return error.TODOImplementNonFnDeclsForWasm, + } + } +}; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index d63ea3a757..52e6a3b651 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -150,6 +150,7 @@ const usage_build_generic = \\ -ofmt=[mode] Override target object format \\ elf Executable and Linking Format \\ c Compile to C source code + \\ wasm WebAssembly \\ coff (planned) Common Object File Format (Windows) \\ pe (planned) Portable Executable (Windows) \\ macho (planned) macOS relocatables -- cgit v1.2.3 From 60fb50ee5a4a06687bf2f7b8774cc46f73a5b07e Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 16 Aug 2020 02:21:20 +0200 Subject: stage2/wasm: write exports on flush, cleanup Exports now have a dirty flag and are rewritten on flush if this flag has been set. A couple other minor changes have been made based on Andrew's review. --- src-self-hosted/Module.zig | 2 +- src-self-hosted/codegen/wasm.zig | 2 +- src-self-hosted/link.zig | 16 +++++------ src-self-hosted/link/MachO.zig | 2 +- src-self-hosted/link/Wasm.zig | 57 ++++++++++++++++++++++------------------ 5 files changed, 43 insertions(+), 36 deletions(-) (limited to 'src-self-hosted/Module.zig') diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 5e22058acf..6e33101e76 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -974,7 +974,7 @@ pub fn update(self: *Module) !void { } // This is needed before reading the error flags. - try self.bin_file.flush(); + try self.bin_file.flush(self); self.link_error_flags = self.bin_file.errorFlags(); diff --git a/src-self-hosted/codegen/wasm.zig b/src-self-hosted/codegen/wasm.zig index 8e794ff934..57eb002e82 100644 --- a/src-self-hosted/codegen/wasm.zig +++ b/src-self-hosted/codegen/wasm.zig @@ -52,7 +52,7 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { const writer = buf.writer(); // Reserve space to write the size after generating the code - try writer.writeAll(&([1]u8{undefined} ** 5)); + try buf.resize(5); // Write the size of the locals vec // TODO: implement locals diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 6a51138785..6ed76ce561 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -168,16 +168,15 @@ pub const File = struct { } } - /// Commit pending changes and write headers. - pub fn flush(base: *File) !void { + pub fn flush(base: *File, module: *Module) !void { const tracy = trace(@src()); defer tracy.end(); try switch (base.tag) { - .elf => @fieldParentPtr(Elf, "base", base).flush(), - .macho => @fieldParentPtr(MachO, "base", base).flush(), - .c => @fieldParentPtr(C, "base", base).flush(), - .wasm => @fieldParentPtr(Wasm, "base", base).flush(), + .elf => @fieldParentPtr(Elf, "base", base).flush(module), + .macho => @fieldParentPtr(MachO, "base", base).flush(module), + .c => @fieldParentPtr(C, "base", base).flush(module), + .wasm => @fieldParentPtr(Wasm, "base", base).flush(module), }; } @@ -285,7 +284,7 @@ pub const File = struct { }; } - pub fn flush(self: *File.C) !void { + pub fn flush(self: *File.C, module: *Module) !void { const writer = self.base.file.?.writer(); try writer.writeAll(@embedFile("cbe.h")); var includes = false; @@ -1038,7 +1037,8 @@ pub const File = struct { pub const abbrev_pad1 = 5; pub const abbrev_parameter = 6; - pub fn flush(self: *Elf) !void { + /// Commit pending changes and write headers. + pub fn flush(self: *Elf, module: *Module) !void { const target_endian = self.base.options.target.cpu.arch.endian(); const foreign_endian = target_endian != std.Target.current.cpu.arch.endian(); const ptr_width_bytes: u8 = self.ptrWidthBytes(); diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index 1b6c395ea0..49e365d203 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -73,7 +73,7 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach } } -pub fn flush(self: *MachO) !void {} +pub fn flush(self: *MachO, module: *Module) !void {} pub fn deinit(self: *MachO) void {} diff --git a/src-self-hosted/link/Wasm.zig b/src-self-hosted/link/Wasm.zig index a62cff15ee..cf1a8c21c2 100644 --- a/src-self-hosted/link/Wasm.zig +++ b/src-self-hosted/link/Wasm.zig @@ -59,34 +59,37 @@ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, option try file.writeAll(&(spec.magic ++ spec.version)); - wasm.base = .{ - .tag = .wasm, - .options = options, - .file = file, - .allocator = allocator, - }; - // TODO: this should vary depending on the section and be less arbitrary const size = 1024; const offset = @sizeOf(@TypeOf(spec.magic ++ spec.version)); - wasm.types = try Types.init(file, offset, size); - wasm.funcs = try Funcs.init(file, offset + size, size, offset + 3 * size, size); - wasm.exports = try Exports.init(file, offset + 2 * size, size); - try file.setEndPos(offset + 4 * size); - - wasm.sections = [_]*Section{ - &wasm.types.typesec.section, - &wasm.funcs.funcsec, - &wasm.exports.exportsec, - &wasm.funcs.codesec.section, + wasm.* = .{ + .base = .{ + .tag = .wasm, + .options = options, + .file = file, + .allocator = allocator, + }, + + .types = try Types.init(file, offset, size), + .funcs = try Funcs.init(file, offset + size, size, offset + 3 * size, size), + .exports = try Exports.init(file, offset + 2 * size, size), + + // These must be ordered as they will appear in the output file + .sections = [_]*Section{ + &wasm.types.typesec.section, + &wasm.funcs.funcsec, + &wasm.exports.exportsec, + &wasm.funcs.codesec.section, + }, }; + try file.setEndPos(offset + 4 * size); + return &wasm.base; } pub fn deinit(self: *Wasm) void { - if (self.base.file) |f| f.close(); self.types.deinit(); self.funcs.deinit(); } @@ -112,7 +115,8 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { decl.fn_link.wasm = .{ .typeidx = typeidx, .funcidx = funcidx }; - try self.exports.writeAll(module); + // TODO: we should be more smart and set this only when needed + self.exports.dirty = true; } pub fn updateDeclExports( @@ -121,11 +125,7 @@ pub fn updateDeclExports( decl: *const Module.Decl, exports: []const *Module.Export, ) !void { - // TODO: updateDeclExports() may currently be called before updateDecl, - // presumably due to a bug. For now just rely on the following call - // being made in updateDecl(). - - //try self.exports.writeAll(module); + self.exports.dirty = true; } pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { @@ -138,7 +138,9 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { } } -pub fn flush(self: *Wasm) !void {} +pub fn flush(self: *Wasm, module: *Module) !void { + if (self.exports.dirty) try self.exports.writeAll(module); +} /// This struct describes the location of a named section + custom section /// padding in the output file. This is all the data we need to allow for @@ -373,11 +375,14 @@ const Exports = struct { /// Size in bytes of the contents of the section. Does not include /// the "header" containing the section id and this value. contents_size: u32, + /// If this is true, then exports will be rewritten on flush() + dirty: bool, fn init(file: fs.File, offset: u64, initial_size: u64) !Exports { return Exports{ .exportsec = (try VecSection.init(spec.exports_id, file, offset, initial_size)).section, .contents_size = 5, + .dirty = false, }; } @@ -410,6 +415,8 @@ const Exports = struct { for (module.decl_exports.entries.items) |entry| for (entry.value) |e| try writeExport(writer, e); + + self.dirty = false; } /// Return the total number of bytes an export will take. -- cgit v1.2.3