From 8436134499c623485aeb20374a9928685db4211e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Jan 2021 22:49:20 -0700 Subject: [PATCH 1/4] std.ArrayHashMap: add "AssertDiscard" function variants * Add `swapRemoveAssertDiscard` * Add `orderedRemoveAssertDiscard` * Deprecate `removeAssertDiscard` --- lib/std/array_hash_map.zig | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index b6478d4094..5008b3a4af 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -239,12 +239,23 @@ pub fn ArrayHashMap( return self.unmanaged.orderedRemove(key); } - /// Asserts there is an `Entry` with matching key, deletes it from the hash map, - /// and discards it. + /// TODO: deprecated: call swapRemoveAssertDiscard instead. pub fn removeAssertDiscard(self: *Self, key: K) void { return self.unmanaged.removeAssertDiscard(key); } + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by swapping it with the last element, and discards it. + pub fn swapRemoveAssertDiscard(self: *Self, key: K) void { + return self.unmanaged.swapRemoveAssertDiscard(key); + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by by shifting all elements forward thereby maintaining the current ordering. + pub fn orderedRemoveAssertDiscard(self: *Self, key: K) void { + return self.unmanaged.orderedRemoveAssertDiscard(key); + } + pub fn items(self: Self) []Entry { return self.unmanaged.items(); } @@ -602,12 +613,23 @@ pub fn ArrayHashMapUnmanaged( return self.removeInternal(key, .ordered); } - /// Asserts there is an `Entry` with matching key, deletes it from the hash map, - /// and discards it. + /// TODO deprecated: call swapRemoveAssertDiscard instead. pub fn removeAssertDiscard(self: *Self, key: K) void { + return self.swapRemoveAssertDiscard(key); + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by swapping it with the last element, and discards it. + pub fn swapRemoveAssertDiscard(self: *Self, key: K) void { assert(self.swapRemove(key) != null); } + /// Asserts there is an `Entry` with matching key, deletes it from the hash map + /// by by shifting all elements forward thereby maintaining the current ordering. + pub fn orderedRemoveAssertDiscard(self: *Self, key: K) void { + assert(self.orderedRemove(key) != null); + } + pub fn items(self: Self) []Entry { return self.entries.items; } From 8c9ac4db978c80246b4872c899b1618b1b195ec2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Jan 2021 22:51:01 -0700 Subject: [PATCH 2/4] stage2: implement error notes and regress -femit-zir * Implement error notes - note: other symbol exported here - note: previous else prong is here - note: previous '_' prong is here * Add Compilation.CObject.ErrorMsg. This object properly converts to AllErrors.Message when the time comes. * Add Compilation.CObject.failure_retryable. Properly handles out-of-memory and other transient failures. * Introduce Module.SrcLoc which has not only a byte offset but also references the file which the byte offset applies to. * Scope.Block now contains both a pointer to the "owner" Decl and the "source" Decl. As an example, during inline function call, the "owner" will be the Decl of the caller and the "source" will be the Decl of the callee. * Module.ErrorMsg now sports a `file_scope` field so that notes can refer to source locations in a file other than the parent error message. * Some instances where a `*Scope` was stored, now store a `*Scope.Container`. * Some methods in the `Scope` namespace were moved to the more specific type, since there was only an implementation for one particular tag. - `removeDecl` moved to `Scope.Container` - `destroy` moved to `Scope.File` * Two kinds of Scope deleted: - zir_module - decl * astgen: properly use DeclVal / DeclRef. DeclVal was incorrectly changed to be a reference; this commit fixes it. Fewer ZIR instructions processed as a result. - declval_in_module is renamed to declval - previous declval ZIR instruction is deleted; it was only for .zir files. * Test harness: friendlier diagnostics when an unexpected set of errors is encountered. * zir_sema: fix analyzeInstBlockFlat by properly calling resolvingInst on the last zir instruction in the block. Compile log implementation: * Write to a buffer rather than directly to stderr. * Only keep track of 1 callsite per Decl. * No longer mutate the ZIR Inst struct data. * "Compile log statement found" errors are only emitted when there are no other compile errors. -femit-zir and support for .zir source files is regressed. If we wanted to support this again, outputting .zir would need to be done as yet another backend rather than in the haphazard way it was previously implemented. For parsing .zir, it was implemented previously in a way that was not helpful for debugging. We need tighter integration with the test harness for it to be useful; so clearly a rewrite is needed. Given that a rewrite is needed, and it was getting in the way of progress and organization of the rest of stage2, I regressed the feature. --- src/Compilation.zig | 305 +++--- src/Module.zig | 764 ++++++--------- src/astgen.zig | 77 +- src/codegen.zig | 166 ++-- src/codegen/c.zig | 7 +- src/codegen/llvm.zig | 13 +- src/link/Coff.zig | 6 +- src/link/Elf.zig | 33 +- src/link/MachO.zig | 8 +- src/link/MachO/DebugSymbols.zig | 27 +- src/main.zig | 47 +- src/test.zig | 214 ++-- src/type/Enum.zig | 2 +- src/type/Struct.zig | 2 +- src/type/Union.zig | 2 +- src/zir.zig | 1610 +------------------------------ src/zir_sema.zig | 185 +--- test/stage2/test.zig | 62 +- 18 files changed, 908 insertions(+), 2622 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 225a91e5d2..ad99e40541 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -51,7 +51,7 @@ c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic), /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. /// This data is accessed by multiple threads and is protected by `mutex`. -failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{}, +failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.ErrorMsg) = .{}, keep_source_files_loaded: bool, use_clang: bool, @@ -215,13 +215,29 @@ pub const CObject = struct { }, /// There will be a corresponding ErrorMsg in Compilation.failed_c_objects. failure, + /// A transient failure happened when trying to compile the C Object; it may + /// succeed if we try again. There may be a corresponding ErrorMsg in + /// Compilation.failed_c_objects. If there is not, the failure is out of memory. + failure_retryable, }, + pub const ErrorMsg = struct { + msg: []const u8, + line: u32, + column: u32, + + pub fn destroy(em: *ErrorMsg, gpa: *Allocator) void { + gpa.free(em.msg); + gpa.destroy(em); + em.* = undefined; + } + }; + /// Returns if there was failure. pub fn clearStatus(self: *CObject, gpa: *Allocator) bool { switch (self.status) { .new => return false, - .failure => { + .failure, .failure_retryable => { self.status = .new; return true; }, @@ -240,6 +256,11 @@ pub const CObject = struct { } }; +/// To support incremental compilation, errors are stored in various places +/// so that they can be created and destroyed appropriately. This structure +/// is used to collect all the errors from the various places into one +/// convenient place for API users to consume. It is allocated into 1 heap +/// and freed all at once. pub const AllErrors = struct { arena: std.heap.ArenaAllocator.State, list: []const Message, @@ -251,23 +272,32 @@ pub const AllErrors = struct { column: usize, byte_offset: usize, msg: []const u8, + notes: []Message = &.{}, }, plain: struct { msg: []const u8, }, - pub fn renderToStdErr(self: Message) void { - switch (self) { + pub fn renderToStdErr(msg: Message) void { + return msg.renderToStdErrInner("error"); + } + + fn renderToStdErrInner(msg: Message, kind: []const u8) void { + switch (msg) { .src => |src| { - std.debug.print("{s}:{d}:{d}: error: {s}\n", .{ + std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ src.src_path, src.line + 1, src.column + 1, + kind, src.msg, }); + for (src.notes) |note| { + note.renderToStdErrInner("note"); + } }, .plain => |plain| { - std.debug.print("error: {s}\n", .{plain.msg}); + std.debug.print("{s}: {s}\n", .{ kind, plain.msg }); }, } } @@ -278,20 +308,38 @@ pub const AllErrors = struct { } fn add( + module: *Module, arena: *std.heap.ArenaAllocator, errors: *std.ArrayList(Message), - sub_file_path: []const u8, - source: []const u8, - simple_err_msg: ErrorMsg, + module_err_msg: Module.ErrorMsg, ) !void { - const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset); + const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len); + for (notes) |*note, i| { + const module_note = module_err_msg.notes[i]; + const source = try module_note.src_loc.file_scope.getSource(module); + const loc = std.zig.findLineColumn(source, module_note.src_loc.byte_offset); + const sub_file_path = module_note.src_loc.file_scope.sub_file_path; + note.* = .{ + .src = .{ + .src_path = try arena.allocator.dupe(u8, sub_file_path), + .msg = try arena.allocator.dupe(u8, module_note.msg), + .byte_offset = module_note.src_loc.byte_offset, + .line = loc.line, + .column = loc.column, + }, + }; + } + const source = try module_err_msg.src_loc.file_scope.getSource(module); + const loc = std.zig.findLineColumn(source, module_err_msg.src_loc.byte_offset); + const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path; try errors.append(.{ .src = .{ .src_path = try arena.allocator.dupe(u8, sub_file_path), - .msg = try arena.allocator.dupe(u8, simple_err_msg.msg), - .byte_offset = simple_err_msg.byte_offset, + .msg = try arena.allocator.dupe(u8, module_err_msg.msg), + .byte_offset = module_err_msg.src_loc.byte_offset, .line = loc.line, .column = loc.column, + .notes = notes, }, }); } @@ -849,17 +897,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .ty = struct_ty, }, }; - break :rs &root_scope.base; + break :rs root_scope; } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { - const root_scope = try gpa.create(Module.Scope.ZIRModule); - root_scope.* = .{ - .sub_file_path = root_pkg.root_src_path, - .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, - .status = .never_loaded, - .decls = .{}, - }; - break :rs &root_scope.base; + return error.ZirFilesUnsupported; } else { unreachable; } @@ -1258,32 +1298,23 @@ pub fn update(self: *Compilation) !void { const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_llvm; if (!use_stage1) { if (self.bin_file.options.module) |module| { + module.compile_log_text.shrinkAndFree(module.gpa, 0); module.generation += 1; // TODO Detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded for any reason; // to force a refresh we unload now. - if (module.root_scope.cast(Module.Scope.File)) |zig_file| { - zig_file.unload(module.gpa); - module.failed_root_src_file = null; - module.analyzeContainer(&zig_file.root_container) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - error.OutOfMemory => return error.OutOfMemory, - else => |e| { - module.failed_root_src_file = e; - }, - }; - } else if (module.root_scope.cast(Module.Scope.ZIRModule)) |zir_module| { - zir_module.unload(module.gpa); - module.analyzeRootZIRModule(zir_module) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - else => |e| return e, - }; - } + module.root_scope.unload(module.gpa); + module.failed_root_src_file = null; + module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + error.OutOfMemory => return error.OutOfMemory, + else => |e| { + module.failed_root_src_file = e; + }, + }; // TODO only analyze imports if they are still referenced for (module.import_table.items()) |entry| { @@ -1359,14 +1390,18 @@ pub fn totalErrorCount(self: *Compilation) usize { module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); - for (module.compile_log_decls.items()) |entry| { - total += entry.value.items.len; - } } // The "no entry point found" error only counts if there are no other errors. if (total == 0) { - return @boolToInt(self.link_error_flags.no_entry_point_found); + total += @boolToInt(self.link_error_flags.no_entry_point_found); + } + + // Compile log errors only count if there are no other errors. + if (total == 0) { + if (self.bin_file.options.module) |module| { + total += @boolToInt(module.compile_log_decls.items().len != 0); + } } return total; @@ -1382,32 +1417,32 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { for (self.failed_c_objects.items()) |entry| { const c_object = entry.key; const err_msg = entry.value; - try AllErrors.add(&arena, &errors, c_object.src.src_path, "", err_msg.*); + // TODO these fields will need to be adjusted when we have proper + // C error reporting bubbling up. + try errors.append(.{ + .src = .{ + .src_path = try arena.allocator.dupe(u8, c_object.src.src_path), + .msg = try std.fmt.allocPrint(&arena.allocator, "unable to build C object: {s}", .{ + err_msg.msg, + }), + .byte_offset = 0, + .line = err_msg.line, + .column = err_msg.column, + }, + }); } if (self.bin_file.options.module) |module| { for (module.failed_files.items()) |entry| { - const scope = entry.key; - const err_msg = entry.value; - const source = try scope.getSource(module); - try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_decls.items()) |entry| { - const decl = entry.key; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.emit_h_failed_decls.items()) |entry| { - const decl = entry.key; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_exports.items()) |entry| { - const decl = entry.key.owner_decl; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } if (module.failed_root_src_file) |err| { const file_path = try module.root_pkg.root_src_directory.join(&arena.allocator, &[_][]const u8{ @@ -1418,15 +1453,6 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }); try AllErrors.addPlain(&arena, &errors, msg); } - for (module.compile_log_decls.items()) |entry| { - const decl = entry.key; - const path = decl.scope.subFilePath(); - const source = try decl.scope.getSource(module); - for (entry.value.items) |src_loc| { - const err_msg = ErrorMsg{ .byte_offset = src_loc, .msg = "found compile log statement" }; - try AllErrors.add(&arena, &errors, path, source, err_msg); - } - } } if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) { @@ -1437,6 +1463,28 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }); } + if (self.bin_file.options.module) |module| { + const compile_log_items = module.compile_log_decls.items(); + if (errors.items.len == 0 and compile_log_items.len != 0) { + // First one will be the error; subsequent ones will be notes. + const err_msg = Module.ErrorMsg{ + .src_loc = compile_log_items[0].value, + .msg = "found compile log statement", + .notes = try self.gpa.alloc(Module.ErrorMsg, compile_log_items.len - 1), + }; + defer self.gpa.free(err_msg.notes); + + for (compile_log_items[1..]) |entry, i| { + err_msg.notes[i] = .{ + .src_loc = entry.value, + .msg = "also here", + }; + } + + try AllErrors.add(module, &arena, &errors, err_msg); + } + } + assert(errors.items.len == self.totalErrorCount()); return AllErrors{ @@ -1445,6 +1493,11 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }; } +pub fn getCompileLogOutput(self: *Compilation) []const u8 { + const module = self.bin_file.options.module orelse return &[0]u8{}; + return module.compile_log_text.items; +} + pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void { var progress: std.Progress = .{}; var main_progress_node = try progress.start("", 0); @@ -1517,9 +1570,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor }, else => { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( module.gpa, - decl.src(), + decl.srcLoc(), "unable to codegen: {s}", .{@errorName(err)}, )); @@ -1586,9 +1639,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor const module = self.bin_file.options.module.?; self.bin_file.updateDeclLineNumber(module, decl) catch |err| { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( module.gpa, - decl.src(), + decl.srcLoc(), "unable to update line number: {s}", .{@errorName(err)}, )); @@ -1858,26 +1911,38 @@ fn workerUpdateCObject( comp.updateCObject(c_object, progress_node) catch |err| switch (err) { error.AnalysisFail => return, else => { - { - const lock = comp.mutex.acquire(); - defer lock.release(); - comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1) catch { - fatal("TODO handle this by setting c_object.status = oom failure", .{}); - }; - comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, ErrorMsg.create( - comp.gpa, - 0, - "unable to build C object: {s}", - .{@errorName(err)}, - ) catch { - fatal("TODO handle this by setting c_object.status = oom failure", .{}); - }); - } - c_object.status = .{ .failure = {} }; + comp.reportRetryableCObjectError(c_object, err) catch |oom| switch (oom) { + // Swallowing this error is OK because it's implied to be OOM when + // there is a missing failed_c_objects error message. + error.OutOfMemory => {}, + }; }, }; } +fn reportRetryableCObjectError( + comp: *Compilation, + c_object: *CObject, + err: anyerror, +) error{OutOfMemory}!void { + c_object.status = .failure_retryable; + + const c_obj_err_msg = try comp.gpa.create(CObject.ErrorMsg); + errdefer comp.gpa.destroy(c_obj_err_msg); + const msg = try std.fmt.allocPrint(comp.gpa, "unable to build C object: {s}", .{@errorName(err)}); + errdefer comp.gpa.free(msg); + c_obj_err_msg.* = .{ + .msg = msg, + .line = 0, + .column = 0, + }; + { + const lock = comp.mutex.acquire(); + defer lock.release(); + try comp.failed_c_objects.putNoClobber(comp.gpa, c_object, c_obj_err_msg); + } +} + fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return comp.failCObj(c_object, "clang not available: compiler built without LLVM extensions", .{}); @@ -1892,7 +1957,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * // There was previous failure. const lock = comp.mutex.acquire(); defer lock.release(); - comp.failed_c_objects.removeAssertDiscard(c_object); + // If the failure was OOM, there will not be an entry here, so we do + // not assert discard. + _ = comp.failed_c_objects.swapRemove(c_object); } var man = comp.obtainCObjectCacheManifest(); @@ -2343,11 +2410,27 @@ pub fn addCCArgs( fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError { @setCold(true); - const err_msg = try ErrorMsg.create(comp.gpa, 0, "unable to build C object: " ++ format, args); + const err_msg = blk: { + const msg = try std.fmt.allocPrint(comp.gpa, format, args); + errdefer comp.gpa.free(msg); + const err_msg = try comp.gpa.create(CObject.ErrorMsg); + errdefer comp.gpa.destroy(err_msg); + err_msg.* = .{ + .msg = msg, + .line = 0, + .column = 0, + }; + break :blk err_msg; + }; return comp.failCObjWithOwnedErrorMsg(c_object, err_msg); } -fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError { +fn failCObjWithOwnedErrorMsg( + comp: *Compilation, + c_object: *CObject, + err_msg: *CObject.ErrorMsg, +) InnerError { + @setCold(true); { const lock = comp.mutex.acquire(); defer lock.release(); @@ -2361,36 +2444,6 @@ fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *E return error.AnalysisFail; } -pub const ErrorMsg = struct { - byte_offset: usize, - msg: []const u8, - - pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !*ErrorMsg { - const self = try gpa.create(ErrorMsg); - errdefer gpa.destroy(self); - self.* = try init(gpa, byte_offset, format, args); - return self; - } - - /// Assumes the ErrorMsg struct and msg were both allocated with allocator. - pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { - self.deinit(gpa); - gpa.destroy(self); - } - - pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !ErrorMsg { - return ErrorMsg{ - .byte_offset = byte_offset, - .msg = try std.fmt.allocPrint(gpa, format, args), - }; - } - - pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { - gpa.free(self.msg); - self.* = undefined; - } -}; - pub const FileExt = enum { c, cpp, diff --git a/src/Module.zig b/src/Module.zig index 0bdeab68d0..e612f8f759 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -35,8 +35,7 @@ zig_cache_artifact_directory: Compilation.Directory, /// Pointer to externally managed resource. `null` if there is no zig file being compiled. root_pkg: *Package, /// Module owns this resource. -/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`. -root_scope: *Scope, +root_scope: *Scope.File, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. @@ -57,19 +56,19 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator. /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. -failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, +failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, /// When emit_h is non-null, each Decl gets one more compile error slot for /// emit-h failing for that Decl. This table is also how we tell if a Decl has /// failed emit-h or succeeded. -emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, -/// A Decl can have multiple compileLogs, but only one error, so we map a Decl to a the src locs of all the compileLogs -compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, ArrayListUnmanaged(usize)) = .{}, +emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, +/// Keep track of one `@compileLog` callsite per owner Decl. +compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. -failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, +failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator. -failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *Compilation.ErrorMsg) = .{}, +failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{}, next_anon_name_index: usize = 0, @@ -103,6 +102,8 @@ stage1_flags: packed struct { emit_h: ?Compilation.EmitLoc, +compile_log_text: std.ArrayListUnmanaged(u8) = .{}, + pub const Export = struct { options: std.builtin.ExportOptions, /// Byte offset into the file that contains the export directive. @@ -138,9 +139,9 @@ pub const Decl = struct { /// mapping them to an address in the output file. /// Memory owned by this decl, using Module's allocator. name: [*:0]const u8, - /// The direct parent container of the Decl. This is either a `Scope.Container` or `Scope.ZIRModule`. + /// The direct parent container of the Decl. /// Reference to externally owned memory. - scope: *Scope, + container: *Scope.Container, /// The AST Node decl index or ZIR Inst index that contains this declaration. /// Must be recomputed when the corresponding source file is modified. src_index: usize, @@ -235,31 +236,21 @@ pub const Decl = struct { } } + pub fn srcLoc(self: Decl) SrcLoc { + return .{ + .byte_offset = self.src(), + .file_scope = self.getFileScope(), + }; + } + pub fn src(self: Decl) usize { - switch (self.scope.tag) { - .container => { - const container = @fieldParentPtr(Scope.Container, "base", self.scope); - const tree = container.file_scope.contents.tree; - // TODO Container should have its own decls() - const decl_node = tree.root_node.decls()[self.src_index]; - return tree.token_locs[decl_node.firstToken()].start; - }, - .zir_module => { - const zir_module = @fieldParentPtr(Scope.ZIRModule, "base", self.scope); - const module = zir_module.contents.module; - const src_decl = module.decls[self.src_index]; - return src_decl.inst.src; - }, - .file, .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - } + const tree = self.container.file_scope.contents.tree; + const decl_node = tree.root_node.decls()[self.src_index]; + return tree.token_locs[decl_node.firstToken()].start; } pub fn fullyQualifiedNameHash(self: Decl) Scope.NameHash { - return self.scope.fullyQualifiedNameHash(mem.spanZ(self.name)); + return self.container.fullyQualifiedNameHash(mem.spanZ(self.name)); } pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { @@ -293,9 +284,8 @@ pub const Decl = struct { } } - /// Asserts that the `Decl` is part of AST and not ZIRModule. - pub fn getFileScope(self: *Decl) *Scope.File { - return self.scope.cast(Scope.Container).?.file_scope; + pub fn getFileScope(self: Decl) *Scope.File { + return self.container.file_scope; } pub fn getEmitH(decl: *Decl, module: *Module) *EmitH { @@ -326,7 +316,7 @@ pub const Fn = struct { /// Contains un-analyzed ZIR instructions generated from Zig source AST. /// Even after we finish analysis, the ZIR is kept in memory, so that /// comptime and inline function calls can happen. - zir: zir.Module.Body, + zir: zir.Body, /// undefined unless analysis state is `success`. body: Body, state: Analysis, @@ -373,47 +363,49 @@ pub const Scope = struct { return @fieldParentPtr(T, "base", base); } - /// Asserts the scope has a parent which is a DeclAnalysis and - /// returns the arena Allocator. + /// Returns the arena Allocator associated with the Decl of the Scope. pub fn arena(self: *Scope) *Allocator { switch (self.tag) { .block => return self.cast(Block).?.arena, - .decl => return &self.cast(DeclAnalysis).?.arena.allocator, .gen_zir => return self.cast(GenZIR).?.arena, .local_val => return self.cast(LocalVal).?.gen_zir.arena, .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena, - .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, .file => unreachable, .container => unreachable, } } - /// If the scope has a parent which is a `DeclAnalysis`, - /// returns the `Decl`, otherwise returns `null`. - pub fn decl(self: *Scope) ?*Decl { + pub fn ownerDecl(self: *Scope) ?*Decl { return switch (self.tag) { - .block => self.cast(Block).?.decl, + .block => self.cast(Block).?.owner_decl, .gen_zir => self.cast(GenZIR).?.decl, .local_val => self.cast(LocalVal).?.gen_zir.decl, .local_ptr => self.cast(LocalPtr).?.gen_zir.decl, - .decl => self.cast(DeclAnalysis).?.decl, - .zir_module => null, .file => null, .container => null, }; } - /// Asserts the scope has a parent which is a ZIRModule or Container and - /// returns it. - pub fn namespace(self: *Scope) *Scope { + pub fn srcDecl(self: *Scope) ?*Decl { + return switch (self.tag) { + .block => self.cast(Block).?.src_decl, + .gen_zir => self.cast(GenZIR).?.decl, + .local_val => self.cast(LocalVal).?.gen_zir.decl, + .local_ptr => self.cast(LocalPtr).?.gen_zir.decl, + .file => null, + .container => null, + }; + } + + /// Asserts the scope has a parent which is a Container and returns it. + pub fn namespace(self: *Scope) *Container { switch (self.tag) { - .block => return self.cast(Block).?.decl.scope, - .gen_zir => return self.cast(GenZIR).?.decl.scope, - .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope, - .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope, - .decl => return self.cast(DeclAnalysis).?.decl.scope, - .file => return &self.cast(File).?.root_container.base, - .zir_module, .container => return self, + .block => return self.cast(Block).?.owner_decl.container, + .gen_zir => return self.cast(GenZIR).?.decl.container, + .local_val => return self.cast(LocalVal).?.gen_zir.decl.container, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.container, + .file => return &self.cast(File).?.root_container, + .container => return self.cast(Container).?, } } @@ -426,9 +418,7 @@ pub const Scope = struct { .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, - .decl => unreachable, .file => unreachable, - .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), .container => return self.cast(Container).?.fullyQualifiedNameHash(name), } } @@ -437,12 +427,10 @@ pub const Scope = struct { pub fn tree(self: *Scope) *ast.Tree { switch (self.tag) { .file => return self.cast(File).?.contents.tree, - .zir_module => unreachable, - .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree, - .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree, - .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree, - .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, - .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, + .block => return self.cast(Block).?.src_decl.container.file_scope.contents.tree, + .gen_zir => return self.cast(GenZIR).?.decl.container.file_scope.contents.tree, + .local_val => return self.cast(LocalVal).?.gen_zir.decl.container.file_scope.contents.tree, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.container.file_scope.contents.tree, .container => return self.cast(Container).?.file_scope.contents.tree, } } @@ -454,38 +442,21 @@ pub const Scope = struct { .gen_zir => self.cast(GenZIR).?, .local_val => return self.cast(LocalVal).?.gen_zir, .local_ptr => return self.cast(LocalPtr).?.gen_zir, - .decl => unreachable, - .zir_module => unreachable, .file => unreachable, .container => unreachable, }; } - /// Asserts the scope has a parent which is a ZIRModule, Container or File and + /// Asserts the scope has a parent which is a Container or File and /// returns the sub_file_path field. pub fn subFilePath(base: *Scope) []const u8 { switch (base.tag) { .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path, .file => return @fieldParentPtr(File, "base", base).sub_file_path, - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, .block => unreachable, .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, - .decl => unreachable, - } - } - - pub fn unload(base: *Scope, gpa: *Allocator) void { - switch (base.tag) { - .file => return @fieldParentPtr(File, "base", base).unload(gpa), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(gpa), - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - .container => unreachable, } } @@ -493,67 +464,28 @@ pub const Scope = struct { switch (base.tag) { .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module), .file => return @fieldParentPtr(File, "base", base).getSource(module), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, .block => unreachable, - .decl => unreachable, } } + /// When called from inside a Block Scope, chases the src_decl, not the owner_decl. pub fn getFileScope(base: *Scope) *Scope.File { var cur = base; while (true) { cur = switch (cur.tag) { .container => return @fieldParentPtr(Container, "base", cur).file_scope, .file => return @fieldParentPtr(File, "base", cur), - .zir_module => unreachable, // TODO are zir modules allowed to import packages? .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent, .local_val => @fieldParentPtr(LocalVal, "base", cur).parent, .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent, - .block => @fieldParentPtr(Block, "base", cur).decl.scope, - .decl => @fieldParentPtr(DeclAnalysis, "base", cur).decl.scope, + .block => return @fieldParentPtr(Block, "base", cur).src_decl.container.file_scope, }; } } - /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. - pub fn removeDecl(base: *Scope, child: *Decl) void { - switch (base.tag) { - .container => return @fieldParentPtr(Container, "base", base).removeDecl(child), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), - .file => unreachable, - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - } - } - - /// Asserts the scope is a File or ZIRModule and deinitializes it, then deallocates it. - pub fn destroy(base: *Scope, gpa: *Allocator) void { - switch (base.tag) { - .file => { - const scope_file = @fieldParentPtr(File, "base", base); - scope_file.deinit(gpa); - gpa.destroy(scope_file); - }, - .zir_module => { - const scope_zir_module = @fieldParentPtr(ZIRModule, "base", base); - scope_zir_module.deinit(gpa); - gpa.destroy(scope_zir_module); - }, - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - .container => unreachable, - } - } - fn name_hash_hash(x: NameHash) u32 { return @truncate(u32, @bitCast(u128, x)); } @@ -563,14 +495,11 @@ pub const Scope = struct { } pub const Tag = enum { - /// .zir source code. - zir_module, /// .zig source code. file, /// struct, enum or union, every .file contains one of these. container, block, - decl, gen_zir, local_val, local_ptr, @@ -657,6 +586,11 @@ pub const Scope = struct { self.* = undefined; } + pub fn destroy(self: *File, gpa: *Allocator) void { + self.deinit(gpa); + gpa.destroy(self); + } + pub fn dumpSrc(self: *File, src: usize) void { const loc = std.zig.findLineColumn(self.source.bytes, src); std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); @@ -681,109 +615,6 @@ pub const Scope = struct { } }; - pub const ZIRModule = struct { - pub const base_tag: Tag = .zir_module; - base: Scope = Scope{ .tag = base_tag }, - /// Relative to the owning package's root_src_dir. - /// Reference to external memory, not owned by ZIRModule. - sub_file_path: []const u8, - source: union(enum) { - unloaded: void, - bytes: [:0]const u8, - }, - contents: union { - not_available: void, - module: *zir.Module, - }, - status: enum { - never_loaded, - unloaded_success, - unloaded_parse_failure, - unloaded_sema_failure, - - loaded_sema_failure, - loaded_success, - }, - - /// Even though .zir files only have 1 module, this set is still needed - /// because of anonymous Decls, which can exist in the global set, but - /// not this one. - decls: ArrayListUnmanaged(*Decl), - - pub fn unload(self: *ZIRModule, gpa: *Allocator) void { - switch (self.status) { - .never_loaded, - .unloaded_parse_failure, - .unloaded_sema_failure, - .unloaded_success, - => {}, - - .loaded_success => { - self.contents.module.deinit(gpa); - gpa.destroy(self.contents.module); - self.contents = .{ .not_available = {} }; - self.status = .unloaded_success; - }, - .loaded_sema_failure => { - self.contents.module.deinit(gpa); - gpa.destroy(self.contents.module); - self.contents = .{ .not_available = {} }; - self.status = .unloaded_sema_failure; - }, - } - switch (self.source) { - .bytes => |bytes| { - gpa.free(bytes); - self.source = .{ .unloaded = {} }; - }, - .unloaded => {}, - } - } - - pub fn deinit(self: *ZIRModule, gpa: *Allocator) void { - self.decls.deinit(gpa); - self.unload(gpa); - self.* = undefined; - } - - pub fn removeDecl(self: *ZIRModule, child: *Decl) void { - for (self.decls.items) |item, i| { - if (item == child) { - _ = self.decls.swapRemove(i); - return; - } - } - } - - pub fn dumpSrc(self: *ZIRModule, src: usize) void { - const loc = std.zig.findLineColumn(self.source.bytes, src); - std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); - } - - pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { - switch (self.source) { - .unloaded => { - const source = try module.root_pkg.root_src_directory.handle.readFileAllocOptions( - module.gpa, - self.sub_file_path, - std.math.maxInt(u32), - null, - 1, - 0, - ); - self.source = .{ .bytes = source }; - return source; - }, - .bytes => |bytes| return bytes, - } - } - - pub fn fullyQualifiedNameHash(self: *ZIRModule, name: []const u8) NameHash { - // ZIR modules only have 1 file with all decls global in the same namespace. - return std.zig.hashSrc(name); - } - }; - /// This is a temporary structure, references to it are valid only /// during semantic analysis of the block. pub const Block = struct { @@ -794,9 +625,14 @@ pub const Scope = struct { /// Maps ZIR to TZIR. Shared to sub-blocks. inst_table: *InstTable, func: ?*Fn, - decl: *Decl, + /// When analyzing an inline function call, owner_decl is the Decl of the caller + /// and src_decl is the Decl of the callee. + /// This Decl owns the arena memory of this Block. + owner_decl: *Decl, + /// This Decl is the Decl according to the Zig source code corresponding to this Block. + src_decl: *Decl, instructions: ArrayListUnmanaged(*Inst), - /// Points to the arena allocator of DeclAnalysis + /// Points to the arena allocator of the Decl. arena: *Allocator, label: ?Label = null, inlining: ?*Inlining, @@ -845,21 +681,12 @@ pub const Scope = struct { } }; - /// This is a temporary structure, references to it are valid only - /// during semantic analysis of the decl. - pub const DeclAnalysis = struct { - pub const base_tag: Tag = .decl; - base: Scope = Scope{ .tag = base_tag }, - decl: *Decl, - arena: std.heap.ArenaAllocator, - }; - /// This is a temporary structure, references to it are valid only /// during semantic analysis of the decl. pub const GenZIR = struct { pub const base_tag: Tag = .gen_zir; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `GenZIR`, `ZIRModule`, `File` + /// Parents can be: `GenZIR`, `File` parent: *Scope, decl: *Decl, arena: *Allocator, @@ -905,11 +732,73 @@ pub const Scope = struct { }; }; +/// This struct holds data necessary to construct API-facing `AllErrors.Message`. +/// Its memory is managed with the general purpose allocator so that they +/// can be created and destroyed in response to incremental updates. +/// In some cases, the Scope.File could have been inferred from where the ErrorMsg +/// is stored. For example, if it is stored in Module.failed_decls, then the Scope.File +/// would be determined by the Decl Scope. However, the data structure contains the field +/// anyway so that `ErrorMsg` can be reused for error notes, which may be in a different +/// file than the parent error message. It also simplifies processing of error messages. +pub const ErrorMsg = struct { + src_loc: SrcLoc, + msg: []const u8, + notes: []ErrorMsg = &.{}, + + pub fn create( + gpa: *Allocator, + src_loc: SrcLoc, + comptime format: []const u8, + args: anytype, + ) !*ErrorMsg { + const self = try gpa.create(ErrorMsg); + errdefer gpa.destroy(self); + self.* = try init(gpa, src_loc, format, args); + return self; + } + + /// Assumes the ErrorMsg struct and msg were both allocated with `gpa`, + /// as well as all notes. + pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { + self.deinit(gpa); + gpa.destroy(self); + } + + pub fn init( + gpa: *Allocator, + src_loc: SrcLoc, + comptime format: []const u8, + args: anytype, + ) !ErrorMsg { + return ErrorMsg{ + .src_loc = src_loc, + .msg = try std.fmt.allocPrint(gpa, format, args), + }; + } + + pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { + for (self.notes) |*note| { + note.deinit(gpa); + } + gpa.free(self.notes); + gpa.free(self.msg); + self.* = undefined; + } +}; + +/// Canonical reference to a position within a source file. +pub const SrcLoc = struct { + file_scope: *Scope.File, + byte_offset: usize, +}; + pub const InnerError = error{ OutOfMemory, AnalysisFail }; pub fn deinit(self: *Module) void { const gpa = self.gpa; + self.compile_log_text.deinit(gpa); + self.zig_cache_artifact_directory.handle.close(); self.deletion_set.deinit(gpa); @@ -939,9 +828,6 @@ pub fn deinit(self: *Module) void { } self.failed_exports.deinit(gpa); - for (self.compile_log_decls.items()) |*entry| { - entry.value.deinit(gpa); - } self.compile_log_decls.deinit(gpa); for (self.decl_exports.items()) |entry| { @@ -965,7 +851,7 @@ pub fn deinit(self: *Module) void { self.global_error_set.deinit(gpa); for (self.import_table.items()) |entry| { - entry.value.base.destroy(gpa); + entry.value.destroy(gpa); } self.import_table.deinit(gpa); } @@ -978,7 +864,7 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void { gpa.free(export_list); } -pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { +pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void { const tracy = trace(@src()); defer tracy.end(); @@ -999,7 +885,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // The exports this Decl performs will be re-discovered, so we remove them here // prior to re-analysis. - self.deleteDeclExports(decl); + mod.deleteDeclExports(decl); // Dependencies will be re-discovered, so we remove them here prior to re-analysis. for (decl.dependencies.items()) |entry| { const dep = entry.key; @@ -1008,7 +894,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // We don't perform a deletion here, because this Decl or another one // may end up referencing it before the update is complete. dep.deletion_flag = true; - try self.deletion_set.append(self.gpa, dep); + try mod.deletion_set.append(mod.gpa, dep); } } decl.dependencies.clearRetainingCapacity(); @@ -1019,24 +905,21 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .unreferenced => false, }; - const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| - try zir_sema.analyzeZirDecl(self, decl, zir_module.contents.module.decls[decl.src_index]) - else - self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return error.AnalysisFail, - else => { - try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try Compilation.ErrorMsg.create( - self.gpa, - decl.src(), - "unable to analyze: {s}", - .{@errorName(err)}, - )); - decl.analysis = .sema_failure_retryable; - return error.AnalysisFail; - }, - }; + const type_changed = mod.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return error.AnalysisFail, + else => { + decl.analysis = .sema_failure_retryable; + try mod.failed_decls.ensureCapacity(mod.gpa, mod.failed_decls.items().len + 1); + mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + mod.gpa, + decl.srcLoc(), + "unable to analyze: {s}", + .{@errorName(err)}, + )); + return error.AnalysisFail; + }, + }; if (subsequent_analysis) { // We may need to chase the dependants and re-analyze them. @@ -1055,8 +938,8 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .codegen_failure, .codegen_failure_retryable, .complete, - => if (dep.generation != self.generation) { - try self.markOutdatedDecl(dep); + => if (dep.generation != mod.generation) { + try mod.markOutdatedDecl(dep); }, } } @@ -1068,8 +951,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const tracy = trace(@src()); defer tracy.end(); - const container_scope = decl.scope.cast(Scope.Container).?; - const tree = try self.getAstTree(container_scope.file_scope); + const tree = try self.getAstTree(decl.container.file_scope); const ast_node = tree.root_node.decls()[decl.src_index]; switch (ast_node.tag) { .FnProto => { @@ -1085,7 +967,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var fn_type_scope: Scope.GenZIR = .{ .decl = decl, .arena = &fn_type_scope_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer fn_type_scope.instructions.deinit(self.gpa); @@ -1197,7 +1079,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &decl_arena.allocator, .inlining = null, @@ -1242,12 +1125,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const new_func = try decl_arena.allocator.create(Fn); const fn_payload = try decl_arena.allocator.create(Value.Payload.Function); - const fn_zir: zir.Module.Body = blk: { + const fn_zir: zir.Body = blk: { // We put the ZIR inside the Decl arena. var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &decl_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1400,7 +1283,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &decl_inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &decl_arena.allocator, .inlining = null, @@ -1444,7 +1328,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &gen_scope_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1472,7 +1356,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &var_inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &gen_scope_arena.allocator, .inlining = null, @@ -1503,7 +1388,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var type_scope: Scope.GenZIR = .{ .decl = decl, .arena = &type_scope_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer type_scope.instructions.deinit(self.gpa); @@ -1584,7 +1469,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &analysis_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1602,7 +1487,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &analysis_arena.allocator, .inlining = null, @@ -1632,44 +1518,6 @@ fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void dependee.dependants.putAssumeCapacity(depender, {}); } -fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { - switch (root_scope.status) { - .never_loaded, .unloaded_success => { - try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); - - const source = try root_scope.getSource(self); - - var keep_zir_module = false; - const zir_module = try self.gpa.create(zir.Module); - defer if (!keep_zir_module) self.gpa.destroy(zir_module); - - zir_module.* = try zir.parse(self.gpa, source); - defer if (!keep_zir_module) zir_module.deinit(self.gpa); - - if (zir_module.error_msg) |src_err_msg| { - self.failed_files.putAssumeCapacityNoClobber( - &root_scope.base, - try Compilation.ErrorMsg.create(self.gpa, src_err_msg.byte_offset, "{s}", .{src_err_msg.msg}), - ); - root_scope.status = .unloaded_parse_failure; - return error.AnalysisFail; - } - - root_scope.status = .loaded_success; - root_scope.contents = .{ .module = zir_module }; - keep_zir_module = true; - - return zir_module; - }, - - .unloaded_parse_failure, - .unloaded_sema_failure, - => return error.AnalysisFail, - - .loaded_success, .loaded_sema_failure => return root_scope.contents.module, - } -} - pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { const tracy = trace(@src()); defer tracy.end(); @@ -1691,10 +1539,13 @@ pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { defer msg.deinit(); try parse_err.render(tree.token_ids, msg.writer()); - const err_msg = try self.gpa.create(Compilation.ErrorMsg); + const err_msg = try self.gpa.create(ErrorMsg); err_msg.* = .{ + .src_loc = .{ + .file_scope = root_scope, + .byte_offset = tree.token_locs[parse_err.loc()].start, + }, .msg = msg.toOwnedSlice(), - .byte_offset = tree.token_locs[parse_err.loc()].start, }; self.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg); @@ -1753,9 +1604,12 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.src_index = decl_i; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{s}'", .{decl.name}); - errdefer err_msg.destroy(self.gpa); - try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); + const msg = try ErrorMsg.create(self.gpa, .{ + .file_scope = container_scope.file_scope, + .byte_offset = tree.token_locs[name_tok].start, + }, "redefinition of '{s}'", .{decl.name}); + errdefer msg.destroy(self.gpa); + try self.failed_decls.putNoClobber(self.gpa, decl, msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); @@ -1795,7 +1649,10 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.src_index = decl_i; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{s}'", .{decl.name}); + const err_msg = try ErrorMsg.create(self.gpa, .{ + .file_scope = container_scope.file_scope, + .byte_offset = name_loc.start, + }, "redefinition of '{s}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { @@ -1840,65 +1697,12 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void } } -pub fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { - // We may be analyzing it for the first time, or this may be - // an incremental update. This code handles both cases. - const src_module = try self.getSrcModule(root_scope); - - try self.comp.work_queue.ensureUnusedCapacity(src_module.decls.len); - try root_scope.decls.ensureCapacity(self.gpa, src_module.decls.len); - - var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.gpa); - defer exports_to_resolve.deinit(); - - // Keep track of the decls that we expect to see in this file so that - // we know which ones have been deleted. - var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa); - defer deleted_decls.deinit(); - try deleted_decls.ensureCapacity(self.decl_table.items().len); - for (self.decl_table.items()) |entry| { - deleted_decls.putAssumeCapacityNoClobber(entry.value, {}); - } - - for (src_module.decls) |src_decl, decl_i| { - const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); - if (self.decl_table.get(name_hash)) |decl| { - deleted_decls.removeAssertDiscard(decl); - if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { - try self.markOutdatedDecl(decl); - decl.contents_hash = src_decl.contents_hash; - } - } else { - const new_decl = try self.createNewDecl( - &root_scope.base, - src_decl.name, - decl_i, - name_hash, - src_decl.contents_hash, - ); - root_scope.decls.appendAssumeCapacity(new_decl); - if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { - try exports_to_resolve.append(src_decl); - } - } - } - for (exports_to_resolve.items) |export_decl| { - _ = try zir_sema.resolveZirDecl(self, &root_scope.base, export_decl); - } - // 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("noticed '{s}' deleted from source\n", .{entry.key.name}); - try self.deleteDecl(entry.key); - } -} - pub fn deleteDecl(self: *Module, decl: *Decl) !void { try self.deletion_set.ensureCapacity(self.gpa, self.deletion_set.items.len + decl.dependencies.items().len); // Remove from the namespace it resides in. In the case of an anonymous Decl it will // not be present in the set, and this does nothing. - decl.scope.removeDecl(decl); + decl.container.removeDecl(decl); log.debug("deleting decl '{s}'\n", .{decl.name}); const name_hash = decl.fullyQualifiedNameHash(); @@ -1929,9 +1733,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { if (self.emit_h_failed_decls.swapRemove(decl)) |entry| { entry.value.destroy(self.gpa); } - if (self.compile_log_decls.swapRemove(decl)) |*entry| { - entry.value.deinit(self.gpa); - } + _ = self.compile_log_decls.swapRemove(decl); self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); @@ -1993,7 +1795,8 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { .parent = null, .inst_table = &inst_table, .func = func, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &arena.allocator, .inlining = null, @@ -2022,9 +1825,7 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { if (self.emit_h_failed_decls.swapRemove(decl)) |entry| { entry.value.destroy(self.gpa); } - if (self.compile_log_decls.swapRemove(decl)) |*entry| { - entry.value.deinit(self.gpa); - } + _ = self.compile_log_decls.swapRemove(decl); decl.analysis = .outdated; } @@ -2046,7 +1847,7 @@ fn allocateNewDecl( new_decl.* = .{ .name = "", - .scope = scope.namespace(), + .container = scope.namespace(), .src_index = src_index, .typed_value = .{ .never_succeeded = {} }, .analysis = .unreferenced, @@ -2129,34 +1930,34 @@ pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { } pub fn analyzeExport( - self: *Module, + mod: *Module, scope: *Scope, src: usize, borrowed_symbol_name: []const u8, exported_decl: *Decl, ) !void { - try self.ensureDeclAnalyzed(exported_decl); + try mod.ensureDeclAnalyzed(exported_decl); const typed_value = exported_decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { .Fn => {}, - else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), + else => return mod.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } - try self.decl_exports.ensureCapacity(self.gpa, self.decl_exports.items().len + 1); - try self.export_owners.ensureCapacity(self.gpa, self.export_owners.items().len + 1); + try mod.decl_exports.ensureCapacity(mod.gpa, mod.decl_exports.items().len + 1); + try mod.export_owners.ensureCapacity(mod.gpa, mod.export_owners.items().len + 1); - const new_export = try self.gpa.create(Export); - errdefer self.gpa.destroy(new_export); + const new_export = try mod.gpa.create(Export); + errdefer mod.gpa.destroy(new_export); - const symbol_name = try self.gpa.dupe(u8, borrowed_symbol_name); - errdefer self.gpa.free(symbol_name); + const symbol_name = try mod.gpa.dupe(u8, borrowed_symbol_name); + errdefer mod.gpa.free(symbol_name); - const owner_decl = scope.decl().?; + const owner_decl = scope.ownerDecl().?; new_export.* = .{ .options = .{ .name = symbol_name }, .src = src, - .link = switch (self.comp.bin_file.tag) { + .link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.Export{} }, .macho => .{ .macho = link.File.MachO.Export{} }, @@ -2169,48 +1970,53 @@ pub fn analyzeExport( }; // Add to export_owners table. - const eo_gop = self.export_owners.getOrPutAssumeCapacity(owner_decl); + const eo_gop = mod.export_owners.getOrPutAssumeCapacity(owner_decl); if (!eo_gop.found_existing) { eo_gop.entry.value = &[0]*Export{}; } - eo_gop.entry.value = try self.gpa.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); + eo_gop.entry.value = try mod.gpa.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); eo_gop.entry.value[eo_gop.entry.value.len - 1] = new_export; - errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); + errdefer eo_gop.entry.value = mod.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); // Add to exported_decl table. - const de_gop = self.decl_exports.getOrPutAssumeCapacity(exported_decl); + const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl); if (!de_gop.found_existing) { de_gop.entry.value = &[0]*Export{}; } - de_gop.entry.value = try self.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); + de_gop.entry.value = try mod.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); de_gop.entry.value[de_gop.entry.value.len - 1] = new_export; - errdefer de_gop.entry.value = self.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); + errdefer de_gop.entry.value = mod.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); - if (self.symbol_exports.get(symbol_name)) |_| { - try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try Compilation.ErrorMsg.create( - self.gpa, + if (mod.symbol_exports.get(symbol_name)) |other_export| { + new_export.status = .failed_retryable; + try mod.failed_exports.ensureCapacity(mod.gpa, mod.failed_exports.items().len + 1); + const msg = try mod.errMsg( + scope, src, "exported symbol collision: {s}", .{symbol_name}, - )); - // TODO: add a note + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote( + &other_export.owner_decl.container.base, + other_export.src, + msg, + "other symbol here", + .{}, + ); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); new_export.status = .failed; return; } - try self.symbol_exports.putNoClobber(self.gpa, symbol_name, new_export); - self.comp.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { + try mod.symbol_exports.putNoClobber(mod.gpa, symbol_name, new_export); + mod.comp.bin_file.updateDeclExports(mod, exported_decl, de_gop.entry.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try Compilation.ErrorMsg.create( - self.gpa, - src, - "unable to export: {s}", - .{@errorName(err)}, - )); new_export.status = .failed_retryable; + try mod.failed_exports.ensureCapacity(mod.gpa, mod.failed_exports.items().len + 1); + const msg = try mod.errMsg(scope, src, "unable to export: {s}", .{@errorName(err)}); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); }, }; } @@ -2476,7 +2282,7 @@ pub fn createAnonymousDecl( typed_value: TypedValue, ) !*Decl { const name_index = self.getNextAnonNameIndex(); - const scope_decl = scope.decl().?; + const scope_decl = scope.ownerDecl().?; const name = try std.fmt.allocPrint(self.gpa, "{s}__anon_{d}", .{ scope_decl.name, name_index }); defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); @@ -2512,7 +2318,7 @@ pub fn createContainerDecl( decl_arena: *std.heap.ArenaAllocator, typed_value: TypedValue, ) !*Decl { - const scope_decl = scope.decl().?; + const scope_decl = scope.ownerDecl().?; const name = try self.getAnonTypeName(scope, base_token); defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); @@ -2558,14 +2364,14 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De } pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { - const scope_decl = scope.decl().?; + const scope_decl = scope.ownerDecl().?; try self.declareDeclDependency(scope_decl, decl); self.ensureDeclAnalyzed(decl) catch |err| { if (scope.cast(Scope.Block)) |block| { if (block.func) |func| { func.state = .dependency_failure; } else { - block.decl.analysis = .dependency_failure; + block.owner_decl.analysis = .dependency_failure; } } else { scope_decl.analysis = .dependency_failure; @@ -3217,10 +3023,51 @@ fn coerceArrayPtrToMany(self: *Module, scope: *Scope, dest_type: Type, inst: *In return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToMany runtime instruction", .{}); } -pub fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - const err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); - return self.failWithOwnedErrorMsg(scope, src, err_msg); +/// We don't return a pointer to the new error note because the pointer +/// becomes invalid when you add another one. +pub fn errNote( + mod: *Module, + scope: *Scope, + src: usize, + parent: *ErrorMsg, + comptime format: []const u8, + args: anytype, +) error{OutOfMemory}!void { + const msg = try std.fmt.allocPrint(mod.gpa, format, args); + errdefer mod.gpa.free(msg); + + parent.notes = try mod.gpa.realloc(parent.notes, parent.notes.len + 1); + parent.notes[parent.notes.len - 1] = .{ + .src_loc = .{ + .file_scope = scope.getFileScope(), + .byte_offset = src, + }, + .msg = msg, + }; +} + +pub fn errMsg( + mod: *Module, + scope: *Scope, + src_byte_offset: usize, + comptime format: []const u8, + args: anytype, +) error{OutOfMemory}!*ErrorMsg { + return ErrorMsg.create(mod.gpa, .{ + .file_scope = scope.getFileScope(), + .byte_offset = src_byte_offset, + }, format, args); +} + +pub fn fail( + mod: *Module, + scope: *Scope, + src_byte_offset: usize, + comptime format: []const u8, + args: anytype, +) InnerError { + const err_msg = try mod.errMsg(scope, src_byte_offset, format, args); + return mod.failWithOwnedErrorMsg(scope, err_msg); } pub fn failTok( @@ -3230,7 +3077,6 @@ pub fn failTok( comptime format: []const u8, args: anytype, ) InnerError { - @setCold(true); const src = scope.tree().token_locs[token_index].start; return self.fail(scope, src, format, args); } @@ -3242,80 +3088,36 @@ pub fn failNode( comptime format: []const u8, args: anytype, ) InnerError { - @setCold(true); const src = scope.tree().token_locs[ast_node.firstToken()].start; return self.fail(scope, src, format, args); } -fn addCompileLog(self: *Module, decl: *Decl, src: usize) error{OutOfMemory}!void { - const entry = try self.compile_log_decls.getOrPutValue(self.gpa, decl, .{}); - try entry.value.append(self.gpa, src); -} - -pub fn failCompileLog( - self: *Module, - scope: *Scope, - src: usize, -) InnerError!void { - switch (scope.tag) { - .decl => { - const decl = scope.cast(Scope.DeclAnalysis).?.decl; - try self.addCompileLog(decl, src); - }, - .block => { - const block = scope.cast(Scope.Block).?; - try self.addCompileLog(block.decl, src); - }, - .gen_zir => { - const gen_zir = scope.cast(Scope.GenZIR).?; - try self.addCompileLog(gen_zir.decl, src); - }, - .local_val => { - const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; - try self.addCompileLog(gen_zir.decl, src); - }, - .local_ptr => { - const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; - try self.addCompileLog(gen_zir.decl, src); - }, - .zir_module, - .file, - .container, - => unreachable, - } -} - -fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Compilation.ErrorMsg) InnerError { +pub fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, err_msg: *ErrorMsg) InnerError { + @setCold(true); { errdefer err_msg.destroy(self.gpa); try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); } switch (scope.tag) { - .decl => { - const decl = scope.cast(Scope.DeclAnalysis).?.decl; - decl.analysis = .sema_failure; - decl.generation = self.generation; - self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); - }, .block => { const block = scope.cast(Scope.Block).?; if (block.inlining) |inlining| { if (inlining.shared.caller) |func| { func.state = .sema_failure; } else { - block.decl.analysis = .sema_failure; - block.decl.generation = self.generation; + block.owner_decl.analysis = .sema_failure; + block.owner_decl.generation = self.generation; } } else { if (block.func) |func| { func.state = .sema_failure; } else { - block.decl.analysis = .sema_failure; - block.decl.generation = self.generation; + block.owner_decl.analysis = .sema_failure; + block.owner_decl.generation = self.generation; } } - self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(block.owner_decl, err_msg); }, .gen_zir => { const gen_zir = scope.cast(Scope.GenZIR).?; @@ -3335,11 +3137,6 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com gen_zir.decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, - .zir_module => { - const zir_module = scope.cast(Scope.ZIRModule).?; - zir_module.status = .loaded_sema_failure; - self.failed_files.putAssumeCapacityNoClobber(scope, err_msg); - }, .file => unreachable, .container => unreachable, } @@ -3671,7 +3468,8 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, diff --git a/src/astgen.zig b/src/astgen.zig index 53503a0467..4631e46b5d 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -318,7 +318,7 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as // Make a scope to collect generated instructions in the sub-expression. var block_scope: Scope.GenZIR = .{ .parent = parent_scope, - .decl = parent_scope.decl().?, + .decl = parent_scope.ownerDecl().?, .arena = parent_scope.arena(), .instructions = .{}, }; @@ -474,7 +474,7 @@ fn labeledBlockExpr( var block_scope: Scope.GenZIR = .{ .parent = parent_scope, - .decl = parent_scope.decl().?, + .decl = parent_scope.ownerDecl().?, .arena = gen_zir.arena, .instructions = .{}, .break_result_loc = rl, @@ -899,7 +899,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con var gen_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1028,7 +1028,13 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con .ty = Type.initTag(.type), .val = val, }); - return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{})); + if (rl == .ref) { + return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{}); + } else { + return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{ + .decl = decl, + }, .{})); + } } fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst { @@ -1084,7 +1090,7 @@ fn orelseCatchExpr( var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1266,7 +1272,7 @@ fn boolBinOp( var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1412,7 +1418,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn } var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1513,7 +1519,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W var expr_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1649,7 +1655,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) var for_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1843,7 +1849,7 @@ fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1885,7 +1891,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node var item_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1922,8 +1928,15 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node // Check for else/_ prong, those are handled last. if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { if (else_src) |src| { - return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); - // TODO notes "previous else prong is here" + const msg = try mod.errMsg( + scope, + case_src, + "multiple else prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous else prong is here", .{}); + return mod.failWithOwnedErrorMsg(scope, msg); } else_src = case_src; special_case = case; @@ -1932,8 +1945,15 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) { if (underscore_src) |src| { - return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{}); - // TODO notes "previous '_' prong is here" + const msg = try mod.errMsg( + scope, + case_src, + "multiple '_' prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); + return mod.failWithOwnedErrorMsg(scope, msg); } underscore_src = case_src; special_case = case; @@ -1942,9 +1962,16 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node if (else_src) |some_else| { if (underscore_src) |some_underscore| { - return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{}); - // TODO notes "else prong is here" - // TODO notes "'_' prong is here" + const msg = try mod.errMsg( + scope, + switch_src, + "else and '_' prong in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, some_else, msg, "else prong is here", .{}); + try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); + return mod.failWithOwnedErrorMsg(scope, msg); } } @@ -2162,7 +2189,13 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo } if (mod.lookupDeclName(scope, ident_name)) |decl| { - return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{})); + if (rl == .ref) { + return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{}); + } else { + return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{ + .decl = decl, + }, .{})); + } } return mod.failNode(scope, &ident.base, "use of undeclared identifier '{s}'", .{ident_name}); @@ -2927,6 +2960,8 @@ fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul return rlWrap(mod, scope, rl, void_inst); } +/// TODO go over all the callsites and see where we can introduce "by-value" ZIR instructions +/// to save ZIR memory. For example, see DeclVal vs DeclRef. fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerError!*zir.Inst { if (rl == .ref) return ptr; @@ -3032,7 +3067,7 @@ pub fn addZIRInstBlock( scope: *Scope, src: usize, tag: zir.Inst.Tag, - body: zir.Module.Body, + body: zir.Body, ) !*zir.Inst.Block { const gen_zir = scope.getGenZIR(); try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); @@ -3070,7 +3105,7 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ } /// TODO The existence of this function is a workaround for a bug in stage1. -pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop { +pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Body) !*zir.Inst.Loop { const P = std.meta.fieldInfo(zir.Inst.Loop, .positionals).field_type; return addZIRInstSpecial(mod, scope, src, zir.Inst.Loop, P{ .body = body }, .{}); } diff --git a/src/codegen.zig b/src/codegen.zig index 709c91a635..9f2fbaab78 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -9,7 +9,7 @@ const TypedValue = @import("TypedValue.zig"); const link = @import("link.zig"); const Module = @import("Module.zig"); const Compilation = @import("Compilation.zig"); -const ErrorMsg = Compilation.ErrorMsg; +const ErrorMsg = Module.ErrorMsg; const Target = std.Target; const Allocator = mem.Allocator; const trace = @import("tracy.zig").trace; @@ -74,7 +74,7 @@ pub const DebugInfoOutput = union(enum) { pub fn generateSymbol( bin_file: *link.File, - src: usize, + src_loc: Module.SrcLoc, typed_value: TypedValue, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, @@ -87,56 +87,56 @@ pub fn generateSymbol( switch (bin_file.options.target.cpu.arch) { .wasm32 => unreachable, // has its own code path .wasm64 => unreachable, // has its own code path - .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output), - .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output), - .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output), - .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, debug_output), - .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, debug_output), - .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, debug_output), - .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, debug_output), + .arm => return Function(.arm).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .armeb => return Function(.armeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.arc => return Function(.arc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.avr => return Function(.avr).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mips => return Function(.mips).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mips64 => return Function(.mips64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.msp430 => return Function(.msp430).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.r600 => return Function(.r600).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.sparc => return Function(.sparc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.s390x => return Function(.s390x).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.tce => return Function(.tce).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.tcele => return Function(.tcele).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.thumb => return Function(.thumb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.i386 => return Function(.i386).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.xcore => return Function(.xcore).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.le32 => return Function(.le32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.le64 => return Function(.le64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.amdil => return Function(.amdil).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.hsail => return Function(.hsail).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.spir => return Function(.spir).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.spir64 => return Function(.spir64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.shave => return Function(.shave).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.lanai => return Function(.lanai).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.ve => return Function(.ve).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), 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."), } }, @@ -147,7 +147,7 @@ pub fn generateSymbol( try code.ensureCapacity(code.items.len + payload.data.len + 1); code.appendSliceAssumeCapacity(payload.data); const prev_len = code.items.len; - switch (try generateSymbol(bin_file, src, .{ + switch (try generateSymbol(bin_file, src_loc, .{ .ty = typed_value.ty.elemType(), .val = sentinel, }, code, debug_output)) { @@ -165,7 +165,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for more kinds of arrays", .{}, ), @@ -200,7 +200,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for pointer {}", .{typed_value.val}, ), @@ -217,7 +217,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for int type '{}'", .{typed_value.ty}, ), @@ -227,7 +227,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for type '{s}'", .{@tagName(t)}, ), @@ -259,7 +259,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { ret_mcv: MCValue, fn_type: Type, arg_index: usize, - src: usize, + src_loc: Module.SrcLoc, stack_align: u32, /// Byte offset within the source file. @@ -428,7 +428,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn generateSymbol( bin_file: *link.File, - src: usize, + src_loc: Module.SrcLoc, typed_value: TypedValue, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, @@ -450,19 +450,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try branch_stack.append(.{}); const src_data: struct { lbrace_src: usize, rbrace_src: usize, source: []const u8 } = blk: { - if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| { - const tree = container_scope.file_scope.contents.tree; - const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; - const block = fn_proto.getBodyNode().?.castTag(.Block).?; - const lbrace_src = tree.token_locs[block.lbrace].start; - const rbrace_src = tree.token_locs[block.rbrace].start; - break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source }; - } else if (module_fn.owner_decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { - const byte_off = zir_module.contents.module.decls[module_fn.owner_decl.src_index].inst.src; - break :blk .{ .lbrace_src = byte_off, .rbrace_src = byte_off, .source = zir_module.source.bytes }; - } else { - unreachable; - } + const container_scope = module_fn.owner_decl.container; + const tree = container_scope.file_scope.contents.tree; + const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const lbrace_src = tree.token_locs[block.lbrace].start; + const rbrace_src = tree.token_locs[block.rbrace].start; + break :blk .{ + .lbrace_src = lbrace_src, + .rbrace_src = rbrace_src, + .source = tree.source, + }; }; var function = Self{ @@ -478,7 +476,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .fn_type = fn_type, .arg_index = 0, .branch_stack = &branch_stack, - .src = src, + .src_loc = src_loc, .stack_align = undefined, .prev_di_pc = 0, .prev_di_src = src_data.lbrace_src, @@ -489,7 +487,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { defer function.stack.deinit(bin_file.allocator); defer function.exitlude_jump_relocs.deinit(bin_file.allocator); - var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) { + var call_info = function.resolveCallingConventionValues(src_loc.byte_offset, fn_type) catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, else => |e| return e, }; @@ -536,12 +534,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const stack_end = self.max_end_stack; if (stack_end > math.maxInt(i32)) - return self.fail(self.src, "too much stack used in call parameters", .{}); + return self.failSymbol("too much stack used in call parameters", .{}); const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end)); if (self.code.items.len >= math.maxInt(i32)) { - return self.fail(self.src, "unable to perform relocation: jump too far", .{}); + return self.failSymbol("unable to perform relocation: jump too far", .{}); } if (self.exitlude_jump_relocs.items.len == 1) { self.code.items.len -= 5; @@ -598,7 +596,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (Instruction.Operand.fromU32(@intCast(u32, aligned_stack_end))) |op| { writeInt(u32, self.code.items[backpatch_reloc..][0..4], Instruction.sub(.al, .sp, .sp, op).toU32()); } else { - return self.fail(self.src, "TODO ARM: allow larger stacks", .{}); + return self.failSymbol("TODO ARM: allow larger stacks", .{}); } try self.dbgSetEpilogueBegin(); @@ -624,7 +622,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (math.cast(i26, amt)) |offset| { writeInt(u32, self.code.items[jmp_reloc..][0..4], Instruction.b(.al, offset).toU32()); } else |err| { - return self.fail(self.src, "exitlude jump is too large", .{}); + return self.failSymbol("exitlude jump is too large", .{}); } } } @@ -3678,7 +3676,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { 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.allocator, src, format, args); + self.err_msg = try ErrorMsg.create(self.bin_file.allocator, .{ + .file_scope = self.src_loc.file_scope, + .byte_offset = src, + }, format, args); + return error.CodegenFail; + } + + fn failSymbol(self: *Self, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + assert(self.err_msg == null); + self.err_msg = try ErrorMsg.create(self.bin_file.allocator, self.src_loc, format, args); return error.CodegenFail; } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 8c85f482fd..b26f753757 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -114,10 +114,13 @@ pub const DeclGen = struct { module: *Module, decl: *Decl, fwd_decl: std.ArrayList(u8), - error_msg: ?*Compilation.ErrorMsg, + error_msg: ?*Module.ErrorMsg, fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { - dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args); + dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{ + .file_scope = dg.decl.getFileScope(), + .byte_offset = src, + }, format, args); return error.AnalysisFail; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5d753c41cb..1edd466d54 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -148,7 +148,7 @@ pub const LLVMIRModule = struct { object_path: []const u8, gpa: *Allocator, - err_msg: ?*Compilation.ErrorMsg = null, + err_msg: ?*Module.ErrorMsg = null, // TODO: The fields below should really move into a different struct, // because they are only valid when generating a function @@ -177,6 +177,8 @@ pub const LLVMIRModule = struct { break_vals: *BreakValues, }) = .{}, + src_loc: Module.SrcLoc, + const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock); const BreakValues = std.ArrayListUnmanaged(*const llvm.Value); @@ -254,6 +256,8 @@ pub const LLVMIRModule = struct { .builder = builder, .object_path = object_path, .gpa = gpa, + // TODO move this field into a struct that is only instantiated per gen() call + .src_loc = undefined, }; return self; } @@ -335,6 +339,8 @@ pub const LLVMIRModule = struct { const typed_value = decl.typed_value.most_recent.typed_value; const src = decl.src(); + self.src_loc = decl.srcLoc(); + log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); if (typed_value.val.castTag(.function)) |func_payload| { @@ -853,7 +859,10 @@ pub const LLVMIRModule = struct { pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @setCold(true); assert(self.err_msg == null); - self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); + self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ + .file_scope = self.src_loc.file_scope, + .byte_offset = src, + }, format, args); return error.CodegenFail; } }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index f7cd9b69ce..981d4ec3a3 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -670,7 +670,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none); + const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none); const code = switch (res) { .externally_managed => |x| x, .appended => code_buffer.items, @@ -732,7 +732,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), ); continue; } @@ -743,7 +743,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: Exports other than '_start'", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), ); continue; } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 8c76a4e967..ee50eb5d94 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2189,22 +2189,14 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { - if (decl.scope.cast(Module.Scope.Container)) |container_scope| { - const tree = container_scope.file_scope.contents.tree; - const file_ast_decls = tree.root_node.decls(); - // TODO Look into improving the performance here by adding a token-index-to-line - // lookup table. Currently this involves scanning over the source code for newlines. - const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; - const block = fn_proto.getBodyNode().?.castTag(.Block).?; - const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); - break :blk @intCast(u28, line_delta); - } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { - const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; - const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); - break :blk @intCast(u28, line_delta); - } else { - unreachable; - } + const tree = decl.container.file_scope.contents.tree; + const file_ast_decls = tree.root_node.decls(); + // TODO Look into improving the performance here by adding a token-index-to-line + // lookup table. Currently this involves scanning over the source code for newlines. + const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); + break :blk @intCast(u28, line_delta); }; const ptr_width_bytes = self.ptrWidthBytes(); @@ -2268,7 +2260,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { } else { // TODO implement .debug_info for global variables } - const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{ + const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{ .dwarf = .{ .dbg_line = &dbg_line_buffer, .dbg_info = &dbg_info_buffer, @@ -2642,7 +2634,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), ); continue; } @@ -2660,7 +2652,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}), ); continue; }, @@ -2703,8 +2695,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec if (self.llvm_ir_module) |_| return; - const container_scope = decl.scope.cast(Module.Scope.Container).?; - const tree = container_scope.file_scope.contents.tree; + const tree = decl.container.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d913a82328..1017405255 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1148,7 +1148,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } const res = if (debug_buffers) |*dbg| - try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{ + try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{ .dwarf = .{ .dbg_line = &dbg.dbg_line_buffer, .dbg_info = &dbg.dbg_info_buffer, @@ -1156,7 +1156,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }, }) else - try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none); + try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none); const code = switch (res) { .externally_managed => |x| x, @@ -1316,7 +1316,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), ); continue; } @@ -1334,7 +1334,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}), ); continue; }, diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 81a016ce42..fb7488a12c 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -906,8 +906,7 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M const tracy = trace(@src()); defer tracy.end(); - const container_scope = decl.scope.cast(Module.Scope.Container).?; - const tree = container_scope.file_scope.contents.tree; + const tree = decl.container.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. @@ -951,22 +950,14 @@ pub fn initDeclDebugBuffers( try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { - if (decl.scope.cast(Module.Scope.Container)) |container_scope| { - const tree = container_scope.file_scope.contents.tree; - const file_ast_decls = tree.root_node.decls(); - // TODO Look into improving the performance here by adding a token-index-to-line - // lookup table. Currently this involves scanning over the source code for newlines. - const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; - const block = fn_proto.getBodyNode().?.castTag(.Block).?; - const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); - break :blk @intCast(u28, line_delta); - } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { - const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; - const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); - break :blk @intCast(u28, line_delta); - } else { - unreachable; - } + const tree = decl.container.file_scope.contents.tree; + const file_ast_decls = tree.root_node.decls(); + // TODO Look into improving the performance here by adding a token-index-to-line + // lookup table. Currently this involves scanning over the source code for newlines. + const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); + break :blk @intCast(u28, line_delta); }; dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ diff --git a/src/main.zig b/src/main.zig index 867aa348b1..13bea13a5e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -221,7 +221,6 @@ const usage_build_generic = \\ \\Supported file types: \\ .zig Zig source code - \\ .zir Zig Intermediate Representation code \\ .o ELF object file \\ .o MACH-O (macOS) object file \\ .obj COFF (Windows) object file @@ -245,8 +244,6 @@ const usage_build_generic = \\ -fno-emit-bin Do not output machine code \\ -femit-asm[=path] Output .s (assembly code) \\ -fno-emit-asm (default) Do not output .s (assembly code) - \\ -femit-zir[=path] Produce a .zir file with Zig IR - \\ -fno-emit-zir (default) Do not produce a .zir file with Zig IR \\ -femit-llvm-ir[=path] Produce a .ll file with LLVM IR (requires LLVM extensions) \\ -fno-emit-llvm-ir (default) Do not produce a .ll file with LLVM IR \\ -femit-h[=path] Generate a C header file (.h) @@ -1631,18 +1628,12 @@ fn buildOutputType( var emit_docs_resolved = try emit_docs.resolve("docs"); defer emit_docs_resolved.deinit(); - const zir_out_path: ?[]const u8 = switch (emit_zir) { - .no => null, - .yes_default_path => blk: { - if (root_src_file) |rsf| { - if (mem.endsWith(u8, rsf, ".zir")) { - break :blk try std.fmt.allocPrint(arena, "{s}.out.zir", .{root_name}); - } - } - break :blk try std.fmt.allocPrint(arena, "{s}.zir", .{root_name}); + switch (emit_zir) { + .no => {}, + .yes_default_path, .yes => { + fatal("The -femit-zir implementation has been intentionally deleted so that it can be rewritten as a proper backend.", .{}); }, - .yes => |p| p, - }; + } const root_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { @@ -1753,7 +1744,7 @@ fn buildOutputType( .dll_export_fns = dll_export_fns, .object_format = object_format, .optimize_mode = optimize_mode, - .keep_source_files_loaded = zir_out_path != null, + .keep_source_files_loaded = false, .clang_argv = clang_argv.items, .lld_argv = lld_argv.items, .lib_dirs = lib_dirs.items, @@ -1845,7 +1836,7 @@ fn buildOutputType( } }; - updateModule(gpa, comp, zir_out_path, hook) catch |err| switch (err) { + updateModule(gpa, comp, hook) catch |err| switch (err) { error.SemanticAnalyzeFail => if (!watch) process.exit(1), else => |e| return e, }; @@ -1980,7 +1971,7 @@ fn buildOutputType( if (output_mode == .Exe) { try comp.makeBinFileWritable(); } - updateModule(gpa, comp, zir_out_path, hook) catch |err| switch (err) { + updateModule(gpa, comp, hook) catch |err| switch (err) { error.SemanticAnalyzeFail => continue, else => |e| return e, }; @@ -2003,7 +1994,7 @@ const AfterUpdateHook = union(enum) { update: []const u8, }; -fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, hook: AfterUpdateHook) !void { +fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !void { try comp.update(); var errors = try comp.getAllErrorsAlloc(); @@ -2013,6 +2004,10 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, for (errors.list) |full_err_msg| { full_err_msg.renderToStdErr(); } + const log_text = comp.getCompileLogOutput(); + if (log_text.len != 0) { + std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); + } return error.SemanticAnalyzeFail; } else switch (hook) { .none => {}, @@ -2024,20 +2019,6 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, .{}, ), } - - if (zir_out_path) |zop| { - const module = comp.bin_file.options.module orelse - fatal("-femit-zir with no zig source code", .{}); - var new_zir_module = try zir.emit(gpa, module); - defer new_zir_module.deinit(gpa); - - const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{}); - defer baf.destroy(); - - try new_zir_module.writeToStream(gpa, baf.writer()); - - try baf.finish(); - } } fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void { @@ -2506,7 +2487,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v }; defer comp.destroy(); - try updateModule(gpa, comp, null, .none); + try updateModule(gpa, comp, .none); try comp.makeBinFileExecutable(); child_argv.items[argv_index_exe] = try comp.bin_file.options.emit.?.directory.join( diff --git a/src/test.zig b/src/test.zig index 59927525df..1c9fb57f01 100644 --- a/src/test.zig +++ b/src/test.zig @@ -15,6 +15,8 @@ const CrossTarget = std.zig.CrossTarget; const zig_h = link.File.C.zig_h; +const hr = "=" ** 40; + test "self-hosted" { var ctx = TestContext.init(); defer ctx.deinit(); @@ -29,23 +31,32 @@ const ErrorMsg = union(enum) { msg: []const u8, line: u32, column: u32, + kind: Kind, }, plain: struct { msg: []const u8, + kind: Kind, }, - fn init(other: Compilation.AllErrors.Message) ErrorMsg { + const Kind = enum { + @"error", + note, + }; + + fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg { switch (other) { .src => |src| return .{ .src = .{ .msg = src.msg, .line = @intCast(u32, src.line), .column = @intCast(u32, src.column), + .kind = kind, }, }, .plain => |plain| return .{ .plain = .{ .msg = plain.msg, + .kind = kind, }, }, } @@ -59,14 +70,15 @@ const ErrorMsg = union(enum) { ) !void { switch (self) { .src => |src| { - return writer.print(":{d}:{d}: error: {s}", .{ + return writer.print(":{d}:{d}: {s}: {s}", .{ src.line + 1, src.column + 1, + @tagName(src.kind), src.msg, }); }, .plain => |plain| { - return writer.print("error: {s}", .{plain.msg}); + return writer.print("{s}: {s}", .{ plain.msg, @tagName(plain.kind) }); }, } } @@ -86,9 +98,6 @@ pub const TestContext = struct { /// effects of the incremental compilation. src: [:0]const u8, case: union(enum) { - /// A transformation update transforms the input and tests against - /// the expected output ZIR. - Transformation: [:0]const u8, /// Check the main binary output file against an expected set of bytes. /// This is most useful with, for example, `-ofmt=c`. CompareObjectFile: []const u8, @@ -139,15 +148,6 @@ pub const TestContext = struct { files: std.ArrayList(File), - /// Adds a subcase in which the module is updated with `src`, and the - /// resulting ZIR is validated against `result`. - pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { - self.updates.append(.{ - .src = src, - .case = .{ .Transformation = result }, - }) catch unreachable; - } - /// Adds a subcase in which the module is updated with `src`, and a C /// header is generated. pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void { @@ -182,31 +182,37 @@ pub const TestContext = struct { /// the form `:line:column: error: message`. pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; - for (errors) |e, i| { - if (e[0] != ':') { - array[i] = .{ .plain = .{ .msg = e } }; + for (errors) |err_msg_line, i| { + if (std.mem.startsWith(u8, err_msg_line, "error: ")) { + array[i] = .{ + .plain = .{ .msg = err_msg_line["error: ".len..], .kind = .@"error" }, + }; + continue; + } else if (std.mem.startsWith(u8, err_msg_line, "note: ")) { + array[i] = .{ + .plain = .{ .msg = err_msg_line["note: ".len..], .kind = .note }, + }; continue; } - var cur = e[1..]; - var line_index = std.mem.indexOf(u8, cur, ":"); - if (line_index == null) { - @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); - } - const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); - cur = cur[line_index.? + 1 ..]; - const column_index = std.mem.indexOf(u8, cur, ":"); - if (column_index == null) { - @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); - } - const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); - cur = cur[column_index.? + 2 ..]; - if (!std.mem.eql(u8, cur[0..7], "error: ")) { - @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); - } - const msg = cur[7..]; + // example: ":1:2: error: bad thing happened" + var it = std.mem.split(err_msg_line, ":"); + _ = it.next() orelse @panic("missing colon"); + const line_text = it.next() orelse @panic("missing line"); + const col_text = it.next() orelse @panic("missing column"); + const kind_text = it.next() orelse @panic("missing 'error'/'note'"); + const msg = it.rest()[1..]; // skip over the space at end of "error: " + + const line = std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number"); + const column = std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number"); + const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error")) + .@"error" + else if (std.mem.eql(u8, kind_text, " note")) + .note + else + @panic("expected 'error'/'note'"); if (line == 0 or column == 0) { - @panic("Invalid test: error line and column must be specified starting at one!"); + @panic("line and column must be specified starting at one"); } array[i] = .{ @@ -214,6 +220,7 @@ pub const TestContext = struct { .msg = msg, .line = line - 1, .column = column - 1, + .kind = kind, }, }; } @@ -689,25 +696,20 @@ pub const TestContext = struct { var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); if (all_errors.list.len != 0) { - std.debug.print("\nErrors occurred updating the compilation:\n================\n", .{}); + std.debug.print("\nErrors occurred updating the compilation:\n{s}\n", .{hr}); for (all_errors.list) |err_msg| { switch (err_msg) { .src => |src| { - std.debug.print(":{d}:{d}: error: {s}\n================\n", .{ - src.line + 1, src.column + 1, src.msg, + std.debug.print(":{d}:{d}: error: {s}\n{s}\n", .{ + src.line + 1, src.column + 1, src.msg, hr, }); }, .plain => |plain| { - std.debug.print("error: {s}\n================\n", .{plain.msg}); + std.debug.print("error: {s}\n{s}\n", .{ plain.msg, hr }); }, } } // TODO print generated C code - //if (comp.bin_file.cast(link.File.C)) |c_file| { - // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ - // c_file.main.items, - // }); - //} std.debug.print("Test failed.\n", .{}); std.process.exit(1); } @@ -728,48 +730,36 @@ pub const TestContext = struct { std.testing.expectEqualStrings(expected_output, out); }, - .Transformation => |expected_output| { - update_node.setEstimatedTotalItems(5); - var emit_node = update_node.start("emit", 0); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, comp.bin_file.options.module.?); - defer new_zir_module.deinit(allocator); - emit_node.end(); - - var write_node = update_node.start("write", 0); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.writer()); - write_node.end(); - + .Error => |case_error_list| { var test_node = update_node.start("assert", 0); test_node.activate(); defer test_node.end(); - std.testing.expectEqualStrings(expected_output, out_zir.items); - }, - .Error => |e| { - var test_node = update_node.start("assert", 0); - test_node.activate(); - defer test_node.end(); - var handled_errors = try arena.alloc(bool, e.len); - for (handled_errors) |*handled| { - handled.* = false; - } - var all_errors = try comp.getAllErrorsAlloc(); - defer all_errors.deinit(allocator); - for (all_errors.list) |a| { - for (e) |ex, i| { - const a_tag: @TagType(@TypeOf(a)) = a; - const ex_tag: @TagType(@TypeOf(ex)) = ex; - switch (a) { - .src => |src| { + const handled_errors = try arena.alloc(bool, case_error_list.len); + std.mem.set(bool, handled_errors, false); + + var actual_errors = try comp.getAllErrorsAlloc(); + defer actual_errors.deinit(allocator); + + var any_failed = false; + var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator); + defer notes_to_check.deinit(); + + for (actual_errors.list) |actual_error| { + for (case_error_list) |case_msg, i| { + const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg; + switch (actual_error) { + .src => |actual_msg| { + for (actual_msg.notes) |*note| { + try notes_to_check.append(note); + } + if (ex_tag != .src) continue; - if (src.line == ex.src.line and - src.column == ex.src.column and - std.mem.eql(u8, ex.src.msg, src.msg)) + if (actual_msg.line == case_msg.src.line and + actual_msg.column == case_msg.src.column and + std.mem.eql(u8, case_msg.src.msg, actual_msg.msg) and + case_msg.src.kind == .@"error") { handled_errors[i] = true; break; @@ -778,7 +768,9 @@ pub const TestContext = struct { .plain => |plain| { if (ex_tag != .plain) continue; - if (std.mem.eql(u8, ex.plain.msg, plain.msg)) { + if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and + case_msg.plain.kind == .@"error") + { handled_errors[i] = true; break; } @@ -786,23 +778,67 @@ pub const TestContext = struct { } } else { std.debug.print( - "{s}\nUnexpected error:\n================\n{}\n================\nTest failed.\n", - .{ case.name, ErrorMsg.init(a) }, + "\nUnexpected error:\n{s}\n{}\n{s}", + .{ hr, ErrorMsg.init(actual_error, .@"error"), hr }, ); - std.process.exit(1); + any_failed = true; + } + } + while (notes_to_check.popOrNull()) |note| { + for (case_error_list) |case_msg, i| { + const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg; + switch (note.*) { + .src => |actual_msg| { + for (actual_msg.notes) |*sub_note| { + try notes_to_check.append(sub_note); + } + if (ex_tag != .src) continue; + + if (actual_msg.line == case_msg.src.line and + actual_msg.column == case_msg.src.column and + std.mem.eql(u8, case_msg.src.msg, actual_msg.msg) and + case_msg.src.kind == .note) + { + handled_errors[i] = true; + break; + } + }, + .plain => |plain| { + if (ex_tag != .plain) continue; + + if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and + case_msg.plain.kind == .note) + { + handled_errors[i] = true; + break; + } + }, + } + } else { + std.debug.print( + "\nUnexpected note:\n{s}\n{}\n{s}", + .{ hr, ErrorMsg.init(note.*, .note), hr }, + ); + any_failed = true; } } for (handled_errors) |handled, i| { if (!handled) { - const er = e[i]; std.debug.print( - "{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n", - .{ case.name, er }, + "\nExpected error not found:\n{s}\n{}\n{s}", + .{ hr, case_error_list[i], hr }, ); - std.process.exit(1); + any_failed = true; } } + + if (any_failed) { + std.debug.print("\nTest case '{s}' failed, update_index={d}.\n", .{ + case.name, update_index, + }); + std.process.exit(1); + } }, .Execution => |expected_stdout| { update_node.setEstimatedTotalItems(4); diff --git a/src/type/Enum.zig b/src/type/Enum.zig index 9b9ec5b319..4dfd5f6e44 100644 --- a/src/type/Enum.zig +++ b/src/type/Enum.zig @@ -21,7 +21,7 @@ pub const Field = struct { }; pub const Zir = struct { - body: zir.Module.Body, + body: zir.Body, inst: *zir.Inst, }; diff --git a/src/type/Struct.zig b/src/type/Struct.zig index d6a591c95e..24e3a0dcad 100644 --- a/src/type/Struct.zig +++ b/src/type/Struct.zig @@ -24,7 +24,7 @@ pub const Field = struct { }; pub const Zir = struct { - body: zir.Module.Body, + body: zir.Body, inst: *zir.Inst, }; diff --git a/src/type/Union.zig b/src/type/Union.zig index 26cc1796c6..5c7acf7d36 100644 --- a/src/type/Union.zig +++ b/src/type/Union.zig @@ -24,7 +24,7 @@ pub const Field = struct { }; pub const Zir = struct { - body: zir.Module.Body, + body: zir.Body, inst: *zir.Inst, }; diff --git a/src/zir.zig b/src/zir.zig index 0ccb09efac..0e7b3a3520 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -12,17 +12,6 @@ const TypedValue = @import("TypedValue.zig"); const ir = @import("ir.zig"); const IrModule = @import("Module.zig"); -/// This struct is relevent only for the ZIR Module text format. It is not used for -/// semantic analysis of Zig source code. -pub const Decl = struct { - name: []const u8, - - /// Hash of slice into the source of the part after the = and before the next instruction. - contents_hash: std.zig.SrcHash, - - inst: *Inst, -}; - /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for /// in-memory, analyzed instructions with types and values. /// We use a table to map these instruction to their respective semantically analyzed @@ -141,15 +130,12 @@ pub const Inst = struct { container_field, /// Declares the beginning of a statement. Used for debug info. dbg_stmt, - /// Represents a pointer to a global decl by name. + /// Represents a pointer to a global decl. declref, /// Represents a pointer to a global decl by string name. declref_str, - /// The syntax `@foo` is equivalent to `declval("foo")`. - /// declval is equivalent to declref followed by deref. + /// Equivalent to a declref followed by deref. declval, - /// Same as declval but the parameter is a `*Module.Decl` rather than a name. - declval_in_module, /// Load the value from a pointer. deref, /// Arithmetic division. Asserts no integer overflow. @@ -419,7 +405,6 @@ pub const Inst = struct { .declref => DeclRef, .declref_str => DeclRefStr, .declval => DeclVal, - .declval_in_module => DeclValInModule, .coerce_result_block_ptr => CoerceResultBlockPtr, .compilelog => CompileLog, .loop => Loop, @@ -496,7 +481,6 @@ pub const Inst = struct { .declref, .declref_str, .declval, - .declval_in_module, .deref, .div, .elemptr, @@ -650,7 +634,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - body: Module.Body, + body: Body, }, kw_args: struct {}, }; @@ -705,7 +689,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - name: []const u8, + decl: *IrModule.Decl, }, kw_args: struct {}, }; @@ -724,16 +708,6 @@ pub const Inst = struct { pub const base_tag = Tag.declval; base: Inst, - positionals: struct { - name: []const u8, - }, - kw_args: struct {}, - }; - - pub const DeclValInModule = struct { - pub const base_tag = Tag.declval_in_module; - base: Inst, - positionals: struct { decl: *IrModule.Decl, }, @@ -758,10 +732,7 @@ pub const Inst = struct { positionals: struct { to_log: []*Inst, }, - kw_args: struct { - /// If we have seen it already so don't make another error - seen: bool = false, - }, + kw_args: struct {}, }; pub const Const = struct { @@ -799,7 +770,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - body: Module.Body, + body: Body, }, kw_args: struct {}, }; @@ -838,7 +809,7 @@ pub const Inst = struct { positionals: struct { fn_type: *Inst, - body: Module.Body, + body: Body, }, kw_args: struct { is_inline: bool = false, @@ -998,8 +969,8 @@ pub const Inst = struct { positionals: struct { condition: *Inst, - then_body: Module.Body, - else_body: Module.Body, + then_body: Body, + else_body: Body, }, kw_args: struct {}, }; @@ -1078,7 +1049,7 @@ pub const Inst = struct { /// List of all individual items and ranges items: []*Inst, cases: []Case, - else_body: Module.Body, + else_body: Body, }, kw_args: struct { /// Pointer to first range if such exists. @@ -1092,7 +1063,7 @@ pub const Inst = struct { pub const Case = struct { item: *Inst, - body: Module.Body, + body: Body, }; }; pub const TypeOfPeer = struct { @@ -1192,6 +1163,10 @@ pub const ErrorMsg = struct { msg: []const u8, }; +pub const Body = struct { + instructions: []*Inst, +}; + pub const Module = struct { decls: []*Decl, arena: std.heap.ArenaAllocator, @@ -1199,6 +1174,15 @@ pub const Module = struct { metadata: std.AutoHashMap(*Inst, MetaData), body_metadata: std.AutoHashMap(*Body, BodyMetaData), + pub const Decl = struct { + name: []const u8, + + /// Hash of slice into the source of the part after the = and before the next instruction. + contents_hash: std.zig.SrcHash, + + inst: *Inst, + }; + pub const MetaData = struct { deaths: ir.Inst.DeathsInt, addr: usize, @@ -1208,10 +1192,6 @@ pub const Module = struct { deaths: []*Inst, }; - pub const Body = struct { - instructions: []*Inst, - }; - pub fn deinit(self: *Module, allocator: *Allocator) void { self.metadata.deinit(); self.body_metadata.deinit(); @@ -1369,7 +1349,7 @@ const Writer = struct { } try stream.writeByte(']'); }, - Module.Body => { + Body => { try stream.writeAll("{\n"); if (self.module.body_metadata.get(param_ptr)) |metadata| { if (metadata.deaths.len > 0) { @@ -1468,8 +1448,6 @@ const Writer = struct { try stream.print("@{s}", .{info.name}); } } else if (inst.cast(Inst.DeclVal)) |decl_val| { - try stream.print("@{s}", .{decl_val.positionals.name}); - } else if (inst.cast(Inst.DeclValInModule)) |decl_val| { try stream.print("@{s}", .{decl_val.positionals.decl.name}); } else { // This should be unreachable in theory, but since ZIR is used for debugging the compiler @@ -1479,502 +1457,6 @@ const Writer = struct { } }; -pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module { - var global_name_map = std.StringHashMap(*Inst).init(allocator); - defer global_name_map.deinit(); - - var parser: Parser = .{ - .allocator = allocator, - .arena = std.heap.ArenaAllocator.init(allocator), - .i = 0, - .source = source, - .global_name_map = &global_name_map, - .decls = .{}, - .unnamed_index = 0, - .block_table = std.StringHashMap(*Inst.Block).init(allocator), - .loop_table = std.StringHashMap(*Inst.Loop).init(allocator), - }; - defer parser.block_table.deinit(); - defer parser.loop_table.deinit(); - errdefer parser.arena.deinit(); - - parser.parseRoot() catch |err| switch (err) { - error.ParseFailure => { - assert(parser.error_msg != null); - }, - else => |e| return e, - }; - - return 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), - }; -} - -const Parser = struct { - allocator: *Allocator, - arena: std.heap.ArenaAllocator, - i: usize, - source: [:0]const u8, - decls: std.ArrayListUnmanaged(*Decl), - global_name_map: *std.StringHashMap(*Inst), - error_msg: ?ErrorMsg = null, - unnamed_index: usize, - block_table: std.StringHashMap(*Inst.Block), - loop_table: std.StringHashMap(*Inst.Loop), - - const Body = struct { - instructions: std.ArrayList(*Inst), - name_map: *std.StringHashMap(*Inst), - }; - - fn parseBody(self: *Parser, body_ctx: ?*Body) !Module.Body { - var name_map = std.StringHashMap(*Inst).init(self.allocator); - defer name_map.deinit(); - - var body_context = Body{ - .instructions = std.ArrayList(*Inst).init(self.allocator), - .name_map = if (body_ctx) |bctx| bctx.name_map else &name_map, - }; - defer body_context.instructions.deinit(); - - try requireEatBytes(self, "{"); - skipSpace(self); - - while (true) : (self.i += 1) switch (self.source[self.i]) { - ';' => _ = try skipToAndOver(self, '\n'), - '%' => { - self.i += 1; - const ident = try skipToAndOver(self, ' '); - skipSpace(self); - try requireEatBytes(self, "="); - skipSpace(self); - const decl = try parseInstruction(self, &body_context, ident); - const ident_index = body_context.instructions.items.len; - if (try body_context.name_map.fetchPut(ident, decl.inst)) |_| { - return self.fail("redefinition of identifier '{s}'", .{ident}); - } - try body_context.instructions.append(decl.inst); - continue; - }, - ' ', '\n' => continue, - '}' => { - self.i += 1; - break; - }, - else => |byte| return self.failByte(byte), - }; - - // Move the instructions to the arena - const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len); - mem.copy(*Inst, instrs, body_context.instructions.items); - return Module.Body{ .instructions = instrs }; - } - - fn parseStringLiteral(self: *Parser) ![]u8 { - const start = self.i; - try self.requireEatBytes("\""); - - while (true) : (self.i += 1) switch (self.source[self.i]) { - '"' => { - self.i += 1; - const span = self.source[start..self.i]; - var bad_index: usize = undefined; - const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) { - error.InvalidCharacter => { - self.i = start + bad_index; - const bad_byte = self.source[self.i]; - return self.fail("invalid string literal character: '{c}'\n", .{bad_byte}); - }, - else => |e| return e, - }; - return parsed; - }, - '\\' => { - self.i += 1; - continue; - }, - 0 => return self.failByte(0), - else => continue, - }; - } - - fn parseIntegerLiteral(self: *Parser) !BigIntConst { - const start = self.i; - if (self.source[self.i] == '-') self.i += 1; - while (true) : (self.i += 1) switch (self.source[self.i]) { - '0'...'9' => continue, - else => break, - }; - const number_text = self.source[start..self.i]; - const base = 10; - // TODO reuse the same array list for this - const limbs_buffer_len = std.math.big.int.calcSetStringLimbsBufferLen(base, number_text.len); - const limbs_buffer = try self.allocator.alloc(std.math.big.Limb, limbs_buffer_len); - defer self.allocator.free(limbs_buffer); - const limb_len = std.math.big.int.calcSetStringLimbCount(base, number_text.len); - const limbs = try self.arena.allocator.alloc(std.math.big.Limb, limb_len); - var result = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - result.setString(base, number_text, limbs_buffer, self.allocator) catch |err| switch (err) { - error.InvalidCharacter => { - self.i = start; - return self.fail("invalid digit in integer literal", .{}); - }, - }; - return result.toConst(); - } - - fn parseRoot(self: *Parser) !void { - // The IR format is designed so that it can be tokenized and parsed at the same time. - while (true) { - switch (self.source[self.i]) { - ';' => _ = try skipToAndOver(self, '\n'), - '@' => { - self.i += 1; - const ident = try skipToAndOver(self, ' '); - skipSpace(self); - try requireEatBytes(self, "="); - skipSpace(self); - const decl = try parseInstruction(self, null, ident); - const ident_index = self.decls.items.len; - if (try self.global_name_map.fetchPut(ident, decl.inst)) |_| { - return self.fail("redefinition of identifier '{s}'", .{ident}); - } - try self.decls.append(self.allocator, decl); - }, - ' ', '\n' => self.i += 1, - 0 => break, - else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), - } - } - } - - fn eatByte(self: *Parser, byte: u8) bool { - if (self.source[self.i] != byte) return false; - self.i += 1; - return true; - } - - fn skipSpace(self: *Parser) void { - while (self.source[self.i] == ' ' or self.source[self.i] == '\n') { - self.i += 1; - } - } - - fn requireEatBytes(self: *Parser, bytes: []const u8) !void { - const start = self.i; - for (bytes) |byte| { - if (self.source[self.i] != byte) { - self.i = start; - return self.fail("expected '{s}'", .{bytes}); - } - self.i += 1; - } - } - - fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 { - const start_i = self.i; - while (self.source[self.i] != 0) : (self.i += 1) { - if (self.source[self.i] == byte) { - const result = self.source[start_i..self.i]; - self.i += 1; - return result; - } - } - return self.fail("unexpected EOF", .{}); - } - - /// ParseFailure is an internal error code; handled in `parse`. - const InnerError = error{ ParseFailure, OutOfMemory }; - - fn failByte(self: *Parser, byte: u8) InnerError { - if (byte == 0) { - return self.fail("unexpected EOF", .{}); - } else { - return self.fail("unexpected byte: '{c}'", .{byte}); - } - } - - fn fail(self: *Parser, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - self.error_msg = ErrorMsg{ - .byte_offset = self.i, - .msg = try std.fmt.allocPrint(&self.arena.allocator, format, args), - }; - return error.ParseFailure; - } - - fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Decl { - const contents_start = self.i; - const fn_name = try skipToAndOver(self, '('); - inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { - if (mem.eql(u8, field.name, fn_name)) { - const tag = @field(Inst.Tag, field.name); - return parseInstructionGeneric(self, field.name, tag.Type(), tag, body_ctx, name, contents_start); - } - } - return self.fail("unknown instruction '{s}'", .{fn_name}); - } - - fn parseInstructionGeneric( - self: *Parser, - comptime fn_name: []const u8, - comptime InstType: type, - tag: Inst.Tag, - body_ctx: ?*Body, - inst_name: []const u8, - contents_start: usize, - ) InnerError!*Decl { - const inst_specific = try self.arena.allocator.create(InstType); - inst_specific.base = .{ - .src = self.i, - .tag = tag, - }; - - if (InstType == Inst.Block) { - try self.block_table.put(inst_name, inst_specific); - } else if (InstType == Inst.Loop) { - try self.loop_table.put(inst_name, inst_specific); - } - - if (@hasField(InstType, "ty")) { - inst_specific.ty = opt_type orelse { - return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); - }; - } - - const Positionals = @TypeOf(inst_specific.positionals); - inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { - if (self.source[self.i] == ',') { - self.i += 1; - skipSpace(self); - } else if (self.source[self.i] == ')') { - return self.fail("expected positional parameter '{s}'", .{arg_field.name}); - } - @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( - self, - arg_field.field_type, - body_ctx, - ); - skipSpace(self); - } - - const KW_Args = @TypeOf(inst_specific.kw_args); - inst_specific.kw_args = .{}; // assign defaults - skipSpace(self); - while (eatByte(self, ',')) { - skipSpace(self); - const name = try skipToAndOver(self, '='); - inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| { - const field_name = arg_field.name; - if (mem.eql(u8, name, field_name)) { - const NonOptional = switch (@typeInfo(arg_field.field_type)) { - .Optional => |info| info.child, - else => arg_field.field_type, - }; - @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx); - break; - } - } else { - return self.fail("unrecognized keyword parameter: '{s}'", .{name}); - } - skipSpace(self); - } - try requireEatBytes(self, ")"); - - const decl = try self.arena.allocator.create(Decl); - decl.* = .{ - .name = inst_name, - .contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]), - .inst = &inst_specific.base, - }; - - return decl; - } - - fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T { - if (@typeInfo(T) == .Enum) { - const start = self.i; - while (true) : (self.i += 1) switch (self.source[self.i]) { - ' ', '\n', ',', ')' => { - const enum_name = self.source[start..self.i]; - return std.meta.stringToEnum(T, enum_name) orelse { - return self.fail("tag '{s}' not a member of enum '{s}'", .{ enum_name, @typeName(T) }); - }; - }, - 0 => return self.failByte(0), - else => continue, - }; - } - switch (T) { - Module.Body => return parseBody(self, body_ctx), - bool => { - const bool_value = switch (self.source[self.i]) { - '0' => false, - '1' => true, - else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}), - }; - self.i += 1; - return bool_value; - }, - []*Inst => { - try requireEatBytes(self, "["); - skipSpace(self); - if (eatByte(self, ']')) return &[0]*Inst{}; - - var instructions = std.ArrayList(*Inst).init(&self.arena.allocator); - while (true) { - skipSpace(self); - try instructions.append(try parseParameterInst(self, body_ctx)); - skipSpace(self); - if (!eatByte(self, ',')) break; - } - try requireEatBytes(self, "]"); - return instructions.toOwnedSlice(); - }, - *Inst => return parseParameterInst(self, body_ctx), - []u8, []const u8 => return self.parseStringLiteral(), - BigIntConst => return self.parseIntegerLiteral(), - usize => { - const big_int = try self.parseIntegerLiteral(); - return big_int.to(usize) catch |err| return self.fail("integer literal: {s}", .{@errorName(err)}); - }, - TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), - *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), - *Inst.Block => { - const name = try self.parseStringLiteral(); - return self.block_table.get(name).?; - }, - *Inst.Loop => { - const name = try self.parseStringLiteral(); - return self.loop_table.get(name).?; - }, - [][]const u8 => { - try requireEatBytes(self, "["); - skipSpace(self); - if (eatByte(self, ']')) return &[0][]const u8{}; - - var strings = std.ArrayList([]const u8).init(&self.arena.allocator); - while (true) { - skipSpace(self); - try strings.append(try self.parseStringLiteral()); - skipSpace(self); - if (!eatByte(self, ',')) break; - } - try requireEatBytes(self, "]"); - return strings.toOwnedSlice(); - }, - []Inst.SwitchBr.Case => { - try requireEatBytes(self, "{"); - skipSpace(self); - if (eatByte(self, '}')) return &[0]Inst.SwitchBr.Case{}; - - var cases = std.ArrayList(Inst.SwitchBr.Case).init(&self.arena.allocator); - while (true) { - const cur = try cases.addOne(); - skipSpace(self); - cur.item = try self.parseParameterGeneric(*Inst, body_ctx); - skipSpace(self); - try requireEatBytes(self, "=>"); - cur.body = try self.parseBody(body_ctx); - skipSpace(self); - if (!eatByte(self, ',')) break; - } - skipSpace(self); - try requireEatBytes(self, "}"); - return cases.toOwnedSlice(); - }, - else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), - } - return self.fail("TODO parse parameter {s}", .{@typeName(T)}); - } - - fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst { - const local_ref = switch (self.source[self.i]) { - '@' => false, - '%' => true, - else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), - }; - const map = if (local_ref) - if (body_ctx) |bc| - bc.name_map - else - return self.fail("referencing a % instruction in global scope", .{}) - else - self.global_name_map; - - self.i += 1; - const name_start = self.i; - while (true) : (self.i += 1) switch (self.source[self.i]) { - 0, ' ', '\n', ',', ')', ']' => break, - else => continue, - }; - const ident = self.source[name_start..self.i]; - return map.get(ident) orelse { - const bad_name = self.source[name_start - 1 .. self.i]; - const src = name_start - 1; - if (local_ref) { - self.i = src; - return self.fail("unrecognized identifier: {s}", .{bad_name}); - } else { - const declval = try self.arena.allocator.create(Inst.DeclVal); - declval.* = .{ - .base = .{ - .src = src, - .tag = Inst.DeclVal.base_tag, - }, - .positionals = .{ .name = ident }, - .kw_args = .{}, - }; - return &declval.base; - } - }; - } - - fn generateName(self: *Parser) ![]u8 { - const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${d}", .{self.unnamed_index}); - self.unnamed_index += 1; - return result; - } -}; - -pub fn emit(allocator: *Allocator, old_module: *IrModule) !Module { - var ctx: EmitZIR = .{ - .allocator = allocator, - .decls = .{}, - .arena = std.heap.ArenaAllocator.init(allocator), - .old_module = old_module, - .next_auto_name = 0, - .names = std.StringArrayHashMap(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), - }; - errdefer ctx.metadata.deinit(); - errdefer 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(); - errdefer ctx.arena.deinit(); - - try ctx.emit(); - - 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; @@ -2374,1052 +1856,14 @@ const DumpTzir = struct { } }; -const EmitZIR = struct { - allocator: *Allocator, - arena: std.heap.ArenaAllocator, - old_module: *const IrModule, - decls: std.ArrayListUnmanaged(*Decl), - names: std.StringArrayHashMap(void), - next_auto_name: usize, - primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), - 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 - // by the hash table. - var src_decls = std.ArrayList(*IrModule.Decl).init(self.allocator); - defer src_decls.deinit(); - try src_decls.ensureCapacity(self.old_module.decl_table.items().len); - try self.decls.ensureCapacity(self.allocator, self.old_module.decl_table.items().len); - try self.names.ensureCapacity(self.old_module.decl_table.items().len); - - for (self.old_module.decl_table.items()) |entry| { - const decl = entry.value; - src_decls.appendAssumeCapacity(decl); - self.names.putAssumeCapacityNoClobber(mem.spanZ(decl.name), {}); - } - std.sort.sort(*IrModule.Decl, src_decls.items, {}, (struct { - fn lessThan(context: void, a: *IrModule.Decl, b: *IrModule.Decl) bool { - return a.src_index < b.src_index; - } - }).lessThan); - - // Emit all the decls. - for (src_decls.items) |ir_decl| { - switch (ir_decl.analysis) { - .unreferenced => continue, - - .complete => {}, - .codegen_failure => {}, // We still can emit the ZIR. - .codegen_failure_retryable => {}, // We still can emit the ZIR. - - .in_progress => unreachable, - .outdated => unreachable, - - .sema_failure, - .sema_failure_retryable, - .dependency_failure, - => if (self.old_module.failed_decls.get(ir_decl)) |err_msg| { - const fail_inst = try self.arena.allocator.create(Inst.UnOp); - fail_inst.* = .{ - .base = .{ - .src = ir_decl.src(), - .tag = .compileerror, - }, - .positionals = .{ - .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, err_msg.msg); - - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = ir_decl.src(), - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = err_msg.msg, - }, - .kw_args = .{}, - }; - break :blk &str_inst.base; - }, - }, - .kw_args = .{}, - }; - const decl = try self.arena.allocator.create(Decl); - decl.* = .{ - .name = mem.spanZ(ir_decl.name), - .contents_hash = undefined, - .inst = &fail_inst.base, - }; - try self.decls.append(self.allocator, decl); - continue; - }, - } - if (self.old_module.export_owners.get(ir_decl)) |exports| { - for (exports) |module_export| { - const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); - const export_inst = try self.arena.allocator.create(Inst.Export); - export_inst.* = .{ - .base = .{ - .src = module_export.src, - .tag = Inst.Export.base_tag, - }, - .positionals = .{ - .symbol_name = symbol_name.inst, - .decl_name = mem.spanZ(module_export.exported_decl.name), - }, - .kw_args = .{}, - }; - _ = try self.emitUnnamedDecl(&export_inst.base); - } - } - const new_decl = try self.emitTypedValue(ir_decl.src(), ir_decl.typed_value.most_recent.typed_value); - new_decl.name = try self.arena.allocator.dupe(u8, mem.spanZ(ir_decl.name)); - } - } - - const ZirBody = struct { - inst_table: *std.AutoHashMap(*ir.Inst, *Inst), - instructions: *std.ArrayList(*Inst), - }; - - fn resolveInst(self: *EmitZIR, new_body: ZirBody, inst: *ir.Inst) !*Inst { - if (inst.cast(ir.Inst.Constant)) |const_inst| { - const new_inst = if (const_inst.val.castTag(.function)) |func_pl| blk: { - const owner_decl = func_pl.data.owner_decl; - break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); - } else if (const_inst.val.castTag(.decl_ref)) |declref| blk: { - const decl_ref = try self.emitDeclRef(inst.src, declref.data); - try new_body.instructions.append(decl_ref); - break :blk decl_ref; - } else if (const_inst.val.castTag(.variable)) |var_pl| blk: { - const owner_decl = var_pl.data.owner_decl; - break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); - } else blk: { - break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; - }; - _ = try new_body.inst_table.put(inst, new_inst); - return new_inst; - } else { - return new_body.inst_table.get(inst).?; - } - } - - fn emitDeclVal(self: *EmitZIR, src: usize, decl_name: []const u8) !*Inst { - const declval = try self.arena.allocator.create(Inst.DeclVal); - declval.* = .{ - .base = .{ - .src = src, - .tag = Inst.DeclVal.base_tag, - }, - .positionals = .{ .name = try self.arena.allocator.dupe(u8, decl_name) }, - .kw_args = .{}, - }; - return &declval.base; - } - - fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Decl { - const big_int_space = try self.arena.allocator.create(Value.BigIntSpace); - const int_inst = try self.arena.allocator.create(Inst.Int); - int_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Int.base_tag, - }, - .positionals = .{ - .int = val.toBigInt(big_int_space), - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&int_inst.base); - } - - fn emitDeclRef(self: *EmitZIR, src: usize, module_decl: *IrModule.Decl) !*Inst { - const declref_inst = try self.arena.allocator.create(Inst.DeclRef); - declref_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.DeclRef.base_tag, - }, - .positionals = .{ - .name = mem.spanZ(module_decl.name), - }, - .kw_args = .{}, - }; - 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.state) { - .queued => unreachable, - .in_progress => unreachable, - .inline_only => unreachable, - .success => { - try self.emitBody(module_fn.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.UnOp); - fail_inst.* = .{ - .base = .{ - .src = src, - .tag = .compileerror, - }, - .positionals = .{ - .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, err_msg.msg); - - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = msg_str, - }, - .kw_args = .{}, - }; - break :blk &str_inst.base; - }, - }, - .kw_args = .{}, - }; - try instructions.append(&fail_inst.base); - }, - .dependency_failure => { - const fail_inst = try self.arena.allocator.create(Inst.UnOp); - fail_inst.* = .{ - .base = .{ - .src = src, - .tag = .compileerror, - }, - .positionals = .{ - .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, "depends on another failed Decl"); - - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = msg_str, - }, - .kw_args = .{}, - }; - break :blk &str_inst.base; - }, - }, - .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 = .{ - .is_inline = module_fn.state == .inline_only, - }, - }; - 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.castTag(.decl_ref)) |decl_ref| { - const decl = decl_ref.data; - return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); - } else if (typed_value.val.castTag(.variable)) |variable| { - return self.emitTypedValue(src, .{ - .ty = typed_value.ty, - .val = variable.data.init, - }); - } - if (typed_value.val.isUndef()) { - const as_inst = try self.arena.allocator.create(Inst.BinOp); - as_inst.* = .{ - .base = .{ - .tag = .as, - .src = src, - }, - .positionals = .{ - .lhs = (try self.emitType(src, typed_value.ty)).inst, - .rhs = (try self.emitPrimitive(src, .@"undefined")).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&as_inst.base); - } - switch (typed_value.ty.zigTypeTag()) { - .Pointer => { - const ptr_elem_type = typed_value.ty.elemType(); - switch (ptr_elem_type.zigTypeTag()) { - .Array => { - // TODO more checks to make sure this can be emitted as a string literal - //const array_elem_type = ptr_elem_type.elemType(); - //if (array_elem_type.eql(Type.initTag(.u8)) and - // ptr_elem_type.hasSentinel(Value.initTag(.zero))) - //{ - //} - const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) { - error.AnalysisFail => unreachable, - else => |e| return e, - }; - return self.emitStringLiteral(src, bytes); - }, - else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {s}", .{@tagName(t)}), - } - }, - .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val), - .Int => { - const as_inst = try self.arena.allocator.create(Inst.BinOp); - as_inst.* = .{ - .base = .{ - .tag = .as, - .src = src, - }, - .positionals = .{ - .lhs = (try self.emitType(src, typed_value.ty)).inst, - .rhs = (try self.emitComptimeIntVal(src, typed_value.val)).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&as_inst.base); - }, - .Type => { - const ty = try typed_value.val.toType(&self.arena.allocator); - return self.emitType(src, ty); - }, - .Fn => { - const module_fn = typed_value.val.castTag(.function).?.data; - return self.emitFn(module_fn, src, typed_value.ty); - }, - .Array => { - // TODO more checks to make sure this can be emitted as a string literal - //const array_elem_type = ptr_elem_type.elemType(); - //if (array_elem_type.eql(Type.initTag(.u8)) and - // ptr_elem_type.hasSentinel(Value.initTag(.zero))) - //{ - //} - const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) { - error.AnalysisFail => unreachable, - else => |e| return e, - }; - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = bytes, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&str_inst.base); - }, - .Void => return self.emitPrimitive(src, .void_value), - .Bool => if (typed_value.val.toBool()) - return self.emitPrimitive(src, .@"true") - else - return self.emitPrimitive(src, .@"false"), - .EnumLiteral => { - const enum_literal = typed_value.val.castTag(.enum_literal).?; - const inst = try self.arena.allocator.create(Inst.Str); - inst.* = .{ - .base = .{ - .src = src, - .tag = .enum_literal, - }, - .positionals = .{ - .bytes = enum_literal.data, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inst.base); - }, - else => |t| std.debug.panic("TODO implement emitTypedValue for {s}", .{@tagName(t)}), - } - } - - 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 = .{ - .src = src, - .tag = tag, - }, - .positionals = .{}, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitUnOp( - self: *EmitZIR, - src: usize, - new_body: ZirBody, - old_inst: *ir.Inst.UnOp, - tag: Inst.Tag, - ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.UnOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .operand = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitBinOp( - self: *EmitZIR, - src: usize, - new_body: ZirBody, - old_inst: *ir.Inst.BinOp, - tag: Inst.Tag, - ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.BinOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .lhs = try self.resolveInst(new_body, old_inst.lhs), - .rhs = try self.resolveInst(new_body, old_inst.rhs), - }, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitCast( - self: *EmitZIR, - src: usize, - new_body: ZirBody, - old_inst: *ir.Inst.UnOp, - tag: Inst.Tag, - ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.BinOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .lhs = (try self.emitType(old_inst.base.src, old_inst.base.ty)).inst, - .rhs = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitBody( - self: *EmitZIR, - body: ir.Body, - inst_table: *std.AutoHashMap(*ir.Inst, *Inst), - instructions: *std.ArrayList(*Inst), - ) Allocator.Error!void { - const new_body = ZirBody{ - .inst_table = inst_table, - .instructions = instructions, - }; - for (body.instructions) |inst| { - const new_inst = switch (inst.tag) { - .constant => unreachable, // excluded from function bodies - - .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"), - .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint), - .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull), - .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), - .iserr => try self.emitUnOp(inst.src, new_body, inst.castTag(.iserr).?, .iserr), - .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), - .store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store), - .cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt), - .cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte), - .cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq), - .cmp_gte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gte).?, .cmp_gte), - .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt), - .cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq), - .booland => try self.emitBinOp(inst.src, new_body, inst.castTag(.booland).?, .booland), - .boolor => try self.emitBinOp(inst.src, new_body, inst.castTag(.boolor).?, .boolor), - .bitand => try self.emitBinOp(inst.src, new_body, inst.castTag(.bitand).?, .bitand), - .bitor => try self.emitBinOp(inst.src, new_body, inst.castTag(.bitor).?, .bitor), - .xor => try self.emitBinOp(inst.src, new_body, inst.castTag(.xor).?, .xor), - - .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast), - .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), - .floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast), - - .alloc => blk: { - const new_inst = try self.arena.allocator.create(Inst.UnOp); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = .alloc, - }, - .positionals = .{ - .operand = (try self.emitType(inst.src, inst.ty)).inst, - }, - .kw_args = .{}, - }; - 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); - - try self.block_table.put(old_inst, new_inst); - - var block_body = std.ArrayList(*Inst).init(self.allocator); - defer block_body.deinit(); - - try self.emitBody(old_inst.body, inst_table, &block_body); - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Block.base_tag, - }, - .positionals = .{ - .body = .{ .instructions = block_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; - - break :blk &new_inst.base; - }, - - .loop => blk: { - const old_inst = inst.castTag(.loop).?; - const new_inst = try self.arena.allocator.create(Inst.Loop); - - try self.loop_table.put(old_inst, new_inst); - - var loop_body = std.ArrayList(*Inst).init(self.allocator); - defer loop_body.deinit(); - - try self.emitBody(old_inst.body, inst_table, &loop_body); - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Loop.base_tag, - }, - .positionals = .{ - .body = .{ .instructions = loop_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; - - break :blk &new_inst.base; - }, - - .brvoid => blk: { - const old_inst = inst.cast(ir.Inst.BrVoid).?; - const new_block = self.block_table.get(old_inst.block).?; - const new_inst = try self.arena.allocator.create(Inst.BreakVoid); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.BreakVoid.base_tag, - }, - .positionals = .{ - .block = new_block, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .br => blk: { - const old_inst = inst.castTag(.br).?; - const new_block = self.block_table.get(old_inst.block).?; - const new_inst = try self.arena.allocator.create(Inst.Break); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Break.base_tag, - }, - .positionals = .{ - .block = new_block, - .operand = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .call => blk: { - const old_inst = inst.castTag(.call).?; - const new_inst = try self.arena.allocator.create(Inst.Call); - - const args = try self.arena.allocator.alloc(*Inst, old_inst.args.len); - for (args) |*elem, i| { - elem.* = try self.resolveInst(new_body, old_inst.args[i]); - } - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Call.base_tag, - }, - .positionals = .{ - .func = try self.resolveInst(new_body, old_inst.func), - .args = args, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .assembly => blk: { - const old_inst = inst.castTag(.assembly).?; - const new_inst = try self.arena.allocator.create(Inst.Asm); - - const inputs = try self.arena.allocator.alloc(*Inst, old_inst.inputs.len); - for (inputs) |*elem, i| { - elem.* = (try self.emitStringLiteral(inst.src, old_inst.inputs[i])).inst; - } - - const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.clobbers.len); - for (clobbers) |*elem, i| { - elem.* = (try self.emitStringLiteral(inst.src, old_inst.clobbers[i])).inst; - } - - const args = try self.arena.allocator.alloc(*Inst, old_inst.args.len); - for (args) |*elem, i| { - elem.* = try self.resolveInst(new_body, old_inst.args[i]); - } - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Asm.base_tag, - }, - .positionals = .{ - .asm_source = (try self.emitStringLiteral(inst.src, old_inst.asm_source)).inst, - .return_type = (try self.emitType(inst.src, inst.ty)).inst, - }, - .kw_args = .{ - .@"volatile" = old_inst.is_volatile, - .output = if (old_inst.output) |o| - (try self.emitStringLiteral(inst.src, o)).inst - else - null, - .inputs = inputs, - .clobbers = clobbers, - .args = args, - }, - }; - break :blk &new_inst.base; - }, - - .condbr => blk: { - const old_inst = inst.castTag(.condbr).?; - - var then_body = std.ArrayList(*Inst).init(self.allocator); - var else_body = std.ArrayList(*Inst).init(self.allocator); - - 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, - .tag = Inst.CondBr.base_tag, - }, - .positionals = .{ - .condition = try self.resolveInst(new_body, old_inst.condition), - .then_body = .{ .instructions = then_body.toOwnedSlice() }, - .else_body = .{ .instructions = else_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - .switchbr => blk: { - const old_inst = inst.castTag(.switchbr).?; - const cases = try self.arena.allocator.alloc(Inst.SwitchBr.Case, old_inst.cases.len); - const new_inst = try self.arena.allocator.create(Inst.SwitchBr); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.SwitchBr.base_tag, - }, - .positionals = .{ - .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr), - .cases = cases, - .items = &[_]*Inst{}, // TODO this should actually be populated - .else_body = undefined, // populated below - }, - .kw_args = .{}, - }; - - var body_tmp = std.ArrayList(*Inst).init(self.allocator); - defer body_tmp.deinit(); - - for (old_inst.cases) |*case, i| { - body_tmp.items.len = 0; - - const case_deaths = try self.arena.allocator.alloc(*Inst, old_inst.caseDeaths(i).len); - for (old_inst.caseDeaths(i)) |death, j| { - case_deaths[j] = try self.resolveInst(new_body, death); - } - try self.body_metadata.put(&cases[i].body, .{ .deaths = case_deaths }); - - try self.emitBody(case.body, inst_table, &body_tmp); - const item = (try self.emitTypedValue(inst.src, .{ - .ty = old_inst.target_ptr.ty.elemType(), - .val = case.item, - })).inst; - - cases[i] = .{ - .item = item, - .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, - }; - } - { // else - const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len); - for (old_inst.elseDeaths()) |death, j| { - else_deaths[j] = try self.resolveInst(new_body, death); - } - try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths }); - - body_tmp.items.len = 0; - try self.emitBody(old_inst.else_body, inst_table, &body_tmp); - new_inst.positionals.else_body = .{ - .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items), - }; - } - - break :blk &new_inst.base; - }, - .varptr => @panic("TODO"), - }; - try self.metadata.put(new_inst, .{ - .deaths = inst.deaths, - .addr = @ptrToInt(inst), - }); - try instructions.append(new_inst); - try inst_table.put(inst, new_inst); - } - } - - fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Decl { - switch (ty.tag()) { - .i8 => return self.emitPrimitive(src, .i8), - .u8 => return self.emitPrimitive(src, .u8), - .i16 => return self.emitPrimitive(src, .i16), - .u16 => return self.emitPrimitive(src, .u16), - .i32 => return self.emitPrimitive(src, .i32), - .u32 => return self.emitPrimitive(src, .u32), - .i64 => return self.emitPrimitive(src, .i64), - .u64 => return self.emitPrimitive(src, .u64), - .isize => return self.emitPrimitive(src, .isize), - .usize => return self.emitPrimitive(src, .usize), - .c_short => return self.emitPrimitive(src, .c_short), - .c_ushort => return self.emitPrimitive(src, .c_ushort), - .c_int => return self.emitPrimitive(src, .c_int), - .c_uint => return self.emitPrimitive(src, .c_uint), - .c_long => return self.emitPrimitive(src, .c_long), - .c_ulong => return self.emitPrimitive(src, .c_ulong), - .c_longlong => return self.emitPrimitive(src, .c_longlong), - .c_ulonglong => return self.emitPrimitive(src, .c_ulonglong), - .c_longdouble => return self.emitPrimitive(src, .c_longdouble), - .c_void => return self.emitPrimitive(src, .c_void), - .f16 => return self.emitPrimitive(src, .f16), - .f32 => return self.emitPrimitive(src, .f32), - .f64 => return self.emitPrimitive(src, .f64), - .f128 => return self.emitPrimitive(src, .f128), - .anyerror => return self.emitPrimitive(src, .anyerror), - else => switch (ty.zigTypeTag()) { - .Bool => return self.emitPrimitive(src, .bool), - .Void => return self.emitPrimitive(src, .void), - .NoReturn => return self.emitPrimitive(src, .noreturn), - .Type => return self.emitPrimitive(src, .type), - .ComptimeInt => return self.emitPrimitive(src, .comptime_int), - .ComptimeFloat => return self.emitPrimitive(src, .comptime_float), - .Fn => { - const param_types = try self.allocator.alloc(Type, ty.fnParamLen()); - defer self.allocator.free(param_types); - - ty.fnParamTypes(param_types); - const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len); - for (param_types) |param_type, i| { - emitted_params[i] = (try self.emitType(src, param_type)).inst; - } - - const fntype_inst = try self.arena.allocator.create(Inst.FnType); - fntype_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.FnType.base_tag, - }, - .positionals = .{ - .param_types = emitted_params, - .return_type = (try self.emitType(src, ty.fnReturnType())).inst, - }, - .kw_args = .{ - .cc = ty.fnCallingConvention(), - }, - }; - return self.emitUnnamedDecl(&fntype_inst.base); - }, - .Int => { - const info = ty.intInfo(self.old_module.getTarget()); - const signed = try self.emitPrimitive(src, switch (info.signedness) { - .signed => .@"true", - .unsigned => .@"false", - }); - const bits_val = try Value.Tag.int_u64.create(&self.arena.allocator, info.bits); - const bits = try self.emitComptimeIntVal(src, bits_val); - const inttype_inst = try self.arena.allocator.create(Inst.IntType); - inttype_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.IntType.base_tag, - }, - .positionals = .{ - .signed = signed.inst, - .bits = bits.inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inttype_inst.base); - }, - .Pointer => { - if (ty.isSinglePointer()) { - const inst = try self.arena.allocator.create(Inst.UnOp); - const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type; - inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .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}); - } - }, - .Optional => { - var buf: Type.Payload.ElemType = undefined; - const inst = try self.arena.allocator.create(Inst.UnOp); - inst.* = .{ - .base = .{ - .src = src, - .tag = .optional_type, - }, - .positionals = .{ - .operand = (try self.emitType(src, ty.optionalChild(&buf))).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inst.base); - }, - .Array => { - var len_pl = Value.Payload.U64{ - .base = .{ .tag = .int_u64 }, - .data = ty.arrayLen(), - }; - const len = Value.initPayload(&len_pl.base); - - const inst = if (ty.sentinel()) |sentinel| blk: { - const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel); - inst.* = .{ - .base = .{ - .src = src, - .tag = .array_type, - }, - .positionals = .{ - .len = (try self.emitTypedValue(src, .{ - .ty = Type.initTag(.usize), - .val = len, - })).inst, - .sentinel = (try self.emitTypedValue(src, .{ - .ty = ty.elemType(), - .val = sentinel, - })).inst, - .elem_type = (try self.emitType(src, ty.elemType())).inst, - }, - .kw_args = .{}, - }; - break :blk &inst.base; - } else blk: { - const inst = try self.arena.allocator.create(Inst.BinOp); - inst.* = .{ - .base = .{ - .src = src, - .tag = .array_type, - }, - .positionals = .{ - .lhs = (try self.emitTypedValue(src, .{ - .ty = Type.initTag(.usize), - .val = len, - })).inst, - .rhs = (try self.emitType(src, ty.elemType())).inst, - }, - .kw_args = .{}, - }; - break :blk &inst.base; - }; - return self.emitUnnamedDecl(inst); - }, - else => std.debug.panic("TODO implement emitType for {}", .{ty}), - }, - } - } - - fn autoName(self: *EmitZIR) ![]u8 { - while (true) { - const proposed_name = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${d}", .{self.next_auto_name}); - self.next_auto_name += 1; - const gop = try self.names.getOrPut(proposed_name); - if (!gop.found_existing) { - gop.entry.value = {}; - return proposed_name; - } - } - } - - fn emitPrimitive(self: *EmitZIR, src: usize, tag: Inst.Primitive.Builtin) !*Decl { - const gop = try self.primitive_table.getOrPut(tag); - if (!gop.found_existing) { - const primitive_inst = try self.arena.allocator.create(Inst.Primitive); - primitive_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Primitive.base_tag, - }, - .positionals = .{ - .tag = tag, - }, - .kw_args = .{}, - }; - gop.entry.value = try self.emitUnnamedDecl(&primitive_inst.base); - } - return gop.entry.value; - } - - fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Decl { - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = str, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&str_inst.base); - } - - fn emitUnnamedDecl(self: *EmitZIR, inst: *Inst) !*Decl { - const decl = try self.arena.allocator.create(Decl); - decl.* = .{ - .name = try self.autoName(), - .contents_hash = undefined, - .inst = inst, - }; - try self.decls.append(self.allocator, decl); - return decl; - } -}; - /// For debugging purposes, like dumpFn but for unanalyzed zir blocks pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8, instructions: []*Inst) !void { var fib = std.heap.FixedBufferAllocator.init(&[_]u8{}); var module = Module{ - .decls = &[_]*Decl{}, + .decls = &[_]*Module.Decl{}, .arena = std.heap.ArenaAllocator.init(&fib.allocator), .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(&fib.allocator), - .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(&fib.allocator), + .body_metadata = std.AutoHashMap(*Body, Module.BodyMetaData).init(&fib.allocator), }; var write = Writer{ .module = &module, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 6302ab29e4..36eb5f4239 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -63,7 +63,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), .declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?), .declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?), - .declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?), .ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?), .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?), .ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?), @@ -166,7 +165,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! } } -pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Module.Body) !void { +pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Body) !void { const tracy = trace(@src()); defer tracy.end(); @@ -183,7 +182,7 @@ pub fn analyzeBodyValueAsType( mod: *Module, block_scope: *Scope.Block, zir_result_inst: *zir.Inst, - body: zir.Module.Body, + body: zir.Body, ) !Type { try analyzeBody(mod, block_scope, body); const result_inst = block_scope.inst_table.get(zir_result_inst).?; @@ -191,84 +190,6 @@ pub fn analyzeBodyValueAsType( return val.toType(block_scope.base.arena()); } -pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { - var decl_scope: Scope.DeclAnalysis = .{ - .decl = decl, - .arena = std.heap.ArenaAllocator.init(mod.gpa), - }; - errdefer decl_scope.arena.deinit(); - - decl.analysis = .in_progress; - - const typed_value = try analyzeConstInst(mod, &decl_scope.base, src_decl.inst); - const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); - - var prev_type_has_bits = false; - var type_changed = true; - - if (decl.typedValueManaged()) |tvm| { - prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); - type_changed = !tvm.typed_value.ty.eql(typed_value.ty); - - tvm.deinit(mod.gpa); - } - - arena_state.* = decl_scope.arena.state; - decl.typed_value = .{ - .most_recent = .{ - .typed_value = typed_value, - .arena = arena_state, - }, - }; - decl.analysis = .complete; - decl.generation = mod.generation; - if (typed_value.ty.hasCodeGenBits()) { - // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. - try mod.comp.bin_file.allocateDeclIndexes(decl); - try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl }); - } else if (prev_type_has_bits) { - mod.comp.bin_file.freeDecl(decl); - } - - return type_changed; -} - -pub fn resolveZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const zir_module = mod.root_scope.cast(Scope.ZIRModule).?; - const entry = zir_module.contents.module.findDecl(src_decl.name).?; - return resolveZirDeclHavingIndex(mod, scope, src_decl, entry.index); -} - -fn resolveZirDeclHavingIndex(mod: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { - const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); - const decl = mod.decl_table.get(name_hash).?; - decl.src_index = src_index; - try mod.ensureDeclAnalyzed(decl); - return decl; -} - -/// Declares a dependency on the decl. -fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const decl = try resolveZirDecl(mod, scope, src_decl); - switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - .outdated => unreachable, - - .dependency_failure, - .sema_failure, - .sema_failure_retryable, - .codegen_failure, - .codegen_failure_retryable, - => return error.AnalysisFail, - - .complete => {}, - } - return decl; -} - pub fn resolveInst(mod: *Module, scope: *Scope, zir_inst: *zir.Inst) InnerError!*Inst { const block = scope.cast(Scope.Block).?; return block.inst_table.get(zir_inst).?; // Instruction does not dominate all uses! @@ -640,22 +561,28 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In } fn analyzeInstCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst { - std.debug.print("| ", .{}); - for (inst.positionals.to_log) |item, i| { - const to_log = try resolveInst(mod, scope, item); - if (to_log.value()) |val| { - std.debug.print("{}", .{val}); - } else { - std.debug.print("(runtime value)", .{}); - } - if (i != inst.positionals.to_log.len - 1) std.debug.print(", ", .{}); - } - std.debug.print("\n", .{}); - if (!inst.kw_args.seen) { + var managed = mod.compile_log_text.toManaged(mod.gpa); + defer mod.compile_log_text = managed.moveToUnmanaged(); + const writer = managed.writer(); - // so that we do not give multiple compile errors if it gets evaled twice - inst.kw_args.seen = true; - try mod.failCompileLog(scope, inst.base.src); + for (inst.positionals.to_log) |arg_inst, i| { + if (i != 0) try writer.print(", ", .{}); + + const arg = try resolveInst(mod, scope, arg_inst); + if (arg.value()) |val| { + try writer.print("@as({}, {})", .{ arg.ty, val }); + } else { + try writer.print("@as({}, [runtime value])", .{arg.ty}); + } + } + try writer.print("\n", .{}); + + const gop = try mod.compile_log_decls.getOrPut(mod.gpa, scope.ownerDecl().?); + if (!gop.found_existing) { + gop.entry.value = .{ + .file_scope = scope.getFileScope(), + .byte_offset = inst.base.src, + }; } return mod.constVoid(scope, inst.base.src); } @@ -705,7 +632,8 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -732,7 +660,8 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .label = null, @@ -744,13 +673,14 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c try analyzeBody(mod, &child_block, inst.positionals.body); - try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items); + // Move the analyzed instructions into the parent block arena. + const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); + try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); - // comptime blocks won't generate any runtime values - if (child_block.instructions.items.len == 0) - return mod.constVoid(scope, inst.base.src); - - return parent_block.instructions.items[parent_block.instructions.items.len - 1]; + // The result of a flat block is the last instruction. + const zir_inst_list = inst.positionals.body.instructions; + const last_zir_inst = zir_inst_list[zir_inst_list.len - 1]; + return resolveInst(mod, scope, last_zir_inst); } fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst { @@ -775,7 +705,8 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, // TODO @as here is working around a stage1 miscompilation bug :( @@ -890,22 +821,15 @@ fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); - return mod.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); + return mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl); } fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); - const decl = try analyzeDeclVal(mod, scope, inst); - const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); - return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); -} - -fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const decl = inst.positionals.decl; - return mod.analyzeDeclRef(scope, inst.base.src, decl); + const decl_ref = try mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl); + // TODO look into avoiding the call to analyzeDeref here + return mod.analyzeDeref(scope, inst.base.src, decl_ref, inst.base.src); } fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { @@ -1032,9 +956,8 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .parent = null, .inst_table = &inst_table, .func = module_fn, - // Note that we pass the caller's Decl, not the callee. This causes - // compile errors to be attached (correctly) to the caller's Decl. - .decl = scope.decl().?, + .owner_decl = scope.ownerDecl().?, + .src_decl = module_fn.owner_decl, .instructions = .{}, .arena = scope.arena(), .label = null, @@ -1069,7 +992,7 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError! .state = if (fn_inst.kw_args.is_inline) .inline_only else .queued, .zir = fn_inst.positionals.body, .body = undefined, - .owner_decl = scope.decl().?, + .owner_decl = scope.ownerDecl().?, }; return mod.constInst(scope, fn_inst.base.src, .{ .ty = fn_type, @@ -1391,7 +1314,7 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr return mod.analyzeDeclRef(scope, fieldptr.base.src, decl); } - if (&container_scope.file_scope.base == mod.root_scope) { + if (container_scope.file_scope == mod.root_scope) { return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{s}'", .{field_name}); } else { return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{s}'", .{ child_type, field_name }); @@ -1606,7 +1529,8 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -2182,7 +2106,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -2196,7 +2121,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -2294,17 +2220,6 @@ fn analyzeBreak( } else unreachable; } -fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { - const decl_name = inst.positionals.name; - const zir_module = scope.namespace().cast(Scope.ZIRModule).?; - const src_decl = zir_module.contents.module.findDecl(decl_name) orelse - return mod.fail(scope, inst.base.src, "use of undeclared identifier '{s}'", .{decl_name}); - - const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl); - - return decl; -} - fn analyzeInstSimplePtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index f25f07adbf..cb5e9bebbb 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -36,7 +36,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("hello world with updates", linux_x64); - case.addError("", &[_][]const u8{"no entry point found"}); + case.addError("", &[_][]const u8{"error: no entry point found"}); // Incorrect return type case.addError( @@ -147,7 +147,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("hello world with updates", macosx_x64); - case.addError("", &[_][]const u8{"no entry point found"}); + case.addError("", &[_][]const u8{"error: no entry point found"}); // Incorrect return type case.addError( @@ -1243,24 +1243,46 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":3:9: error: redefinition of 'testing'"}); } - ctx.compileError("compileLog", linux_x64, - \\export fn _start() noreturn { - \\ const b = true; - \\ var f: u32 = 1; - \\ @compileLog(b, 20, f, x); - \\ @compileLog(1000); - \\ var bruh: usize = true; - \\ unreachable; - \\} - \\fn x() void {} - , &[_][]const u8{ - ":4:3: error: found compile log statement", - ":5:3: error: found compile log statement", - ":6:21: error: expected usize, found bool", - }); - // TODO if this is here it invalidates the compile error checker: - // "| true, 20, (runtime value), (function)" - // "| 1000" + + { + // TODO make the test harness support checking the compile log output too + var case = ctx.obj("@compileLog", linux_x64); + // The other compile error prevents emission of a "found compile log" statement. + case.addError( + \\export fn _start() noreturn { + \\ const b = true; + \\ var f: u32 = 1; + \\ @compileLog(b, 20, f, x); + \\ @compileLog(1000); + \\ var bruh: usize = true; + \\ unreachable; + \\} + \\export fn other() void { + \\ @compileLog(1234); + \\} + \\fn x() void {} + , &[_][]const u8{ + ":6:23: error: expected usize, found bool", + }); + + // Now only compile log statements remain. One per Decl. + case.addError( + \\export fn _start() noreturn { + \\ const b = true; + \\ var f: u32 = 1; + \\ @compileLog(b, 20, f, x); + \\ @compileLog(1000); + \\ unreachable; + \\} + \\export fn other() void { + \\ @compileLog(1234); + \\} + \\fn x() void {} + , &[_][]const u8{ + ":11:8: error: found compile log statement", + ":4:5: note: also here", + }); + } { var case = ctx.obj("extern variable has no type", linux_x64); From 629d3bea1b6aa7660364448cf4e0c045d931be52 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Jan 2021 23:24:00 -0700 Subject: [PATCH 3/4] stage2: slight cleanup of Module by calling astgen functions --- src/Module.zig | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index e612f8f759..d75c1d2a0d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1342,8 +1342,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { break :rl .{ .ty = var_type }; } else .none; - const src = tree.token_locs[init_node.firstToken()].start; - const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node); + const init_inst = try astgen.comptimeExpr(self, &gen_scope.base, init_result_loc, init_node); if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {}; } @@ -1392,12 +1391,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer type_scope.instructions.deinit(self.gpa); - const src = tree.token_locs[type_node.firstToken()].start; - const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node); + const var_type = try astgen.typeExpr(self, &type_scope.base, type_node); if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "var_type", decl.name, type_scope.instructions.items) catch {}; } From 8deb21c58a4e5f9f8805f6b1a2c9a1774c4a4df5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 17 Jan 2021 00:14:24 -0700 Subject: [PATCH 4/4] stage2: add compile error for label redefinition Also fix incorrectly destroying notes. This work is based on Vexu's patch in #7555. --- src/astgen.zig | 145 ++++++++++++++++++++++++++++++++++--------- test/stage2/test.zig | 13 +++- 2 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index 4631e46b5d..6c697897aa 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -442,6 +442,49 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block try blockExprStmts(mod, parent_scope, &block_node.base, block_node.statements()); } +fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void { + // 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) |prev_label| { + if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) { + const tree = parent_scope.tree(); + const label_src = tree.token_locs[label].start; + const prev_label_src = tree.token_locs[prev_label.token].start; + + const label_name = try mod.identifierTokenString(parent_scope, label); + const msg = msg: { + const msg = try mod.errMsg( + parent_scope, + label_src, + "redefinition of label '{s}'", + .{label_name}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote( + parent_scope, + prev_label_src, + msg, + "previous definition is here", + .{}, + ); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(parent_scope, msg); + } + } + scope = gen_zir.parent; + }, + .local_val => scope = scope.cast(Scope.LocalVal).?.parent, + .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + else => return, + } + } +} + fn labeledBlockExpr( mod: *Module, parent_scope: *Scope, @@ -457,6 +500,8 @@ fn labeledBlockExpr( const tree = parent_scope.tree(); const src = tree.token_locs[block_node.lbrace].start; + try checkLabelRedefinition(mod, parent_scope, block_node.label); + // 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(); @@ -560,14 +605,30 @@ fn varDecl( .local_val => { const local_val = s.cast(Scope.LocalVal).?; if (mem.eql(u8, local_val.name, ident_name)) { - return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name}); + const msg = msg: { + const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ + ident_name, + }); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, local_val.inst.src, msg, "previous definition is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); } s = local_val.parent; }, .local_ptr => { const local_ptr = s.cast(Scope.LocalPtr).?; if (mem.eql(u8, local_ptr.name, ident_name)) { - return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name}); + const msg = msg: { + const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ + ident_name, + }); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, local_ptr.ptr.src, msg, "previous definition is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); } s = local_ptr.parent; }, @@ -1166,8 +1227,10 @@ fn orelseCatchExpr( return rlWrapPtr(mod, scope, rl, &block.base); } -/// 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. +/// 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 mod.identifierTokenString(scope, token1); const ident_name_2 = try mod.identifierTokenString(scope, token2); @@ -1514,6 +1577,10 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W } } + if (while_node.label) |label| { + try checkLabelRedefinition(mod, scope, label); + } + if (while_node.inline_token) |tok| return mod.failTok(scope, tok, "TODO inline while", .{}); @@ -1649,7 +1716,16 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W return &while_block.base; } -fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) InnerError!*zir.Inst { +fn forExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + for_node: *ast.Node.For, +) InnerError!*zir.Inst { + if (for_node.label) |label| { + try checkLabelRedefinition(mod, scope, label); + } + if (for_node.inline_token) |tok| return mod.failTok(scope, tok, "TODO inline for", .{}); @@ -1928,14 +2004,17 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node // Check for else/_ prong, those are handled last. if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { if (else_src) |src| { - const msg = try mod.errMsg( - scope, - case_src, - "multiple else prongs in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, src, msg, "previous else prong is here", .{}); + const msg = msg: { + const msg = try mod.errMsg( + scope, + case_src, + "multiple else prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous else prong is here", .{}); + break :msg msg; + }; return mod.failWithOwnedErrorMsg(scope, msg); } else_src = case_src; @@ -1945,14 +2024,17 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) { if (underscore_src) |src| { - const msg = try mod.errMsg( - scope, - case_src, - "multiple '_' prongs in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); + const msg = msg: { + const msg = try mod.errMsg( + scope, + case_src, + "multiple '_' prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); + break :msg msg; + }; return mod.failWithOwnedErrorMsg(scope, msg); } underscore_src = case_src; @@ -1962,15 +2044,18 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node if (else_src) |some_else| { if (underscore_src) |some_underscore| { - const msg = try mod.errMsg( - scope, - switch_src, - "else and '_' prong in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, some_else, msg, "else prong is here", .{}); - try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); + const msg = msg: { + const msg = try mod.errMsg( + scope, + switch_src, + "else and '_' prong in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, some_else, msg, "else prong is here", .{}); + try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); + break :msg msg; + }; return mod.failWithOwnedErrorMsg(scope, msg); } } diff --git a/test/stage2/test.zig b/test/stage2/test.zig index cb5e9bebbb..8f05c0fd23 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1234,7 +1234,10 @@ pub fn addCases(ctx: *TestContext) !void { \\ var i: u32 = 10; \\ unreachable; \\} - , &[_][]const u8{":3:9: error: redefinition of 'i'"}); + , &[_][]const u8{ + ":3:9: error: redefinition of 'i'", + ":2:9: note: previous definition is here", + }); case.addError( \\var testing: i64 = 10; \\export fn _start() noreturn { @@ -1409,6 +1412,14 @@ pub fn addCases(ctx: *TestContext) !void { \\ foo: for ("foo") |_| {} \\} , &[_][]const u8{":2:5: error: unused for label"}); + case.addError( + \\comptime { + \\ blk: {blk: {}} + \\} + , &[_][]const u8{ + ":2:11: error: redefinition of label 'blk'", + ":2:5: note: previous definition is here", + }); } {