From 424e6ac54b0f8bbfb43f24e28c71ac72169f3719 Mon Sep 17 00:00:00 2001 From: mlugg Date: Wed, 28 May 2025 00:31:16 +0100 Subject: compiler: minor refactors to ZCU linking * The `codegen_nav`, `codegen_func`, `codegen_type` tasks are renamed to `link_nav`, `link_func`, and `link_type`, to more accurately reflect their purpose of sending data to the *linker*. Currently, `link_func` remains responsible for codegen; this will change in an upcoming commit. * Don't go on a pointless detour through `PerThread` when linking ZCU functions/`Nav`s; so, the `linkerUpdateNav` etc logic now lives in `link.zig`. Currently, `linkerUpdateFunc` is an exception, because it has broader responsibilities including codegen, but this will be solved in an upcoming commit. --- src/Compilation.zig | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 81d150b03f..69cb2c4d6f 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -848,17 +848,18 @@ pub const RcIncludes = enum { const Job = union(enum) { /// Corresponds to the task in `link.Task`. /// Only needed for backends that haven't yet been updated to not race against Sema. - codegen_nav: InternPool.Nav.Index, + link_nav: InternPool.Nav.Index, /// Corresponds to the task in `link.Task`. + /// TODO: this is currently also responsible for performing codegen. /// Only needed for backends that haven't yet been updated to not race against Sema. - codegen_func: link.Task.CodegenFunc, + link_func: link.Task.CodegenFunc, /// Corresponds to the task in `link.Task`. /// Only needed for backends that haven't yet been updated to not race against Sema. - codegen_type: InternPool.Index, + link_type: InternPool.Index, update_line_number: InternPool.TrackedInst.Index, /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. - /// If the unit is a function, a `codegen_func` job will then be queued. + /// If the unit is a test function, an `analyze_func` job will then be queued. analyze_comptime_unit: InternPool.AnalUnit, /// This function must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. @@ -879,13 +880,13 @@ const Job = union(enum) { return switch (tag) { // Prioritize functions so that codegen can get to work on them on a // separate thread, while Sema goes back to its own work. - .resolve_type_fully, .analyze_func, .codegen_func => 0, + .resolve_type_fully, .analyze_func, .link_func => 0, else => 1, }; } comptime { // Job dependencies - assert(stage(.resolve_type_fully) <= stage(.codegen_func)); + assert(stage(.resolve_type_fully) <= stage(.link_func)); } }; @@ -4552,7 +4553,7 @@ pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void { fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { switch (job) { - .codegen_nav => |nav_index| { + .link_nav => |nav_index| { const zcu = comp.zcu.?; const nav = zcu.intern_pool.getNav(nav_index); if (nav.analysis != null) { @@ -4562,16 +4563,16 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { } } assert(nav.status == .fully_resolved); - comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); + comp.dispatchLinkTask(tid, .{ .link_nav = nav_index }); }, - .codegen_func => |func| { - comp.dispatchCodegenTask(tid, .{ .codegen_func = func }); + .link_func => |func| { + comp.dispatchLinkTask(tid, .{ .link_func = func }); }, - .codegen_type => |ty| { - comp.dispatchCodegenTask(tid, .{ .codegen_type = ty }); + .link_type => |ty| { + comp.dispatchLinkTask(tid, .{ .link_type = ty }); }, .update_line_number => |ti| { - comp.dispatchCodegenTask(tid, .{ .update_line_number = ti }); + comp.dispatchLinkTask(tid, .{ .update_line_number = ti }); }, .analyze_func => |func| { const named_frame = tracy.namedFrame("analyze_func"); @@ -4665,7 +4666,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { /// The reason for the double-queue here is that the first queue ensures any /// resolve_type_fully tasks are complete before this dispatch function is called. -fn dispatchCodegenTask(comp: *Compilation, tid: usize, link_task: link.Task) void { +fn dispatchLinkTask(comp: *Compilation, tid: usize, link_task: link.Task) void { if (comp.separateCodegenThreadOk()) { comp.queueLinkTasks(&.{link_task}); } else { -- cgit v1.2.3 From 3743c3e39c6bb645db7403fd446953d43ac7c7dc Mon Sep 17 00:00:00 2001 From: mlugg Date: Wed, 28 May 2025 06:36:47 +0100 Subject: compiler: slightly untangle LLVM from the linkers The main goal of this commit is to make it easier to decouple codegen from the linkers by being able to do LLVM codegen without going through the `link.File`; however, this ended up being a nice refactor anyway. Previously, every linker stored an optional `llvm.Object`, which was populated when using LLVM for the ZCU *and* linking an output binary; and `Zcu` also stored an optional `llvm.Object`, which was used only when we needed LLVM for the ZCU (e.g. for `-femit-llvm-bc`) but were not emitting a binary. This situation was incredibly silly. It meant there were N+1 places the LLVM object might be instead of just 1, and it meant that every linker had to start a bunch of methods by checking for an LLVM object, and just dispatching to the corresponding method on *it* instead if it was not `null`. Instead, we now always store the LLVM object on the `Zcu` -- which makes sense, because it corresponds to the object emitted by, well, the Zig Compilation Unit! The linkers now mostly don't make reference to LLVM. `Compilation` makes sure to emit the LLVM object if necessary before calling `flush`, so it is ready for the linker. Also, all of the `link.File` methods which act on the ZCU -- like `updateNav` -- now check for the LLVM object in `link.zig` instead of in every single individual linker implementation. Notably, the change to LLVM emit improves this rather ludicrous call chain in the `-fllvm -flld` case: * Compilation.flush * link.File.flush * link.Elf.flush * link.Elf.linkWithLLD * link.Elf.flushModule * link.emitLlvmObject * Compilation.emitLlvmObject * llvm.Object.emit Replacing it with this one: * Compilation.flush * llvm.Object.emit ...although we do currently still end up in `link.Elf.linkWithLLD` to do the actual linking. The logic for invoking LLD should probably also be unified at least somewhat; I haven't done that in this commit. --- src/Compilation.zig | 72 +++++++++++-------------- src/Zcu.zig | 15 +----- src/Zcu/PerThread.zig | 21 ++++---- src/codegen/llvm.zig | 18 +++++++ src/codegen/spirv/Section.zig | 2 - src/link.zig | 85 +++++++++++++++++------------- src/link/C.zig | 4 +- src/link/Coff.zig | 114 +++++++++++----------------------------- src/link/Dwarf.zig | 2 +- src/link/Elf.zig | 40 ++++---------- src/link/Elf/ZigObject.zig | 8 +-- src/link/Goff.zig | 46 ++++++++-------- src/link/MachO.zig | 29 ++-------- src/link/MachO/DebugSymbols.zig | 2 +- src/link/MachO/ZigObject.zig | 8 +-- src/link/Plan9.zig | 10 ++-- src/link/SpirV.zig | 8 +-- src/link/Wasm.zig | 32 ++++------- src/link/Xcoff.zig | 46 ++++++++-------- src/target.zig | 2 +- 20 files changed, 227 insertions(+), 337 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 69cb2c4d6f..61201f39f4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2188,14 +2188,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }, } - // Handle the case of e.g. -fno-emit-bin -femit-llvm-ir. - if (options.emit_bin == null and (comp.verbose_llvm_ir != null or - comp.verbose_llvm_bc != null or - (use_llvm and comp.emit_asm != null) or - comp.emit_llvm_ir != null or - comp.emit_llvm_bc != null)) - { - if (opt_zcu) |zcu| zcu.llvm_object = try LlvmObject.create(arena, comp); + if (use_llvm) { + if (opt_zcu) |zcu| { + zcu.llvm_object = try LlvmObject.create(arena, comp); + } } break :comp comp; @@ -2945,6 +2941,33 @@ fn flush( tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) !void { + if (comp.zcu) |zcu| { + if (zcu.llvm_object) |llvm_object| { + // Emit the ZCU object from LLVM now; it's required to flush the output file. + // If there's an output file, it wants to decide where the LLVM object goes! + const zcu_obj_emit_loc: ?EmitLoc = if (comp.bin_file) |lf| .{ + .directory = null, + .basename = lf.zcu_object_sub_path.?, + } else null; + const sub_prog_node = prog_node.start("LLVM Emit Object", 0); + defer sub_prog_node.end(); + try llvm_object.emit(.{ + .pre_ir_path = comp.verbose_llvm_ir, + .pre_bc_path = comp.verbose_llvm_bc, + .bin_path = try resolveEmitLoc(arena, default_artifact_directory, zcu_obj_emit_loc), + .asm_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_asm), + .post_ir_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_ir), + .post_bc_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_bc), + + .is_debug = comp.root_mod.optimize_mode == .Debug, + .is_small = comp.root_mod.optimize_mode == .ReleaseSmall, + .time_report = comp.time_report, + .sanitize_thread = comp.config.any_sanitize_thread, + .fuzz = comp.config.any_fuzz, + .lto = comp.config.lto, + }); + } + } if (comp.bin_file) |lf| { // This is needed before reading the error flags. lf.flush(arena, tid, prog_node) catch |err| switch (err) { @@ -2952,13 +2975,8 @@ fn flush( error.OutOfMemory => return error.OutOfMemory, }; } - if (comp.zcu) |zcu| { try link.File.C.flushEmitH(zcu); - - if (zcu.llvm_object) |llvm_object| { - try emitLlvmObject(comp, arena, default_artifact_directory, null, llvm_object, prog_node); - } } } @@ -3233,34 +3251,6 @@ fn emitOthers(comp: *Compilation) void { } } -pub fn emitLlvmObject( - comp: *Compilation, - arena: Allocator, - default_artifact_directory: Cache.Path, - bin_emit_loc: ?EmitLoc, - llvm_object: LlvmObject.Ptr, - prog_node: std.Progress.Node, -) !void { - const sub_prog_node = prog_node.start("LLVM Emit Object", 0); - defer sub_prog_node.end(); - - try llvm_object.emit(.{ - .pre_ir_path = comp.verbose_llvm_ir, - .pre_bc_path = comp.verbose_llvm_bc, - .bin_path = try resolveEmitLoc(arena, default_artifact_directory, bin_emit_loc), - .asm_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_asm), - .post_ir_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_ir), - .post_bc_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_bc), - - .is_debug = comp.root_mod.optimize_mode == .Debug, - .is_small = comp.root_mod.optimize_mode == .ReleaseSmall, - .time_report = comp.time_report, - .sanitize_thread = comp.config.any_sanitize_thread, - .fuzz = comp.config.any_fuzz, - .lto = comp.config.lto, - }); -} - fn resolveEmitLoc( arena: Allocator, default_artifact_directory: Cache.Path, diff --git a/src/Zcu.zig b/src/Zcu.zig index 7223e5a55e..6a6a74e260 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -56,9 +56,8 @@ comptime { /// General-purpose allocator. Used for both temporary and long-term storage. gpa: Allocator, comp: *Compilation, -/// Usually, the LlvmObject is managed by linker code, however, in the case -/// that -fno-emit-bin is specified, the linker code never executes, so we -/// store the LlvmObject here. +/// If the ZCU is emitting an LLVM object (i.e. we are using the LLVM backend), then this is the +/// `LlvmObject` we are emitting to. llvm_object: ?LlvmObject.Ptr, /// Pointer to externally managed resource. @@ -267,16 +266,6 @@ resolved_references: ?std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) = n /// Reset to `false` at the start of each update in `Compilation.update`. skip_analysis_this_update: bool = false, -stage1_flags: packed struct { - have_winmain: bool = false, - have_wwinmain: bool = false, - have_winmain_crt_startup: bool = false, - have_wwinmain_crt_startup: bool = false, - have_dllmain_crt_startup: bool = false, - have_c_main: bool = false, - reserved: u2 = 0, -} = .{}, - test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty, global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 4b4ae98cb4..b10e6d7c41 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1784,8 +1784,12 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: *A }; } - if (comp.bin_file) |lf| { - lf.updateFunc(pt, func_index, air.*, liveness) catch |err| switch (err) { + if (zcu.llvm_object) |llvm_object| { + llvm_object.updateFunc(pt, func_index, air.*, liveness) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + }; + } else if (comp.bin_file) |lf| { + lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)), error.Overflow, error.RelocationNotByteAligned => { @@ -1798,10 +1802,6 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: *A // Not a retryable failure. }, }; - } else if (zcu.llvm_object) |llvm_object| { - llvm_object.updateFunc(pt, func_index, air.*, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - }; } } @@ -1877,7 +1877,6 @@ fn createFileRootStruct( try pt.scanNamespace(namespace_index, decls); try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { - if (zcu.comp.config.use_llvm) break :codegen_type; if (file.mod.?.strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); @@ -3309,10 +3308,10 @@ fn processExportsInner( .uav => {}, } - if (zcu.comp.bin_file) |lf| { - try zcu.handleUpdateExports(export_indices, lf.updateExports(pt, exported, export_indices)); - } else if (zcu.llvm_object) |llvm_object| { + if (zcu.llvm_object) |llvm_object| { try zcu.handleUpdateExports(export_indices, llvm_object.updateExports(pt, exported, export_indices)); + } else if (zcu.comp.bin_file) |lf| { + try zcu.handleUpdateExports(export_indices, lf.updateExports(pt, exported, export_indices)); } } @@ -4064,7 +4063,6 @@ fn recreateStructType( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { - if (zcu.comp.config.use_llvm) break :codegen_type; if (file.mod.?.strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); @@ -4157,7 +4155,6 @@ fn recreateUnionType( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { - if (zcu.comp.config.use_llvm) break :codegen_type; if (file.mod.?.strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 3fc6250d3f..2b39396c38 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1586,6 +1586,24 @@ pub const Object = struct { const global_index = self.nav_map.get(nav_index).?; const comp = zcu.comp; + // If we're on COFF and linking with LLD, the linker cares about our exports to determine the subsystem in use. + if (comp.bin_file != null and + comp.bin_file.?.tag == .coff and + zcu.comp.config.use_lld and + ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) + { + const flags = &comp.bin_file.?.cast(.coff).?.lld_export_flags; + for (export_indices) |export_index| { + const name = export_index.ptr(zcu).opts.name; + if (name.eqlSlice("main", ip)) flags.c_main = true; + if (name.eqlSlice("WinMain", ip)) flags.winmain = true; + if (name.eqlSlice("wWinMain", ip)) flags.wwinmain = true; + if (name.eqlSlice("WinMainCRTStartup", ip)) flags.winmain_crt_startup = true; + if (name.eqlSlice("wWinMainCRTStartup", ip)) flags.wwinmain_crt_startup = true; + if (name.eqlSlice("DllMainCRTStartup", ip)) flags.dllmain_crt_startup = true; + } + } + if (export_indices.len != 0) { return updateExportedGlobal(self, zcu, global_index, export_indices); } else { diff --git a/src/codegen/spirv/Section.zig b/src/codegen/spirv/Section.zig index 4fe12f999f..5c2a5fde62 100644 --- a/src/codegen/spirv/Section.zig +++ b/src/codegen/spirv/Section.zig @@ -386,8 +386,6 @@ test "SPIR-V Section emit() - string" { } test "SPIR-V Section emit() - extended mask" { - if (@import("builtin").zig_backend == .stage1) return error.SkipZigTest; - var section = Section{}; defer section.deinit(std.testing.allocator); diff --git a/src/link.zig b/src/link.zig index 7673b44e47..3270d10c87 100644 --- a/src/link.zig +++ b/src/link.zig @@ -19,7 +19,6 @@ const Zcu = @import("Zcu.zig"); const InternPool = @import("InternPool.zig"); const Type = @import("Type.zig"); const Value = @import("Value.zig"); -const LlvmObject = @import("codegen/llvm.zig").Object; const lldMain = @import("main.zig").lldMain; const Package = @import("Package.zig"); const dev = @import("dev.zig"); @@ -704,7 +703,9 @@ pub const File = struct { } /// May be called before or after updateExports for any given Nav. + /// Asserts that the ZCU is not using the LLVM backend. fn updateNav(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) UpdateNavError!void { + assert(base.comp.zcu.?.llvm_object == null); const nav = pt.zcu.intern_pool.getNav(nav_index); assert(nav.status == .fully_resolved); switch (base.tag) { @@ -721,7 +722,9 @@ pub const File = struct { TypeFailureReported, }; + /// Never called when LLVM is codegenning the ZCU. fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateContainerTypeError!void { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { else => {}, inline .elf => |tag| { @@ -733,6 +736,7 @@ pub const File = struct { /// May be called before or after updateExports for any given Decl. /// TODO: currently `pub` because `Zcu.PerThread` is calling this. + /// Never called when LLVM is codegenning the ZCU. pub fn updateFunc( base: *File, pt: Zcu.PerThread, @@ -740,6 +744,7 @@ pub const File = struct { air: Air, liveness: Air.Liveness, ) UpdateNavError!void { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { inline else => |tag| { dev.check(tag.devFeature()); @@ -756,7 +761,9 @@ pub const File = struct { /// On an incremental update, fixup the line number of all `Nav`s at the given `TrackedInst`, because /// its line number has changed. The ZIR instruction `ti_id` has tag `.declaration`. + /// Never called when LLVM is codegenning the ZCU. fn updateLineNumber(base: *File, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) UpdateLineNumberError!void { + assert(base.comp.zcu.?.llvm_object == null); { const ti = ti_id.resolveFull(&pt.zcu.intern_pool).?; const file = pt.zcu.fileByIndex(ti.file); @@ -846,11 +853,13 @@ pub const File = struct { /// Commit pending changes and write headers. Works based on `effectiveOutputMode` /// rather than final output mode. - pub fn flushModule(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { + /// Never called when LLVM is codegenning the ZCU. + fn flushZcu(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { inline else => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).flushModule(arena, tid, prog_node); + return @as(*tag.Type(), @fieldParentPtr("base", base)).flushZcu(arena, tid, prog_node); }, } } @@ -864,12 +873,14 @@ pub const File = struct { /// a list of size 1, meaning that `exported` is exported once. However, it is possible /// to export the same thing with multiple different symbol names (aliases). /// May be called before or after updateDecl for any given Decl. + /// Never called when LLVM is codegenning the ZCU. pub fn updateExports( base: *File, pt: Zcu.PerThread, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, ) UpdateExportsError!void { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { inline else => |tag| { dev.check(tag.devFeature()); @@ -896,7 +907,9 @@ pub const File = struct { /// `Nav`'s address was not yet resolved, or the containing atom gets moved in virtual memory. /// May be called before or after updateFunc/updateNav therefore it is up to the linker to allocate /// the block/atom. + /// Never called when LLVM is codegenning the ZCU. pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) !u64 { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { .c => unreachable, .spirv => unreachable, @@ -909,6 +922,7 @@ pub const File = struct { } } + /// Never called when LLVM is codegenning the ZCU. pub fn lowerUav( base: *File, pt: Zcu.PerThread, @@ -916,6 +930,7 @@ pub const File = struct { decl_align: InternPool.Alignment, src_loc: Zcu.LazySrcLoc, ) !codegen.GenResult { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { .c => unreachable, .spirv => unreachable, @@ -928,7 +943,9 @@ pub const File = struct { } } + /// Never called when LLVM is codegenning the ZCU. pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) !u64 { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { .c => unreachable, .spirv => unreachable, @@ -941,11 +958,13 @@ pub const File = struct { } } + /// Never called when LLVM is codegenning the ZCU. pub fn deleteExport( base: *File, exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { .plan9, .spirv, @@ -1077,7 +1096,7 @@ pub const File = struct { } } - pub fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { + fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { dev.check(.lld_linker); const tracy = trace(@src()); @@ -1103,9 +1122,12 @@ pub const File = struct { // If there is no Zig code to compile, then we should skip flushing the output file // because it will not be part of the linker line anyway. - const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { - try base.flushModule(arena, tid, prog_node); - + const zcu_obj_path: ?[]const u8 = if (opt_zcu) |zcu| blk: { + if (zcu.llvm_object == null) { + try base.flushZcu(arena, tid, prog_node); + } else { + // `Compilation.flush` has already made LLVM emit this object file for us. + } const dirname = fs.path.dirname(full_out_path_z) orelse "."; break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); } else null; @@ -1346,21 +1368,6 @@ pub const File = struct { return output_mode == .Lib and !self.isStatic(); } - pub fn emitLlvmObject( - base: File, - arena: Allocator, - llvm_object: LlvmObject.Ptr, - prog_node: std.Progress.Node, - ) !void { - return base.comp.emitLlvmObject(arena, .{ - .root_dir = base.emit.root_dir, - .sub_path = std.fs.path.dirname(base.emit.sub_path) orelse "", - }, .{ - .directory = null, - .basename = base.zcu_object_sub_path.?, - }, llvm_object, prog_node); - } - pub fn cgFail( base: *File, nav_index: InternPool.Nav.Index, @@ -1600,7 +1607,11 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { // on the failed type, so when it is changed the `Nav` will be updated. return; } - if (comp.bin_file) |lf| { + if (zcu.llvm_object) |llvm_object| { + llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + }; + } else if (comp.bin_file) |lf| { lf.updateNav(pt, nav_index) catch |err| switch (err) { error.OutOfMemory => diags.setAllocFailure(), error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)), @@ -1616,10 +1627,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { // Not a retryable failure. }, }; - } else if (zcu.llvm_object) |llvm_object| { - llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { - error.OutOfMemory => diags.setAllocFailure(), - }; } }, .link_func => |func| { @@ -1650,11 +1657,13 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { // on the failed type, so when that is changed, this type will be updated. return; } - if (comp.bin_file) |lf| { - lf.updateContainerType(pt, ty) catch |err| switch (err) { - error.OutOfMemory => diags.setAllocFailure(), - error.TypeFailureReported => assert(zcu.failed_types.contains(ty)), - }; + if (zcu.llvm_object == null) { + if (comp.bin_file) |lf| { + lf.updateContainerType(pt, ty) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + error.TypeFailureReported => assert(zcu.failed_types.contains(ty)), + }; + } } }, .update_line_number => |ti| { @@ -1664,11 +1673,13 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { } const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - if (comp.bin_file) |lf| { - lf.updateLineNumber(pt, ti) catch |err| switch (err) { - error.OutOfMemory => diags.setAllocFailure(), - else => |e| log.err("update line number failed: {s}", .{@errorName(e)}), - }; + if (pt.zcu.llvm_object == null) { + if (comp.bin_file) |lf| { + lf.updateLineNumber(pt, ti) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + else => |e| log.err("update line number failed: {s}", .{@errorName(e)}), + }; + } } }, } diff --git a/src/link/C.zig b/src/link/C.zig index c32d8ba80b..15004a26b7 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -382,7 +382,7 @@ pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedIn } pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushModule(arena, tid, prog_node); + return self.flushZcu(arena, tid, prog_node); } fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { @@ -400,7 +400,7 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { return defines; } -pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn flushZcu(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = arena; // Has the same lifetime as the call to Compilation.update. const tracy = trace(@src()); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 5100406030..12a9dc9753 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -3,9 +3,6 @@ //! LLD for traditional linking (linking relocatable object files). //! LLD is also the default linker for LLVM. -/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path. -llvm_object: ?LlvmObject.Ptr = null, - base: link.File, image_base: u64, subsystem: ?std.Target.SubSystem, @@ -87,6 +84,16 @@ base_relocs: BaseRelocationTable = .{}, /// Hot-code swapping state. hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, +/// When linking with LLD, these flags are used to determine the subsystem to pass on the LLD command line. +lld_export_flags: struct { + c_main: bool = false, + winmain: bool = false, + wwinmain: bool = false, + winmain_crt_startup: bool = false, + wwinmain_crt_startup: bool = false, + dllmain_crt_startup: bool = false, +} = .{}, + const is_hot_update_compatible = switch (builtin.target.os.tag) { .windows => true, else => false, @@ -302,9 +309,6 @@ pub fn createEmpty( .pdb_out_path = options.pdb_out_path, .repro = options.repro, }; - if (use_llvm and comp.config.have_zcu) { - coff.llvm_object = try LlvmObject.create(arena, comp); - } errdefer coff.base.destroy(); if (use_lld and (use_llvm or !comp.config.have_zcu)) { @@ -322,7 +326,6 @@ pub fn createEmpty( .mode = link.File.determineMode(use_lld, output_mode, link_mode), }); - assert(coff.llvm_object == null); const gpa = comp.gpa; try coff.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32)); @@ -428,8 +431,6 @@ pub fn open( pub fn deinit(coff: *Coff) void { const gpa = coff.base.comp.gpa; - if (coff.llvm_object) |llvm_object| llvm_object.deinit(); - for (coff.sections.items(.free_list)) |*free_list| { free_list.deinit(gpa); } @@ -1103,9 +1104,6 @@ pub fn updateFunc( if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (coff.llvm_object) |llvm_object| { - return llvm_object.updateFunc(pt, func_index, air, liveness); - } const tracy = trace(@src()); defer tracy.end(); @@ -1205,7 +1203,6 @@ pub fn updateNav( if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (coff.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav_index); const tracy = trace(@src()); defer tracy.end(); @@ -1330,7 +1327,7 @@ pub fn getOrCreateAtomForLazySymbol( } state_ptr.* = .pending_flush; const atom = atom_ptr.*; - // anyerror needs to be deferred until flushModule + // anyerror needs to be deferred until flushZcu if (lazy_sym.ty != .anyerror_type) try coff.updateLazySymbolAtom(pt, lazy_sym, atom, switch (lazy_sym.kind) { .code => coff.text_section_index.?, .const_data => coff.rdata_section_index.?, @@ -1463,8 +1460,6 @@ fn updateNavCode( } pub fn freeNav(coff: *Coff, nav_index: InternPool.NavIndex) void { - if (coff.llvm_object) |llvm_object| return llvm_object.freeNav(nav_index); - const gpa = coff.base.comp.gpa; if (coff.decls.fetchOrderedRemove(nav_index)) |const_kv| { @@ -1485,50 +1480,7 @@ pub fn updateExports( } const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const comp = coff.base.comp; - const target = comp.root_mod.resolved_target.result; - - if (comp.config.use_llvm) { - // Even in the case of LLVM, we need to notice certain exported symbols in order to - // detect the default subsystem. - for (export_indices) |export_idx| { - const exp = export_idx.ptr(zcu); - const exported_nav_index = switch (exp.exported) { - .nav => |nav| nav, - .uav => continue, - }; - const exported_nav = ip.getNav(exported_nav_index); - const exported_ty = exported_nav.typeOf(ip); - if (!ip.isFunctionType(exported_ty)) continue; - const c_cc = target.cCallingConvention().?; - const winapi_cc: std.builtin.CallingConvention = switch (target.cpu.arch) { - .x86 => .{ .x86_stdcall = .{} }, - else => c_cc, - }; - const exported_cc = Type.fromInterned(exported_ty).fnCallingConvention(zcu); - const CcTag = std.builtin.CallingConvention.Tag; - if (@as(CcTag, exported_cc) == @as(CcTag, c_cc) and exp.opts.name.eqlSlice("main", ip) and comp.config.link_libc) { - zcu.stage1_flags.have_c_main = true; - } else if (@as(CcTag, exported_cc) == @as(CcTag, winapi_cc) and target.os.tag == .windows) { - if (exp.opts.name.eqlSlice("WinMain", ip)) { - zcu.stage1_flags.have_winmain = true; - } else if (exp.opts.name.eqlSlice("wWinMain", ip)) { - zcu.stage1_flags.have_wwinmain = true; - } else if (exp.opts.name.eqlSlice("WinMainCRTStartup", ip)) { - zcu.stage1_flags.have_winmain_crt_startup = true; - } else if (exp.opts.name.eqlSlice("wWinMainCRTStartup", ip)) { - zcu.stage1_flags.have_wwinmain_crt_startup = true; - } else if (exp.opts.name.eqlSlice("DllMainCRTStartup", ip)) { - zcu.stage1_flags.have_dllmain_crt_startup = true; - } - } - } - } - - if (coff.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices); - - const gpa = comp.gpa; + const gpa = zcu.gpa; const metadata = switch (exported) { .nav => |nav| blk: { @@ -1621,7 +1573,6 @@ pub fn deleteExport( exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { - if (coff.llvm_object) |_| return; const metadata = switch (exported) { .nav => |nav| coff.navs.getPtr(nav), .uav => |uav| coff.uavs.getPtr(uav), @@ -1692,7 +1643,7 @@ pub fn flush(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st }; } switch (comp.config.output_mode) { - .Exe, .Obj => return coff.flushModule(arena, tid, prog_node), + .Exe, .Obj => return coff.flushZcu(arena, tid, prog_node), .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), } } @@ -1711,8 +1662,12 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: // If there is no Zig code to compile, then we should skip flushing the output file because it // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) blk: { - try coff.flushModule(arena, tid, prog_node); + const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { + if (zcu.llvm_object == null) { + try coff.flushZcu(arena, tid, prog_node); + } else { + // `Compilation.flush` has already made LLVM emit this object file for us. + } if (fs.path.dirname(full_out_path)) |dirname| { break :blk try fs.path.join(arena, &.{ dirname, coff.base.zcu_object_sub_path.? }); @@ -1998,16 +1953,16 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: if (coff.subsystem) |explicit| break :blk explicit; switch (target.os.tag) { .windows => { - if (comp.zcu) |module| { - if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) + if (comp.zcu != null) { + if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib) break :blk null; - if (module.stage1_flags.have_c_main or comp.config.is_test or - module.stage1_flags.have_winmain_crt_startup or - module.stage1_flags.have_wwinmain_crt_startup) + if (coff.lld_export_flags.c_main or comp.config.is_test or + coff.lld_export_flags.winmain_crt_startup or + coff.lld_export_flags.wwinmain_crt_startup) { break :blk .Console; } - if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) + if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) break :blk .Windows; } }, @@ -2136,8 +2091,8 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: } else { try argv.append("-NODEFAULTLIB"); if (!is_lib and entry_name == null) { - if (comp.zcu) |module| { - if (module.stage1_flags.have_winmain_crt_startup) { + if (comp.zcu != null) { + if (coff.lld_export_flags.winmain_crt_startup) { try argv.append("-ENTRY:WinMainCRTStartup"); } else { try argv.append("-ENTRY:wWinMainCRTStartup"); @@ -2244,7 +2199,7 @@ fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Director return null; } -pub fn flushModule( +pub fn flushZcu( coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, @@ -2256,22 +2211,17 @@ pub fn flushModule( const comp = coff.base.comp; const diags = &comp.link_diags; - if (coff.llvm_object) |llvm_object| { - try coff.base.emitLlvmObject(arena, llvm_object, prog_node); - return; - } - const sub_prog_node = prog_node.start("COFF Flush", 0); defer sub_prog_node.end(); - return flushModuleInner(coff, arena, tid) catch |err| switch (err) { + return flushZcuInner(coff, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("COFF flush failed: {s}", .{@errorName(e)}), }; } -fn flushModuleInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { +fn flushZcuInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { _ = arena; const comp = coff.base.comp; @@ -2397,7 +2347,6 @@ pub fn getNavVAddr( nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo, ) !u64 { - assert(coff.llvm_object == null); const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); @@ -2483,8 +2432,6 @@ pub fn getUavVAddr( uav: InternPool.Index, reloc_info: link.File.RelocInfo, ) !u64 { - assert(coff.llvm_object == null); - const this_atom_index = coff.uavs.get(uav).?.atom; const sym_index = coff.getAtom(this_atom_index).getSymbolIndex().?; const atom_index = coff.getAtomIndexForSymbol(.{ @@ -3798,7 +3745,6 @@ const trace = @import("../tracy.zig").trace; const Air = @import("../Air.zig"); const Compilation = @import("../Compilation.zig"); -const LlvmObject = @import("../codegen/llvm.zig").Object; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const TableSection = @import("table_section.zig").TableSection; diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index e2b8229736..c0d1281df2 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -4391,7 +4391,7 @@ fn refAbbrevCode(dwarf: *Dwarf, abbrev_code: AbbrevCode) UpdateError!@typeInfo(A return @intFromEnum(abbrev_code); } -pub fn flushModule(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { +pub fn flushZcu(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 1516993c74..b18fc7ce33 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -32,9 +32,6 @@ entry_name: ?[]const u8, ptr_width: PtrWidth, -/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path. -llvm_object: ?LlvmObject.Ptr = null, - /// A list of all input files. /// First index is a special "null file". Order is otherwise not observed. files: std.MultiArrayList(File.Entry) = .{}, @@ -344,9 +341,6 @@ pub fn createEmpty( .print_map = options.print_map, .dump_argv_list = .empty, }; - if (use_llvm and comp.config.have_zcu) { - self.llvm_object = try LlvmObject.create(arena, comp); - } errdefer self.base.destroy(); if (use_lld and (use_llvm or !comp.config.have_zcu)) { @@ -457,8 +451,6 @@ pub fn open( pub fn deinit(self: *Elf) void { const gpa = self.base.comp.gpa; - if (self.llvm_object) |llvm_object| llvm_object.deinit(); - for (self.file_handles.items) |fh| { fh.close(); } @@ -515,7 +507,6 @@ pub fn deinit(self: *Elf) void { } pub fn getNavVAddr(self: *Elf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo) !u64 { - assert(self.llvm_object == null); return self.zigObjectPtr().?.getNavVAddr(self, pt, nav_index, reloc_info); } @@ -530,7 +521,6 @@ pub fn lowerUav( } pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { - assert(self.llvm_object == null); return self.zigObjectPtr().?.getUavVAddr(self, uav, reloc_info); } @@ -805,35 +795,29 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), }; } - try self.flushModule(arena, tid, prog_node); + try self.flushZcu(arena, tid, prog_node); } -pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); const comp = self.base.comp; const diags = &comp.link_diags; - if (self.llvm_object) |llvm_object| { - try self.base.emitLlvmObject(arena, llvm_object, prog_node); - const use_lld = build_options.have_llvm and comp.config.use_lld; - if (use_lld) return; - } - if (comp.verbose_link) Compilation.dump_argv(self.dump_argv_list.items); const sub_prog_node = prog_node.start("ELF Flush", 0); defer sub_prog_node.end(); - return flushModuleInner(self, arena, tid) catch |err| switch (err) { + return flushZcuInner(self, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("ELF flush failed: {s}", .{@errorName(e)}), }; } -fn flushModuleInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { +fn flushZcuInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { const comp = self.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -1523,8 +1507,12 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s // If there is no Zig code to compile, then we should skip flushing the output file because it // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) blk: { - try self.flushModule(arena, tid, prog_node); + const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { + if (zcu.llvm_object == null) { + try self.flushZcu(arena, tid, prog_node); + } else { + // `Compilation.flush` has already made LLVM emit this object file for us. + } if (fs.path.dirname(full_out_path)) |dirname| { break :blk try fs.path.join(arena, &.{ dirname, self.base.zcu_object_sub_path.? }); @@ -2385,7 +2373,6 @@ pub fn writeElfHeader(self: *Elf) !void { } pub fn freeNav(self: *Elf, nav: InternPool.Nav.Index) void { - if (self.llvm_object) |llvm_object| return llvm_object.freeNav(nav); return self.zigObjectPtr().?.freeNav(self, nav); } @@ -2399,7 +2386,6 @@ pub fn updateFunc( if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness); return self.zigObjectPtr().?.updateFunc(self, pt, func_index, air, liveness); } @@ -2411,7 +2397,6 @@ pub fn updateNav( if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav); return self.zigObjectPtr().?.updateNav(self, pt, nav); } @@ -2423,7 +2408,6 @@ pub fn updateContainerType( if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |_| return; const zcu = pt.zcu; const gpa = zcu.gpa; return self.zigObjectPtr().?.updateContainerType(pt, ty) catch |err| switch (err) { @@ -2449,12 +2433,10 @@ pub fn updateExports( if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices); return self.zigObjectPtr().?.updateExports(self, pt, exported, export_indices); } pub fn updateLineNumber(self: *Elf, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { - if (self.llvm_object) |_| return; return self.zigObjectPtr().?.updateLineNumber(pt, ti_id); } @@ -2463,7 +2445,6 @@ pub fn deleteExport( exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { - if (self.llvm_object) |_| return; return self.zigObjectPtr().?.deleteExport(self, exported, name); } @@ -5332,7 +5313,6 @@ const GotSection = synthetic_sections.GotSection; const GotPltSection = synthetic_sections.GotPltSection; const HashSection = synthetic_sections.HashSection; const LinkerDefined = @import("Elf/LinkerDefined.zig"); -const LlvmObject = @import("../codegen/llvm.zig").Object; const Zcu = @import("../Zcu.zig"); const Object = @import("Elf/Object.zig"); const InternPool = @import("../InternPool.zig"); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 13816940fe..49921089f7 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -310,7 +310,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .activate(elf_file.base.comp.zcu.?, tid); defer pt.deactivate(); - try dwarf.flushModule(pt); + try dwarf.flushZcu(pt); const gpa = elf_file.base.comp.gpa; const cpu_arch = elf_file.getTarget().cpu.arch; @@ -481,7 +481,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { self.debug_str_section_dirty = false; } - // The point of flushModule() is to commit changes, so in theory, nothing should + // The point of flushZcu() is to commit changes, so in theory, nothing should // be dirty after this. However, it is possible for some things to remain // dirty because they fail to be written in the event of compile errors, // such as debug_line_header_dirty and debug_info_header_dirty. @@ -661,7 +661,7 @@ pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void { if (shdr.sh_type == elf.SHT_NOBITS) continue; if (atom_ptr.scanRelocsRequiresCode(elf_file)) { // TODO ideally we don't have to fetch the code here. - // Perhaps it would make sense to save the code until flushModule where we + // Perhaps it would make sense to save the code until flushZcu where we // would free all of generated code? const code = try self.codeAlloc(elf_file, atom_index); defer gpa.free(code); @@ -1075,7 +1075,7 @@ pub fn getOrCreateMetadataForLazySymbol( } state_ptr.* = .pending_flush; const symbol_index = symbol_index_ptr.*; - // anyerror needs to be deferred until flushModule + // anyerror needs to be deferred until flushZcu if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(elf_file, pt, lazy_sym, symbol_index); return symbol_index; } diff --git a/src/link/Goff.zig b/src/link/Goff.zig index 6ed360be25..35821289cd 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -17,10 +17,8 @@ const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); const Air = @import("../Air.zig"); -const LlvmObject = @import("../codegen/llvm.zig").Object; base: link.File, -llvm_object: LlvmObject.Ptr, pub fn createEmpty( arena: Allocator, @@ -36,7 +34,6 @@ pub fn createEmpty( assert(!use_lld); // Caught by Compilation.Config.resolve. assert(target.os.tag == .zos); // Caught by Compilation.Config.resolve. - const llvm_object = try LlvmObject.create(arena, comp); const goff = try arena.create(Goff); goff.* = .{ .base = .{ @@ -52,7 +49,6 @@ pub fn createEmpty( .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, - .llvm_object = llvm_object, }; return goff; @@ -70,7 +66,7 @@ pub fn open( } pub fn deinit(self: *Goff) void { - self.llvm_object.deinit(); + _ = self; } pub fn updateFunc( @@ -80,17 +76,19 @@ pub fn updateFunc( air: Air, liveness: Air.Liveness, ) link.File.UpdateNavError!void { - if (build_options.skip_non_native and builtin.object_format != .goff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - - try self.llvm_object.updateFunc(pt, func_index, air, liveness); + _ = self; + _ = pt; + _ = func_index; + _ = air; + _ = liveness; + unreachable; // we always use llvm } pub fn updateNav(self: *Goff, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { - if (build_options.skip_non_native and builtin.object_format != .goff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - - return self.llvm_object.updateNav(pt, nav); + _ = self; + _ = pt; + _ = nav; + unreachable; // we always use llvm } pub fn updateExports( @@ -99,21 +97,21 @@ pub fn updateExports( exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, ) !void { - if (build_options.skip_non_native and builtin.object_format != .goff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - - return self.llvm_object.updateExports(pt, exported, export_indices); + _ = self; + _ = pt; + _ = exported; + _ = export_indices; + unreachable; // we always use llvm } pub fn flush(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushModule(arena, tid, prog_node); + return self.flushZcu(arena, tid, prog_node); } -pub fn flushModule(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - if (build_options.skip_non_native and builtin.object_format != .goff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - +pub fn flushZcu(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { + _ = self; + _ = arena; _ = tid; - - try self.base.emitLlvmObject(arena, self.llvm_object, prog_node); + _ = prog_node; + unreachable; // we always use llvm } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3ddc12a5b0..6667ed6a63 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -6,9 +6,6 @@ base: link.File, rpath_list: []const []const u8, -/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path. -llvm_object: ?LlvmObject.Ptr = null, - /// Debug symbols bundle (or dSym). d_sym: ?DebugSymbols = null, @@ -225,9 +222,6 @@ pub fn createEmpty( .force_load_objc = options.force_load_objc, .discard_local_symbols = options.discard_local_symbols, }; - if (use_llvm and comp.config.have_zcu) { - self.llvm_object = try LlvmObject.create(arena, comp); - } errdefer self.base.destroy(); self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ @@ -280,8 +274,6 @@ pub fn open( pub fn deinit(self: *MachO) void { const gpa = self.base.comp.gpa; - if (self.llvm_object) |llvm_object| llvm_object.deinit(); - if (self.d_sym) |*d_sym| { d_sym.deinit(); } @@ -350,10 +342,10 @@ pub fn flush( tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { - try self.flushModule(arena, tid, prog_node); + try self.flushZcu(arena, tid, prog_node); } -pub fn flushModule( +pub fn flushZcu( self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, @@ -366,10 +358,6 @@ pub fn flushModule( const gpa = comp.gpa; const diags = &self.base.comp.link_diags; - if (self.llvm_object) |llvm_object| { - try self.base.emitLlvmObject(arena, llvm_object, prog_node); - } - const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); @@ -385,7 +373,7 @@ pub fn flushModule( // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); - if (self.getZigObject()) |zo| try zo.flushModule(self, tid); + if (self.getZigObject()) |zo| try zo.flushZcu(self, tid); if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); @@ -629,7 +617,7 @@ pub fn flushModule( error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to calculate and write uuid: {s}", .{@errorName(e)}), }; - if (self.getDebugSymbols()) |dsym| dsym.flushModule(self) catch |err| switch (err) { + if (self.getDebugSymbols()) |dsym| dsym.flushZcu(self) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}), }; @@ -3079,7 +3067,6 @@ pub fn updateFunc( if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness); return self.getZigObject().?.updateFunc(self, pt, func_index, air, liveness); } @@ -3087,12 +3074,10 @@ pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) lin if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav); return self.getZigObject().?.updateNav(self, pt, nav); } pub fn updateLineNumber(self: *MachO, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { - if (self.llvm_object) |_| return; return self.getZigObject().?.updateLineNumber(pt, ti_id); } @@ -3105,7 +3090,6 @@ pub fn updateExports( if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices); return self.getZigObject().?.updateExports(self, pt, exported, export_indices); } @@ -3114,17 +3098,14 @@ pub fn deleteExport( exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { - if (self.llvm_object) |_| return; return self.getZigObject().?.deleteExport(self, exported, name); } pub fn freeNav(self: *MachO, nav: InternPool.Nav.Index) void { - if (self.llvm_object) |llvm_object| return llvm_object.freeNav(nav); return self.getZigObject().?.freeNav(nav); } pub fn getNavVAddr(self: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo) !u64 { - assert(self.llvm_object == null); return self.getZigObject().?.getNavVAddr(self, pt, nav_index, reloc_info); } @@ -3139,7 +3120,6 @@ pub fn lowerUav( } pub fn getUavVAddr(self: *MachO, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { - assert(self.llvm_object == null); return self.getZigObject().?.getUavVAddr(self, uav, reloc_info); } @@ -5496,7 +5476,6 @@ const ObjcStubsSection = synthetic.ObjcStubsSection; const Object = @import("MachO/Object.zig"); const LazyBind = bind.LazyBind; const LaSymbolPtrSection = synthetic.LaSymbolPtrSection; -const LlvmObject = @import("../codegen/llvm.zig").Object; const Md5 = std.crypto.hash.Md5; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 04b2fe92b0..8579863d03 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -178,7 +178,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 return offset; } -pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void { +pub fn flushZcu(self: *DebugSymbols, macho_file: *MachO) !void { const zo = macho_file.getZigObject().?; for (self.relocs.items) |*reloc| { const sym = zo.symbols.items[reloc.target]; diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index a0de866544..4d99afc61a 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -550,7 +550,7 @@ pub fn getInputSection(self: ZigObject, atom: Atom, macho_file: *MachO) macho.se return sect; } -pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void { +pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void { const diags = &macho_file.base.comp.link_diags; // Handle any lazy symbols that were emitted by incremental compilation. @@ -589,7 +589,7 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .activate(macho_file.base.comp.zcu.?, tid); defer pt.deactivate(); - dwarf.flushModule(pt) catch |err| switch (err) { + dwarf.flushZcu(pt) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to flush dwarf module: {s}", .{@errorName(e)}), }; @@ -599,7 +599,7 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) self.debug_strtab_dirty = false; } - // The point of flushModule() is to commit changes, so in theory, nothing should + // The point of flushZcu() is to commit changes, so in theory, nothing should // be dirty after this. However, it is possible for some things to remain // dirty because they fail to be written in the event of compile errors, // such as debug_line_header_dirty and debug_info_header_dirty. @@ -1537,7 +1537,7 @@ pub fn getOrCreateMetadataForLazySymbol( } state_ptr.* = .pending_flush; const symbol_index = symbol_index_ptr.*; - // anyerror needs to be deferred until flushModule + // anyerror needs to be deferred until flushZcu if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(macho_file, pt, lazy_sym, symbol_index); return symbol_index; } diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 5cbb9287d7..0a940cb0b3 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -494,7 +494,7 @@ fn updateFinish(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index // write the symbol // we already have the got index const sym: aout.Sym = .{ - .value = undefined, // the value of stuff gets filled in in flushModule + .value = undefined, // the value of stuff gets filled in in flushZcu .type = atom.type, .name = try gpa.dupe(u8, nav.name.toSlice(ip)), }; @@ -543,7 +543,7 @@ pub fn flush( .Obj => return diags.fail("writing plan9 object files unimplemented", .{}), .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}), } - return self.flushModule(arena, tid, prog_node); + return self.flushZcu(arena, tid, prog_node); } pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void { @@ -586,7 +586,7 @@ fn atomCount(self: *Plan9) usize { return data_nav_count + fn_nav_count + lazy_atom_count + extern_atom_count + uav_atom_count; } -pub fn flushModule( +pub fn flushZcu( self: *Plan9, arena: Allocator, /// TODO: stop using this @@ -610,7 +610,7 @@ pub fn flushModule( const sub_prog_node = prog_node.start("Flush Module", 0); defer sub_prog_node.end(); - log.debug("flushModule", .{}); + log.debug("flushZcu", .{}); defer assert(self.hdr.entry != 0x0); @@ -1039,7 +1039,7 @@ pub fn getOrCreateAtomForLazySymbol(self: *Plan9, pt: Zcu.PerThread, lazy_sym: F const atom = atom_ptr.*; _ = try self.getAtomPtr(atom).getOrCreateSymbolTableEntry(self); _ = self.getAtomPtr(atom).getOrCreateOffsetTableEntry(self); - // anyerror needs to be deferred until flushModule + // anyerror needs to be deferred until flushZcu if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbolAtom(pt, lazy_sym, atom); return atom; } diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index a49771c3e2..c6e86895f5 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -17,7 +17,7 @@ //! All regular functions. // Because SPIR-V requires re-compilation anyway, and so hot swapping will not work -// anyway, we simply generate all the code in flushModule. This keeps +// anyway, we simply generate all the code in flushZcu. This keeps // things considerably simpler. const SpirV = @This(); @@ -194,17 +194,17 @@ pub fn updateExports( } pub fn flush(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushModule(arena, tid, prog_node); + return self.flushZcu(arena, tid, prog_node); } -pub fn flushModule( +pub fn flushZcu( self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { // The goal is to never use this because it's only needed if we need to - // write to InternPool, but flushModule is too late to be writing to the + // write to InternPool, but flushZcu is too late to be writing to the // InternPool. _ = tid; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 69684724a5..e92be32b7d 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -36,7 +36,6 @@ const abi = @import("../arch/wasm/abi.zig"); const Compilation = @import("../Compilation.zig"); const Dwarf = @import("Dwarf.zig"); const InternPool = @import("../InternPool.zig"); -const LlvmObject = @import("../codegen/llvm.zig").Object; const Zcu = @import("../Zcu.zig"); const codegen = @import("../codegen.zig"); const dev = @import("../dev.zig"); @@ -81,8 +80,6 @@ import_table: bool, export_table: bool, /// Output name of the file name: []const u8, -/// If this is not null, an object file is created by LLVM and linked with LLD afterwards. -llvm_object: ?LlvmObject.Ptr = null, /// List of relocatable files to be linked into the final binary. objects: std.ArrayListUnmanaged(Object) = .{}, @@ -2992,9 +2989,6 @@ pub fn createEmpty( .object_host_name = .none, .preloaded_strings = undefined, }; - if (use_llvm and comp.config.have_zcu) { - wasm.llvm_object = try LlvmObject.create(arena, comp); - } errdefer wasm.base.destroy(); if (options.object_host_name) |name| wasm.object_host_name = (try wasm.internString(name)).toOptional(); @@ -3116,7 +3110,6 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { pub fn deinit(wasm: *Wasm) void { const gpa = wasm.base.comp.gpa; - if (wasm.llvm_object) |llvm_object| llvm_object.deinit(); wasm.navs_exe.deinit(gpa); wasm.navs_obj.deinit(gpa); @@ -3196,7 +3189,6 @@ pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness); dev.check(.wasm_backend); @@ -3228,7 +3220,6 @@ pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (wasm.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav_index); const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); @@ -3308,8 +3299,6 @@ pub fn deleteExport( exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { - if (wasm.llvm_object != null) return; - const zcu = wasm.base.comp.zcu.?; const ip = &zcu.intern_pool; const name_slice = name.toSlice(ip); @@ -3332,7 +3321,6 @@ pub fn updateExports( if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices); const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3391,7 +3379,7 @@ pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), }; } - return wasm.flushModule(arena, tid, prog_node); + return wasm.flushZcu(arena, tid, prog_node); } pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!void { @@ -3785,26 +3773,20 @@ fn markTable(wasm: *Wasm, i: ObjectTableIndex) link.File.FlushError!void { try wasm.tables.put(wasm.base.comp.gpa, .fromObjectTable(i), {}); } -pub fn flushModule( +pub fn flushZcu( wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { // The goal is to never use this because it's only needed if we need to - // write to InternPool, but flushModule is too late to be writing to the + // write to InternPool, but flushZcu is too late to be writing to the // InternPool. _ = tid; const comp = wasm.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; const diags = &comp.link_diags; const gpa = comp.gpa; - if (wasm.llvm_object) |llvm_object| { - try wasm.base.emitLlvmObject(arena, llvm_object, prog_node); - if (use_lld) return; - } - if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items); if (wasm.base.zcu_object_sub_path) |path| { @@ -3870,8 +3852,12 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: // If there is no Zig code to compile, then we should skip flushing the output file because it // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) blk: { - try wasm.flushModule(arena, tid, prog_node); + const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { + if (zcu.llvm_object == null) { + try wasm.flushZcu(arena, tid, prog_node); + } else { + // `Compilation.flush` has already made LLVM emit this object file for us. + } if (fs.path.dirname(full_out_path)) |dirname| { break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? }); diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index 525d99d391..e2f81e015e 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -17,10 +17,8 @@ const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); const Air = @import("../Air.zig"); -const LlvmObject = @import("../codegen/llvm.zig").Object; base: link.File, -llvm_object: LlvmObject.Ptr, pub fn createEmpty( arena: Allocator, @@ -36,7 +34,6 @@ pub fn createEmpty( assert(!use_lld); // Caught by Compilation.Config.resolve. assert(target.os.tag == .aix); // Caught by Compilation.Config.resolve. - const llvm_object = try LlvmObject.create(arena, comp); const xcoff = try arena.create(Xcoff); xcoff.* = .{ .base = .{ @@ -52,7 +49,6 @@ pub fn createEmpty( .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, - .llvm_object = llvm_object, }; return xcoff; @@ -70,7 +66,7 @@ pub fn open( } pub fn deinit(self: *Xcoff) void { - self.llvm_object.deinit(); + _ = self; } pub fn updateFunc( @@ -80,17 +76,19 @@ pub fn updateFunc( air: Air, liveness: Air.Liveness, ) link.File.UpdateNavError!void { - if (build_options.skip_non_native and builtin.object_format != .xcoff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - - try self.llvm_object.updateFunc(pt, func_index, air, liveness); + _ = self; + _ = pt; + _ = func_index; + _ = air; + _ = liveness; + unreachable; // we always use llvm } pub fn updateNav(self: *Xcoff, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { - if (build_options.skip_non_native and builtin.object_format != .xcoff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - - return self.llvm_object.updateNav(pt, nav); + _ = self; + _ = pt; + _ = nav; + unreachable; // we always use llvm } pub fn updateExports( @@ -99,21 +97,21 @@ pub fn updateExports( exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, ) !void { - if (build_options.skip_non_native and builtin.object_format != .xcoff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - - return self.llvm_object.updateExports(pt, exported, export_indices); + _ = self; + _ = pt; + _ = exported; + _ = export_indices; + unreachable; // we always use llvm } pub fn flush(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushModule(arena, tid, prog_node); + return self.flushZcu(arena, tid, prog_node); } -pub fn flushModule(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - if (build_options.skip_non_native and builtin.object_format != .xcoff) - @panic("Attempted to compile for object format that was disabled by build configuration"); - +pub fn flushZcu(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { + _ = self; + _ = arena; _ = tid; - - try self.base.emitLlvmObject(arena, self.llvm_object, prog_node); + _ = prog_node; + unreachable; // we always use llvm } diff --git a/src/target.zig b/src/target.zig index 247b783439..6172b5e7e9 100644 --- a/src/target.zig +++ b/src/target.zig @@ -739,7 +739,7 @@ pub fn functionPointerMask(target: std.Target) ?u64 { pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool { switch (backend) { - .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target), + .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target), .stage2_c => return true, else => return false, } -- cgit v1.2.3 From 2fb6f5c1adcd764372ad28ed4014fdaf558da778 Mon Sep 17 00:00:00 2001 From: mlugg Date: Wed, 28 May 2025 09:30:31 +0100 Subject: link: divorce LLD from the self-hosted linkers Similar to the previous commit, this commit untangles LLD integration from the self-hosted linkers. Despite the big network of functions which were involved, it turns out what was going on here is quite simple. The LLD linking logic is actually very self-contained; it requires a few flags from the `link.File.OpenOptions`, but that's really about it. We don't need any of the mutable state on `Elf`/`Coff`/`Wasm`, for instance. There was some legacy code trying to handle support for using self-hosted codegen with LLD, but that's not a supported use case, so I've just stripped it out. For now, I've just pasted the logic for linking the 3 targets we currently support using LLD for into this new linker implementation, `link.Lld`; however, it's almost certainly possible to combine some of the logic and simplify this file a bit. But to be honest, it's not actually that bad right now. This commit ends up eliminating the distinction between `flush` and `flushZcu` (formerly `flushModule`) in linkers, where the latter previously meant something along the lines of "flush, but if you're going to be linking with LLD, just flush the ZCU object file, don't actually link"?. The distinction here doesn't seem like it was properly defined, and most linkers seem to treat them as essentially identical anyway. Regardless, all calls to `flushZcu` are gone now, so it's deleted -- one `flush` to rule them all! The end result of this commit and the preceding one is that LLVM and LLD fit into the pipeline much more sanely: * If we're using LLVM for the ZCU, that state is on `zcu.llvm_object` * If we're using LLD to link, then the `link.File` is a `link.Lld` * Calls to "ZCU link functions" (e.g. `updateNav`) lower to calls to the LLVM object if it's available, or otherwise to the `link.File` if it's available (neither is available under `-fno-emit-bin`) * After everything is done, linking is finalized by calling `flush` on the `link.File`; for `link.Lld` this invokes LLD, for other linkers it flushes self-hosted linker state There's one messy thing remaining, and that's how self-hosted function codegen in a ZCU works; right now, we process AIR with a call sequence something like this: * `link.doTask` * `Zcu.PerThread.linkerUpdateFunc` * `link.File.updateFunc` * `link.Elf.updateFunc` * `link.Elf.ZigObject.updateFunc` * `codegen.generateFunction` * `arch.x86_64.CodeGen.generate` So, we start in the linker, take a scenic detour through `Zcu`, go back to the linker, into its implementation, and then... right back out, into code which is generic over the linker implementation, and then dispatch on the *backend* instead! Of course, within `arch.x86_64.CodeGen`, there are some more places which switch on the `link` implementation being used. This is all pretty silly... so it shall be my next target. --- src/Compilation.zig | 6 +- src/codegen/llvm.zig | 15 +- src/link.zig | 371 +------ src/link/C.zig | 7 +- src/link/Coff.zig | 630 +----------- src/link/Dwarf.zig | 2 +- src/link/Elf.zig | 781 +------------- src/link/Elf/ZigObject.zig | 8 +- src/link/Goff.zig | 5 - src/link/Lld.zig | 2148 +++++++++++++++++++++++++++++++++++++++ src/link/MachO.zig | 16 +- src/link/MachO/DebugSymbols.zig | 2 +- src/link/MachO/ZigObject.zig | 8 +- src/link/Plan9.zig | 40 +- src/link/SpirV.zig | 11 +- src/link/Wasm.zig | 473 +-------- src/link/Xcoff.zig | 5 - src/main.zig | 18 +- 18 files changed, 2262 insertions(+), 2284 deletions(-) create mode 100644 src/link/Lld.zig (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 61201f39f4..f51020c0ff 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1592,9 +1592,9 @@ pub const CreateOptions = struct { linker_tsaware: bool = false, linker_nxcompat: bool = false, linker_dynamicbase: bool = true, - linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null, + linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null, linker_module_definition_file: ?[]const u8 = null, - linker_sort_section: ?link.File.Elf.SortSection = null, + linker_sort_section: ?link.File.Lld.Elf.SortSection = null, major_subsystem_version: ?u16 = null, minor_subsystem_version: ?u16 = null, clang_passthrough_mode: bool = false, @@ -1616,7 +1616,7 @@ pub const CreateOptions = struct { /// building such dependencies themselves, this flag must be set to avoid /// infinite recursion. skip_linker_dependencies: bool = false, - hash_style: link.File.Elf.HashStyle = .both, + hash_style: link.File.Lld.Elf.HashStyle = .both, entry: Entry = .default, force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .empty, stack_size: ?u64 = null, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2b39396c38..37c13c7211 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1587,12 +1587,15 @@ pub const Object = struct { const comp = zcu.comp; // If we're on COFF and linking with LLD, the linker cares about our exports to determine the subsystem in use. - if (comp.bin_file != null and - comp.bin_file.?.tag == .coff and - zcu.comp.config.use_lld and - ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) - { - const flags = &comp.bin_file.?.cast(.coff).?.lld_export_flags; + coff_export_flags: { + const lf = comp.bin_file orelse break :coff_export_flags; + const lld = lf.cast(.lld) orelse break :coff_export_flags; + const coff = switch (lld.ofmt) { + .elf, .wasm => break :coff_export_flags, + .coff => |*coff| coff, + }; + if (!ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) break :coff_export_flags; + const flags = &coff.lld_export_flags; for (export_indices) |export_index| { const name = export_index.ptr(zcu).opts.name; if (name.eqlSlice("main", ip)) flags.c_main = true; diff --git a/src/link.zig b/src/link.zig index 3270d10c87..68ea533eed 100644 --- a/src/link.zig +++ b/src/link.zig @@ -19,7 +19,6 @@ const Zcu = @import("Zcu.zig"); const InternPool = @import("InternPool.zig"); const Type = @import("Type.zig"); const Value = @import("Value.zig"); -const lldMain = @import("main.zig").lldMain; const Package = @import("Package.zig"); const dev = @import("dev.zig"); const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue; @@ -388,7 +387,6 @@ pub const File = struct { /// When linking with LLD, this linker code will output an object file only at /// this location, and then this path can be placed on the LLD linker line. zcu_object_sub_path: ?[]const u8 = null, - disable_lld_caching: bool, gc_sections: bool, print_gc_sections: bool, build_id: std.zig.BuildId, @@ -424,7 +422,7 @@ pub const File = struct { tsaware: bool, nxcompat: bool, dynamicbase: bool, - compress_debug_sections: Elf.CompressDebugSections, + compress_debug_sections: Lld.Elf.CompressDebugSections, bind_global_refs_locally: bool, import_symbols: bool, import_table: bool, @@ -436,8 +434,8 @@ pub const File = struct { global_base: ?u64, build_id: std.zig.BuildId, disable_lld_caching: bool, - hash_style: Elf.HashStyle, - sort_section: ?Elf.SortSection, + hash_style: Lld.Elf.HashStyle, + sort_section: ?Lld.Elf.SortSection, major_subsystem_version: ?u16, minor_subsystem_version: ?u16, gc_sections: ?bool, @@ -521,12 +519,20 @@ pub const File = struct { emit: Path, options: OpenOptions, ) !*File { + if (comp.config.use_lld) { + dev.check(.lld_linker); + assert(comp.zcu == null or comp.config.use_llvm); + // LLD does not support incremental linking. + const lld: *Lld = try .createEmpty(arena, comp, emit, options); + return &lld.base; + } switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) { inline else => |tag| { dev.check(tag.devFeature()); const ptr = try tag.Type().open(arena, comp, emit, options); return &ptr.base; }, + .lld => unreachable, // not known from ofmt } } @@ -536,12 +542,19 @@ pub const File = struct { emit: Path, options: OpenOptions, ) !*File { + if (comp.config.use_lld) { + dev.check(.lld_linker); + assert(comp.zcu == null or comp.config.use_llvm); + const lld: *Lld = try .createEmpty(arena, comp, emit, options); + return &lld.base; + } switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) { inline else => |tag| { dev.check(tag.devFeature()); const ptr = try tag.Type().createEmpty(arena, comp, emit, options); return &ptr.base; }, + .lld => unreachable, // not known from ofmt } } @@ -554,6 +567,7 @@ pub const File = struct { const comp = base.comp; const gpa = comp.gpa; switch (base.tag) { + .lld => assert(base.file == null), .coff, .elf, .macho, .plan9, .wasm, .goff, .xcoff => { if (base.file != null) return; dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker }); @@ -586,13 +600,12 @@ pub const File = struct { } } } - const use_lld = build_options.have_llvm and comp.config.use_lld; const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = false, .read = true, - .mode = determineMode(use_lld, output_mode, link_mode), + .mode = determineMode(output_mode, link_mode), }); }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), @@ -618,7 +631,6 @@ pub const File = struct { const comp = base.comp; const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; - const use_lld = build_options.have_llvm and comp.config.use_lld; switch (output_mode) { .Obj => return, @@ -629,13 +641,9 @@ pub const File = struct { .Exe => {}, } switch (base.tag) { + .lld => assert(base.file == null), .elf => if (base.file) |f| { dev.check(.elf_linker); - if (base.zcu_object_sub_path != null and use_lld) { - // The file we have open is not the final file that we want to - // make executable, so we don't have to close it. - return; - } f.close(); base.file = null; @@ -650,11 +658,6 @@ pub const File = struct { }, .coff, .macho, .plan9, .wasm, .goff, .xcoff => if (base.file) |f| { dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker }); - if (base.zcu_object_sub_path != null) { - // The file we have open is not the final file that we want to - // make executable, so we don't have to close it. - return; - } f.close(); base.file = null; @@ -692,6 +695,7 @@ pub const File = struct { pub fn getGlobalSymbol(base: *File, name: []const u8, lib_name: ?[]const u8) UpdateNavError!u32 { log.debug("getGlobalSymbol '{s}' (expected in '{?s}')", .{ name, lib_name }); switch (base.tag) { + .lld => unreachable, .plan9 => unreachable, .spirv => unreachable, .c => unreachable, @@ -709,6 +713,7 @@ pub const File = struct { const nav = pt.zcu.intern_pool.getNav(nav_index); assert(nav.status == .fully_resolved); switch (base.tag) { + .lld => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateNav(pt, nav_index); @@ -726,6 +731,7 @@ pub const File = struct { fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateContainerTypeError!void { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, else => {}, inline .elf => |tag| { dev.check(tag.devFeature()); @@ -746,6 +752,7 @@ pub const File = struct { ) UpdateNavError!void { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, air, liveness); @@ -772,6 +779,7 @@ pub const File = struct { } switch (base.tag) { + .lld => unreachable, .spirv => {}, .goff, .xcoff => {}, inline else => |tag| { @@ -811,8 +819,7 @@ pub const File = struct { OutOfMemory, }; - /// Commit pending changes and write headers. Takes into account final output mode - /// and `use_lld`, not only `effectiveOutputMode`. + /// Commit pending changes and write headers. Takes into account final output mode. /// `arena` has the lifetime of the call to `Compilation.update`. pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { const comp = base.comp; @@ -834,15 +841,7 @@ pub const File = struct { }; return; } - assert(base.post_prelink); - - const use_lld = build_options.have_llvm and comp.config.use_lld; - const output_mode = comp.config.output_mode; - const link_mode = comp.config.link_mode; - if (use_lld and output_mode == .Lib and link_mode == .static) { - return base.linkAsArchive(arena, tid, prog_node); - } switch (base.tag) { inline else => |tag| { dev.check(tag.devFeature()); @@ -851,19 +850,6 @@ pub const File = struct { } } - /// Commit pending changes and write headers. Works based on `effectiveOutputMode` - /// rather than final output mode. - /// Never called when LLVM is codegenning the ZCU. - fn flushZcu(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { - assert(base.comp.zcu.?.llvm_object == null); - switch (base.tag) { - inline else => |tag| { - dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).flushZcu(arena, tid, prog_node); - }, - } - } - pub const UpdateExportsError = error{ OutOfMemory, AnalysisFail, @@ -882,6 +868,7 @@ pub const File = struct { ) UpdateExportsError!void { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateExports(pt, exported, export_indices); @@ -911,6 +898,7 @@ pub const File = struct { pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) !u64 { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, .c => unreachable, .spirv => unreachable, .wasm => unreachable, @@ -932,6 +920,7 @@ pub const File = struct { ) !codegen.GenResult { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, .c => unreachable, .spirv => unreachable, .wasm => unreachable, @@ -947,6 +936,7 @@ pub const File = struct { pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) !u64 { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, .c => unreachable, .spirv => unreachable, .wasm => unreachable, @@ -966,6 +956,8 @@ pub const File = struct { ) void { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { + .lld => unreachable, + .plan9, .spirv, .goff, @@ -981,6 +973,7 @@ pub const File = struct { /// Opens a path as an object file and parses it into the linker. fn openLoadObject(base: *File, path: Path) anyerror!void { + if (base.tag == .lld) return; const diags = &base.comp.link_diags; const input = try openObjectInput(diags, path); errdefer input.object.file.close(); @@ -990,6 +983,7 @@ pub const File = struct { /// Opens a path as a static library and parses it into the linker. /// If `query` is non-null, allows GNU ld scripts. fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void { + if (base.tag == .lld) return; if (opt_query) |query| { const archive = try openObject(path, query.must_link, query.hidden); errdefer archive.file.close(); @@ -1012,6 +1006,7 @@ pub const File = struct { /// Opens a path as a shared library and parses it into the linker. /// Handles GNU ld scripts. fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void { + if (base.tag == .lld) return; const dso = try openDso(path, query.needed, query.weak, query.reexport); errdefer dso.file.close(); loadInput(base, .{ .dso = dso }) catch |err| switch (err) { @@ -1064,8 +1059,7 @@ pub const File = struct { } pub fn loadInput(base: *File, input: Input) anyerror!void { - const use_lld = build_options.have_llvm and base.comp.config.use_lld; - if (use_lld) return; + if (base.tag == .lld) return; switch (base.tag) { inline .elf, .wasm => |tag| { dev.check(tag.devFeature()); @@ -1079,8 +1073,6 @@ pub const File = struct { /// this, `loadInput` will not be called anymore. pub fn prelink(base: *File, prog_node: std.Progress.Node) FlushError!void { assert(!base.post_prelink); - const use_lld = build_options.have_llvm and base.comp.config.use_lld; - if (use_lld) return; // In this case, an object file is created by the LLVM backend, so // there is no prelink phase. The Zig code is linked as a standard @@ -1096,170 +1088,6 @@ pub const File = struct { } } - fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = base.comp; - const diags = &comp.link_diags; - - return linkAsArchiveInner(base, arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link as archive: {s}", .{@errorName(e)}), - }; - } - - fn linkAsArchiveInner(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - const comp = base.comp; - - const directory = base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); - const full_out_path_z = try arena.dupeZ(u8, full_out_path); - const opt_zcu = comp.zcu; - - // If there is no Zig code to compile, then we should skip flushing the output file - // because it will not be part of the linker line anyway. - const zcu_obj_path: ?[]const u8 = if (opt_zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try base.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - const dirname = fs.path.dirname(full_out_path_z) orelse "."; - break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else null; - - log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"}); - - const compiler_rt_path: ?Path = if (comp.compiler_rt_strat == .obj) - comp.compiler_rt_obj.?.full_object_path - else - null; - - const ubsan_rt_path: ?Path = if (comp.ubsan_rt_strat == .obj) - comp.ubsan_rt_obj.?.full_object_path - else - null; - - // This function follows the same pattern as link.Elf.linkWithLLD so if you want some - // insight as to what's going on here you can read that function body which is more - // well-commented. - - const id_symlink_basename = "llvm-ar.id"; - - var man: Cache.Manifest = undefined; - defer if (!base.disable_lld_caching) man.deinit(); - - const link_inputs = comp.link_inputs; - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - - // We are about to obtain this lock, so here we give other processes a chance first. - base.releaseLock(); - - try hashInputs(&man, link_inputs); - - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - for (comp.win32_resource_table.keys()) |key| { - _ = try man.addFile(key.status.success.res_path, null); - } - try man.addOptionalFile(zcu_obj_path); - try man.addOptionalFilePath(compiler_rt_path); - try man.addOptionalFilePath(ubsan_rt_path); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| b: { - log.debug("archive new_digest={s} readFile error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - break :b prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("archive digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - base.lock = man.toOwnedLock(); - return; - } - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty; - - try object_files.ensureUnusedCapacity(arena, link_inputs.len); - for (link_inputs) |input| { - object_files.appendAssumeCapacity(try input.path().?.toStringZ(arena)); - } - - try object_files.ensureUnusedCapacity(arena, comp.c_object_table.count() + - comp.win32_resource_table.count() + 2); - - for (comp.c_object_table.keys()) |key| { - object_files.appendAssumeCapacity(try key.status.success.object_path.toStringZ(arena)); - } - for (comp.win32_resource_table.keys()) |key| { - object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path)); - } - if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); - if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); - if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); - - if (comp.verbose_link) { - std.debug.print("ar rcs {s}", .{full_out_path_z}); - for (object_files.items) |arg| { - std.debug.print(" {s}", .{arg}); - } - std.debug.print("\n", .{}); - } - - const llvm_bindings = @import("codegen/llvm/bindings.zig"); - const llvm = @import("codegen/llvm.zig"); - const target = comp.root_mod.resolved_target.result; - llvm.initializeLLVMTarget(target.cpu.arch); - const bad = llvm_bindings.WriteArchive( - full_out_path_z, - object_files.items.ptr, - object_files.items.len, - switch (target.os.tag) { - .aix => .AIXBIG, - .windows => .COFF, - else => if (target.os.tag.isDarwin()) .DARWIN else .GNU, - }, - ); - if (bad) return error.UnableToWriteArchive; - - if (!base.disable_lld_caching) { - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)}); - }; - - if (man.have_exclusive_lock) { - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)}); - }; - } - - base.lock = man.toOwnedLock(); - } - } - pub const Tag = enum { coff, elf, @@ -1270,6 +1098,7 @@ pub const File = struct { plan9, goff, xcoff, + lld, pub fn Type(comptime tag: Tag) type { return switch (tag) { @@ -1282,10 +1111,11 @@ pub const File = struct { .plan9 => Plan9, .goff => Goff, .xcoff => Xcoff, + .lld => Lld, }; } - pub fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag { + fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag { return switch (ofmt) { .coff => .coff, .elf => .elf, @@ -1313,15 +1143,7 @@ pub const File = struct { ty: InternPool.Index, }; - pub fn effectiveOutputMode( - use_lld: bool, - output_mode: std.builtin.OutputMode, - ) std.builtin.OutputMode { - return if (use_lld) .Obj else output_mode; - } - pub fn determineMode( - use_lld: bool, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, ) fs.File.Mode { @@ -1330,7 +1152,7 @@ pub const File = struct { // more leniently. As another data point, C's fopen seems to open files with the // 666 mode. const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777; - switch (effectiveOutputMode(use_lld, output_mode)) { + switch (output_mode) { .Lib => return switch (link_mode) { .dynamic => executable_mode, .static => fs.File.default_mode, @@ -1378,6 +1200,7 @@ pub const File = struct { return base.comp.zcu.?.codegenFail(nav_index, format, args); } + pub const Lld = @import("link/Lld.zig"); pub const C = @import("link/C.zig"); pub const Coff = @import("link/Coff.zig"); pub const Plan9 = @import("link/Plan9.zig"); @@ -1685,116 +1508,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { } } -pub fn spawnLld( - comp: *Compilation, - arena: Allocator, - argv: []const []const u8, -) !void { - if (comp.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv[1..]); - } - - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - if (!std.process.can_spawn) { - const exit_code = try lldMain(arena, argv, false); - if (exit_code == 0) return; - if (comp.clang_passthrough_mode) std.process.exit(exit_code); - return error.LinkFailure; - } - - var stderr: []u8 = &.{}; - defer comp.gpa.free(stderr); - - var child = std.process.Child.init(argv, arena); - const term = (if (comp.clang_passthrough_mode) term: { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - break :term child.spawnAndWait(); - } else term: { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - child.spawn() catch |err| break :term err; - stderr = try child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize)); - break :term child.wait(); - }) catch |first_err| term: { - const err = switch (first_err) { - error.NameTooLong => err: { - const s = fs.path.sep_str; - const rand_int = std.crypto.random.int(u64); - const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; - - const rsp_file = try comp.dirs.local_cache.handle.createFileZ(rsp_path, .{}); - defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| - log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); - { - defer rsp_file.close(); - var rsp_buf = std.io.bufferedWriter(rsp_file.writer()); - const rsp_writer = rsp_buf.writer(); - for (argv[2..]) |arg| { - try rsp_writer.writeByte('"'); - for (arg) |c| { - switch (c) { - '\"', '\\' => try rsp_writer.writeByte('\\'), - else => {}, - } - try rsp_writer.writeByte(c); - } - try rsp_writer.writeByte('"'); - try rsp_writer.writeByte('\n'); - } - try rsp_buf.flush(); - } - - var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint( - arena, - "@{s}", - .{try comp.dirs.local_cache.join(arena, &.{rsp_path})}, - ) }, arena); - if (comp.clang_passthrough_mode) { - rsp_child.stdin_behavior = .Inherit; - rsp_child.stdout_behavior = .Inherit; - rsp_child.stderr_behavior = .Inherit; - - break :term rsp_child.spawnAndWait() catch |err| break :err err; - } else { - rsp_child.stdin_behavior = .Ignore; - rsp_child.stdout_behavior = .Ignore; - rsp_child.stderr_behavior = .Pipe; - - rsp_child.spawn() catch |err| break :err err; - stderr = try rsp_child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize)); - break :term rsp_child.wait() catch |err| break :err err; - } - }, - else => first_err, - }; - log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - - const diags = &comp.link_diags; - switch (term) { - .Exited => |code| if (code != 0) { - if (comp.clang_passthrough_mode) std.process.exit(code); - diags.lockAndParseLldStderr(argv[1], stderr); - return error.LinkFailure; - }, - else => { - if (comp.clang_passthrough_mode) std.process.abort(); - return diags.fail("{s} terminated with stderr:\n{s}", .{ argv[0], stderr }); - }, - } - - if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr}); -} - /// Provided by the CLI, processed into `LinkInput` instances at the start of /// the compilation pipeline. pub const UnresolvedInput = union(enum) { diff --git a/src/link/C.zig b/src/link/C.zig index 15004a26b7..34fc1d3775 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -145,7 +145,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = file, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, }; @@ -381,10 +380,6 @@ pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedIn _ = ti_id; } -pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { const gpa = self.base.comp.gpa; var defines = std.ArrayList(u8).init(gpa); @@ -400,7 +395,7 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { return defines; } -pub fn flushZcu(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { +pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = arena; // Has the same lifetime as the call to Compilation.update. const tracy = trace(@src()); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 12a9dc9753..e7dcbcdf2a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,23 +1,14 @@ -//! The main driver of the COFF linker. -//! Currently uses our own implementation for the incremental linker, and falls back to -//! LLD for traditional linking (linking relocatable object files). -//! LLD is also the default linker for LLVM. +//! The main driver of the self-hosted COFF linker. base: link.File, image_base: u64, -subsystem: ?std.Target.SubSystem, -tsaware: bool, -nxcompat: bool, -dynamicbase: bool, /// TODO this and minor_subsystem_version should be combined into one property and left as /// default or populated together. They should not be separate fields. major_subsystem_version: u16, minor_subsystem_version: u16, -lib_directories: []const Directory, entry: link.File.OpenOptions.Entry, entry_addr: ?u32, module_definition_file: ?[]const u8, -pdb_out_path: ?[]const u8, repro: bool, ptr_width: PtrWidth, @@ -84,16 +75,6 @@ base_relocs: BaseRelocationTable = .{}, /// Hot-code swapping state. hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, -/// When linking with LLD, these flags are used to determine the subsystem to pass on the LLD command line. -lld_export_flags: struct { - c_main: bool = false, - winmain: bool = false, - wwinmain: bool = false, - winmain_crt_startup: bool = false, - wwinmain_crt_startup: bool = false, - dllmain_crt_startup: bool = false, -} = .{}, - const is_hot_update_compatible = switch (builtin.target.os.tag) { .windows => true, else => false, @@ -233,7 +214,6 @@ pub fn createEmpty( const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; const use_llvm = comp.config.use_llvm; - const use_lld = build_options.have_llvm and comp.config.use_lld; const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { 0...32 => .p32, @@ -244,12 +224,10 @@ pub fn createEmpty( else => 0x1000, }; - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. // If using LLVM to generate the object file for the zig compilation unit, // we need a place to put the object file so that it can be subsequently // handled. - const zcu_object_sub_path = if (!use_lld and !use_llvm) + const zcu_object_sub_path = if (!use_llvm) null else try allocPrint(arena, "{s}.obj", .{emit.sub_path}); @@ -266,7 +244,6 @@ pub fn createEmpty( .print_gc_sections = options.print_gc_sections, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .ptr_width = ptr_width, @@ -291,39 +268,21 @@ pub fn createEmpty( .Obj => 0, }, - // Subsystem depends on the set of public symbol names from linked objects. - // See LinkerDriver::inferSubsystem from the LLD project for the flow chart. - .subsystem = options.subsystem, - .entry = options.entry, - .tsaware = options.tsaware, - .nxcompat = options.nxcompat, - .dynamicbase = options.dynamicbase, .major_subsystem_version = options.major_subsystem_version orelse 6, .minor_subsystem_version = options.minor_subsystem_version orelse 0, - .lib_directories = options.lib_directories, .entry_addr = math.cast(u32, options.entry_addr orelse 0) orelse return error.EntryAddressTooBig, .module_definition_file = options.module_definition_file, - .pdb_out_path = options.pdb_out_path, .repro = options.repro, }; errdefer coff.base.destroy(); - if (use_lld and (use_llvm or !comp.config.have_zcu)) { - // LLVM emits the object file (if any); LLD links it into the final product. - return coff; - } - - // What path should this COFF linker code output to? - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. - const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - coff.base.file = try emit.root_dir.handle.createFile(sub_path, .{ + coff.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(use_lld, output_mode, link_mode), + .mode = link.File.determineMode(output_mode, link_mode), }); const gpa = comp.gpa; @@ -1327,7 +1286,7 @@ pub fn getOrCreateAtomForLazySymbol( } state_ptr.* = .pending_flush; const atom = atom_ptr.*; - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try coff.updateLazySymbolAtom(pt, lazy_sym, atom, switch (lazy_sym.kind) { .code => coff.text_section_index.?, .const_data => coff.rdata_section_index.?, @@ -1631,575 +1590,7 @@ fn resolveGlobalSymbol(coff: *Coff, current: SymbolWithLoc) !void { gop.value_ptr.* = current; } -pub fn flush(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - const comp = coff.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const diags = &comp.link_diags; - if (use_lld) { - return coff.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), - }; - } - switch (comp.config.output_mode) { - .Exe, .Obj => return coff.flushZcu(arena, tid, prog_node), - .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), - } -} - -fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = coff.base.comp; - const gpa = comp.gpa; - - const directory = coff.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{coff.base.emit.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try coff.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, coff.base.zcu_object_sub_path.? }); - } else { - break :blk coff.base.zcu_object_sub_path.?; - } - } else null; - - const sub_prog_node = prog_node.start("LLD Link", 0); - defer sub_prog_node.end(); - - const is_lib = comp.config.output_mode == .Lib; - const is_dyn_lib = comp.config.link_mode == .dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or comp.config.output_mode == .Exe; - const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib; - const target = comp.root_mod.resolved_target.result; - const optimize_mode = comp.root_mod.optimize_mode; - const entry_name: ?[]const u8 = switch (coff.entry) { - // This logic isn't quite right for disabled or enabled. No point in fixing it - // when the goal is to eliminate dependency on LLD anyway. - // https://github.com/ziglang/zig/issues/17751 - .disabled, .default, .enabled => null, - .named => |name| name, - }; - - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!coff.base.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!coff.base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - coff.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 14); - - try link.hashInputs(&man, comp.link_inputs); - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - for (comp.win32_resource_table.keys()) |key| { - _ = try man.addFile(key.status.success.res_path, null); - } - try man.addOptionalFile(module_obj_path); - man.hash.addOptionalBytes(entry_name); - man.hash.add(coff.base.stack_size); - man.hash.add(coff.image_base); - man.hash.add(coff.base.build_id); - { - // TODO remove this, libraries must instead be resolved by the frontend. - for (coff.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); - } - man.hash.add(comp.skip_linker_dependencies); - if (comp.config.link_libc) { - man.hash.add(comp.libc_installation != null); - if (comp.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - if (target.abi == .msvc or target.abi == .itanium) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); - } - } - } - man.hash.addListOfBytes(comp.windows_libs.keys()); - man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); - man.hash.addOptional(coff.subsystem); - man.hash.add(comp.config.is_test); - man.hash.add(coff.tsaware); - man.hash.add(coff.nxcompat); - man.hash.add(coff.dynamicbase); - man.hash.add(coff.base.allow_shlib_undefined); - // strip does not need to go into the linker hash because it is part of the hash namespace - man.hash.add(coff.major_subsystem_version); - man.hash.add(coff.minor_subsystem_version); - man.hash.add(coff.repro); - man.hash.addOptional(comp.version); - try man.addOptionalFile(coff.module_definition_file); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - coff.base.lock = man.toOwnedLock(); - return; - } - log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (comp.config.output_mode == .Obj) { - // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk Path.initCwd(p); - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - try std.fs.Dir.copyFile( - the_object_path.root_dir.handle, - the_object_path.sub_path, - directory.handle, - coff.base.emit.sub_path, - .{}, - ); - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(gpa); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - const linker_command = "lld-link"; - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); - - if (target.isMinGW()) { - try argv.append("-lldmingw"); - } - - try argv.append("-ERRORLIMIT:0"); - try argv.append("-NOLOGO"); - if (comp.config.debug_format != .strip) { - try argv.append("-DEBUG"); - - const out_ext = std.fs.path.extension(full_out_path); - const out_pdb = coff.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{ - full_out_path[0 .. full_out_path.len - out_ext.len], - }); - const out_pdb_basename = std.fs.path.basename(out_pdb); - - try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb})); - try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb_basename})); - } - if (comp.version) |version| { - try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor })); - } - - if (target_util.llvmMachineAbi(target)) |mabi| { - try argv.append(try allocPrint(arena, "-MLLVM:-target-abi={s}", .{mabi})); - } - - try argv.append(try allocPrint(arena, "-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"})); - - if (comp.config.lto != .none) { - switch (optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-OPT:lldlto=2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), - } - } - if (comp.config.output_mode == .Exe) { - try argv.append(try allocPrint(arena, "-STACK:{d}", .{coff.base.stack_size})); - } - try argv.append(try allocPrint(arena, "-BASE:{d}", .{coff.image_base})); - - switch (coff.base.build_id) { - .none => try argv.append("-BUILD-ID:NO"), - .fast => try argv.append("-BUILD-ID"), - .uuid, .sha1, .md5, .hexstring => {}, - } - - if (target.cpu.arch == .x86) { - try argv.append("-MACHINE:X86"); - } else if (target.cpu.arch == .x86_64) { - try argv.append("-MACHINE:X64"); - } else if (target.cpu.arch == .thumb) { - try argv.append("-MACHINE:ARM"); - } else if (target.cpu.arch == .aarch64) { - try argv.append("-MACHINE:ARM64"); - } - - for (comp.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } - - if (is_dyn_lib) { - try argv.append("-DLL"); - } - - if (entry_name) |name| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name})); - } - - if (coff.repro) { - try argv.append("-BREPRO"); - } - - if (coff.tsaware) { - try argv.append("-tsaware"); - } - if (coff.nxcompat) { - try argv.append("-nxcompat"); - } - if (!coff.dynamicbase) { - try argv.append("-dynamicbase:NO"); - } - if (coff.base.allow_shlib_undefined) { - try argv.append("-FORCE:UNRESOLVED"); - } - - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - - if (comp.implib_emit) |emit| { - const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); - } - - if (comp.config.link_libc) { - if (comp.libc_installation) |libc_installation| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); - - if (target.abi == .msvc or target.abi == .itanium) { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); - } - } - } - - for (coff.lib_directories) |lib_directory| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."})); - } - - try argv.ensureUnusedCapacity(comp.link_inputs.len); - for (comp.link_inputs) |link_input| switch (link_input) { - .dso_exact => unreachable, // not applicable to PE/COFF - inline .dso, .res => |x| { - argv.appendAssumeCapacity(try x.path.toString(arena)); - }, - .object, .archive => |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Path, obj.path)})); - } else { - argv.appendAssumeCapacity(try obj.path.toString(arena)); - } - }, - }; - - for (comp.c_object_table.keys()) |key| { - try argv.append(try key.status.success.object_path.toString(arena)); - } - - for (comp.win32_resource_table.keys()) |key| { - try argv.append(key.status.success.res_path); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - if (coff.module_definition_file) |def| { - try argv.append(try allocPrint(arena, "-DEF:{s}", .{def})); - } - - const resolved_subsystem: ?std.Target.SubSystem = blk: { - if (coff.subsystem) |explicit| break :blk explicit; - switch (target.os.tag) { - .windows => { - if (comp.zcu != null) { - if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib) - break :blk null; - if (coff.lld_export_flags.c_main or comp.config.is_test or - coff.lld_export_flags.winmain_crt_startup or - coff.lld_export_flags.wwinmain_crt_startup) - { - break :blk .Console; - } - if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) - break :blk .Windows; - } - }, - .uefi => break :blk .EfiApplication, - else => {}, - } - break :blk null; - }; - - const Mode = enum { uefi, win32 }; - const mode: Mode = mode: { - if (resolved_subsystem) |subsystem| { - const subsystem_suffix = try allocPrint(arena, ",{d}.{d}", .{ - coff.major_subsystem_version, coff.minor_subsystem_version, - }); - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } - } else if (target.os.tag == .uefi) { - break :mode .uefi; - } else { - break :mode .win32; - } - }; - - switch (mode) { - .uefi => try argv.appendSlice(&[_][]const u8{ - "-BASE:0", - "-ENTRY:EfiMain", - "-OPT:REF", - "-SAFESEH:NO", - "-MERGE:.rdata=.data", - "-NODEFAULTLIB", - "-SECTION:.xdata,D", - }), - .win32 => { - if (link_in_crt) { - if (target.abi.isGnu()) { - if (target.cpu.arch == .x86) { - try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); - } else { - try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); - } - - if (is_dyn_lib) { - try argv.append(try comp.crtFileAsString(arena, "dllcrt2.obj")); - if (target.cpu.arch == .x86) { - try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); - } else { - try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); - } - } else { - try argv.append(try comp.crtFileAsString(arena, "crt2.obj")); - } - - try argv.append(try comp.crtFileAsString(arena, "libmingw32.lib")); - } else { - try argv.append(switch (comp.config.link_mode) { - .static => "libcmt.lib", - .dynamic => "msvcrt.lib", - }); - - const lib_str = switch (comp.config.link_mode) { - .static => "lib", - .dynamic => "", - }; - try argv.append(try allocPrint(arena, "{s}vcruntime.lib", .{lib_str})); - try argv.append(try allocPrint(arena, "{s}ucrt.lib", .{lib_str})); - - //Visual C++ 2015 Conformance Changes - //https://msdn.microsoft.com/en-us/library/bb531344.aspx - try argv.append("legacy_stdio_definitions.lib"); - - // msvcrt depends on kernel32 and ntdll - try argv.append("kernel32.lib"); - try argv.append("ntdll.lib"); - } - } else { - try argv.append("-NODEFAULTLIB"); - if (!is_lib and entry_name == null) { - if (comp.zcu != null) { - if (coff.lld_export_flags.winmain_crt_startup) { - try argv.append("-ENTRY:WinMainCRTStartup"); - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } - } - }, - } - - if (comp.config.link_libc and link_in_crt) { - if (comp.zigc_static_lib) |zigc| { - try argv.append(try zigc.full_object_path.toString(arena)); - } - } - - // libc++ dep - if (comp.config.link_libcpp) { - try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - } - - // libunwind dep - if (comp.config.link_libunwind) { - try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); - } - - if (comp.config.any_fuzz) { - try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); - } - - const ubsan_rt_path: ?Path = blk: { - if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; - if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - if (ubsan_rt_path) |path| { - try argv.append(try path.toString(arena)); - } - - if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { - // MSVC compiler_rt is missing some stuff, so we build it unconditionally but - // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); - if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); - } - - try argv.ensureUnusedCapacity(comp.windows_libs.count()); - for (comp.windows_libs.keys()) |key| { - const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); - if (comp.crt_files.get(lib_basename)) |crt_file| { - argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); - continue; - } - if (try findLib(arena, lib_basename, coff.lib_directories)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - if (target.abi.isGnu()) { - const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try findLib(arena, fallback_name, coff.lib_directories)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - } - if (target.abi == .msvc or target.abi == .itanium) { - argv.appendAssumeCapacity(lib_basename); - continue; - } - - log.err("DLL import library for -l{s} not found", .{key}); - return error.DllImportLibraryNotFound; - } - - try link.spawnLld(comp, arena, argv.items); - } - - if (!coff.base.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - coff.base.lock = man.toOwnedLock(); - } -} - -fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Directory) !?[]const u8 { - for (lib_directories) |lib_directory| { - lib_directory.handle.access(name, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - return try lib_directory.join(arena, &.{name}); - } - return null; -} - -pub fn flushZcu( +pub fn flush( coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, @@ -2211,17 +1602,22 @@ pub fn flushZcu( const comp = coff.base.comp; const diags = &comp.link_diags; + switch (coff.base.comp.config.output_mode) { + .Exe, .Obj => {}, + .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}), + } + const sub_prog_node = prog_node.start("COFF Flush", 0); defer sub_prog_node.end(); - return flushZcuInner(coff, arena, tid) catch |err| switch (err) { + return flushInner(coff, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("COFF flush failed: {s}", .{@errorName(e)}), }; } -fn flushZcuInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { +fn flushInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void { _ = arena; const comp = coff.base.comp; diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index c0d1281df2..393cd53774 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -4391,7 +4391,7 @@ fn refAbbrevCode(dwarf: *Dwarf, abbrev_code: AbbrevCode) UpdateError!@typeInfo(A return @intFromEnum(abbrev_code); } -pub fn flushZcu(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { +pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b18fc7ce33..1702ef200c 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -4,7 +4,6 @@ base: link.File, zig_object: ?*ZigObject, rpath_table: std.StringArrayHashMapUnmanaged(void), image_base: u64, -emit_relocs: bool, z_nodelete: bool, z_notext: bool, z_defs: bool, @@ -16,18 +15,7 @@ z_relro: bool, z_common_page_size: ?u64, /// TODO make this non optional and resolve the default in open() z_max_page_size: ?u64, -hash_style: HashStyle, -compress_debug_sections: CompressDebugSections, -symbol_wrap_set: std.StringArrayHashMapUnmanaged(void), -sort_section: ?SortSection, soname: ?[]const u8, -bind_global_refs_locally: bool, -linker_script: ?[]const u8, -version_script: ?[]const u8, -allow_undefined_version: bool, -enable_new_dtags: ?bool, -print_icf_sections: bool, -print_map: bool, entry_name: ?[]const u8, ptr_width: PtrWidth, @@ -201,9 +189,6 @@ const minimum_atom_size = 64; pub const min_text_capacity = padToIdeal(minimum_atom_size); pub const PtrWidth = enum { p32, p64 }; -pub const HashStyle = enum { sysv, gnu, both }; -pub const CompressDebugSections = enum { none, zlib, zstd }; -pub const SortSection = enum { name, alignment }; pub fn createEmpty( arena: Allocator, @@ -214,7 +199,6 @@ pub fn createEmpty( const target = comp.root_mod.resolved_target.result; assert(target.ofmt == .elf); - const use_lld = build_options.have_llvm and comp.config.use_lld; const use_llvm = comp.config.use_llvm; const opt_zcu = comp.zcu; const output_mode = comp.config.output_mode; @@ -265,12 +249,10 @@ pub fn createEmpty( const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic; const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL; - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. // If using LLVM to generate the object file for the zig compilation unit, // we need a place to put the object file so that it can be subsequently // handled. - const zcu_object_sub_path = if (!use_lld and !use_llvm) + const zcu_object_sub_path = if (!use_llvm) null else try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); @@ -292,7 +274,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .zig_object = null, @@ -317,7 +298,6 @@ pub fn createEmpty( }; }, - .emit_relocs = options.emit_relocs, .z_nodelete = options.z_nodelete, .z_notext = options.z_notext, .z_defs = options.z_defs, @@ -327,27 +307,11 @@ pub fn createEmpty( .z_relro = options.z_relro, .z_common_page_size = options.z_common_page_size, .z_max_page_size = options.z_max_page_size, - .hash_style = options.hash_style, - .compress_debug_sections = options.compress_debug_sections, - .symbol_wrap_set = options.symbol_wrap_set, - .sort_section = options.sort_section, .soname = options.soname, - .bind_global_refs_locally = options.bind_global_refs_locally, - .linker_script = options.linker_script, - .version_script = options.version_script, - .allow_undefined_version = options.allow_undefined_version, - .enable_new_dtags = options.enable_new_dtags, - .print_icf_sections = options.print_icf_sections, - .print_map = options.print_map, .dump_argv_list = .empty, }; errdefer self.base.destroy(); - if (use_lld and (use_llvm or !comp.config.have_zcu)) { - // LLVM emits the object file (if any); LLD links it into the final product. - return self; - } - // --verbose-link if (comp.verbose_link) try dumpArgvInit(self, arena); @@ -355,13 +319,11 @@ pub fn createEmpty( const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static); // What path should this ELF linker code output to? - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. - const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; + const sub_path = emit.sub_path; self.base.file = try emit.root_dir.handle.createFile(sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(use_lld, output_mode, link_mode), + .mode = link.File.determineMode(output_mode, link_mode), }); const gpa = comp.gpa; @@ -785,20 +747,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { } pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - const comp = self.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const diags = &comp.link_diags; - if (use_lld) { - return self.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), - }; - } - try self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -810,14 +758,14 @@ pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: const sub_prog_node = prog_node.start("ELF Flush", 0); defer sub_prog_node.end(); - return flushZcuInner(self, arena, tid) catch |err| switch (err) { + return flushInner(self, arena, tid) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("ELF flush failed: {s}", .{@errorName(e)}), }; } -fn flushZcuInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { +fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { const comp = self.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -1492,643 +1440,6 @@ pub fn initOutputSection(self: *Elf, args: struct { return out_shndx; } -fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = self.base.comp; - const gpa = comp.gpa; - const diags = &comp.link_diags; - - const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try self.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, self.base.zcu_object_sub_path.? }); - } else { - break :blk self.base.zcu_object_sub_path.?; - } - } else null; - - const sub_prog_node = prog_node.start("LLD Link", 0); - defer sub_prog_node.end(); - - const output_mode = comp.config.output_mode; - const is_obj = output_mode == .Obj; - const is_lib = output_mode == .Lib; - const link_mode = comp.config.link_mode; - const is_dyn_lib = link_mode == .dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; - const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib; - const target = self.getTarget(); - const compiler_rt_path: ?Path = blk: { - if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; - if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - const ubsan_rt_path: ?Path = blk: { - if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; - if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - - // Here we want to determine whether we can save time by not invoking LLD when the - // output is unchanged. None of the linker options or the object files that are being - // linked are in the hash that namespaces the directory we are outputting to. Therefore, - // we must hash those now, and the resulting digest will form the "id" of the linking - // job we are about to perform. - // After a successful link, we store the id in the metadata of a symlink named "lld.id" in - // the artifact directory. So, now, we check if this symlink exists, and if it matches - // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD. - const id_symlink_basename = "lld.id"; - - var man: std.Build.Cache.Manifest = undefined; - defer if (!self.base.disable_lld_caching) man.deinit(); - - var digest: [std.Build.Cache.hex_digest_len]u8 = undefined; - - if (!self.base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - - // We are about to obtain this lock, so here we give other processes a chance first. - self.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 14); - - try man.addOptionalFile(self.linker_script); - try man.addOptionalFile(self.version_script); - man.hash.add(self.allow_undefined_version); - man.hash.addOptional(self.enable_new_dtags); - try link.hashInputs(&man, comp.link_inputs); - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - try man.addOptionalFilePath(compiler_rt_path); - try man.addOptionalFilePath(ubsan_rt_path); - try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); - try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); - - // We can skip hashing libc and libc++ components that we are in charge of building from Zig - // installation sources because they are always a product of the compiler version + target information. - man.hash.addOptionalBytes(self.entry_name); - man.hash.add(self.image_base); - man.hash.add(self.base.gc_sections); - man.hash.addOptional(self.sort_section); - man.hash.add(comp.link_eh_frame_hdr); - man.hash.add(self.emit_relocs); - man.hash.add(comp.config.rdynamic); - man.hash.addListOfBytes(self.rpath_table.keys()); - if (output_mode == .Exe) { - man.hash.add(self.base.stack_size); - } - man.hash.add(self.base.build_id); - man.hash.addListOfBytes(self.symbol_wrap_set.keys()); - man.hash.add(comp.skip_linker_dependencies); - man.hash.add(self.z_nodelete); - man.hash.add(self.z_notext); - man.hash.add(self.z_defs); - man.hash.add(self.z_origin); - man.hash.add(self.z_nocopyreloc); - man.hash.add(self.z_now); - man.hash.add(self.z_relro); - man.hash.add(self.z_common_page_size orelse 0); - man.hash.add(self.z_max_page_size orelse 0); - man.hash.add(self.hash_style); - // strip does not need to go into the linker hash because it is part of the hash namespace - if (comp.config.link_libc) { - man.hash.add(comp.libc_installation != null); - if (comp.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - } - } - if (have_dynamic_linker) { - man.hash.addOptionalBytes(target.dynamic_linker.get()); - } - man.hash.addOptionalBytes(self.soname); - man.hash.addOptional(comp.version); - man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); - man.hash.add(self.base.allow_shlib_undefined); - man.hash.add(self.bind_global_refs_locally); - man.hash.add(self.compress_debug_sections); - man.hash.add(comp.config.any_sanitize_thread); - man.hash.add(comp.config.any_fuzz); - man.hash.addOptionalBytes(comp.sysroot); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = std.Build.Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("ELF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("ELF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - self.base.lock = man.toOwnedLock(); - return; - } - log.debug("ELF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - // Due to a deficiency in LLD, we need to special-case BPF to a simple file - // copy when generating relocatables. Normally, we would expect `lld -r` to work. - // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails - // before even generating the relocatable. - // - // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can - // produce usable object files. - if (output_mode == .Obj and - (comp.config.lto != .none or - target.cpu.arch.isBpf() or - target.cpu.arch == .lanai or - target.cpu.arch == .m68k or - target.cpu.arch.isSPARC() or - target.cpu.arch == .ve or - target.cpu.arch == .xcore)) - { - // In this case we must do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk Path.initCwd(p); - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - try std.fs.Dir.copyFile( - the_object_path.root_dir.handle, - the_object_path.sub_path, - directory.handle, - self.base.emit.sub_path, - .{}, - ); - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(gpa); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - const linker_command = "ld.lld"; - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); - if (is_obj) { - try argv.append("-r"); - } - - try argv.append("--error-limit=0"); - - if (comp.sysroot) |sysroot| { - try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot})); - } - - if (target_util.llvmMachineAbi(target)) |mabi| { - try argv.appendSlice(&.{ - "-mllvm", - try std.fmt.allocPrint(arena, "-target-abi={s}", .{mabi}), - }); - } - - try argv.appendSlice(&.{ - "-mllvm", - try std.fmt.allocPrint(arena, "-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}), - }); - - if (comp.config.lto != .none) { - switch (comp.root_mod.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("--lto-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"), - } - } - switch (comp.root_mod.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), - } - - if (self.entry_name) |name| { - try argv.appendSlice(&.{ "--entry", name }); - } - - for (comp.force_undefined_symbols.keys()) |sym| { - try argv.append("-u"); - try argv.append(sym); - } - - switch (self.hash_style) { - .gnu => try argv.append("--hash-style=gnu"), - .sysv => try argv.append("--hash-style=sysv"), - .both => {}, // this is the default - } - - if (output_mode == .Exe) { - try argv.appendSlice(&.{ - "-z", - try std.fmt.allocPrint(arena, "stack-size={d}", .{self.base.stack_size}), - }); - } - - switch (self.base.build_id) { - .none => try argv.append("--build-id=none"), - .fast, .uuid, .sha1, .md5 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ - @tagName(self.base.build_id), - })), - .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ - std.fmt.fmtSliceHexLower(hs.toSlice()), - })), - } - - try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{self.image_base})); - - if (self.linker_script) |linker_script| { - try argv.append("-T"); - try argv.append(linker_script); - } - - if (self.sort_section) |how| { - const arg = try std.fmt.allocPrint(arena, "--sort-section={s}", .{@tagName(how)}); - try argv.append(arg); - } - - if (self.base.gc_sections) { - try argv.append("--gc-sections"); - } - - if (self.base.print_gc_sections) { - try argv.append("--print-gc-sections"); - } - - if (self.print_icf_sections) { - try argv.append("--print-icf-sections"); - } - - if (self.print_map) { - try argv.append("--print-map"); - } - - if (comp.link_eh_frame_hdr) { - try argv.append("--eh-frame-hdr"); - } - - if (self.emit_relocs) { - try argv.append("--emit-relocs"); - } - - if (comp.config.rdynamic) { - try argv.append("--export-dynamic"); - } - - if (comp.config.debug_format == .strip) { - try argv.append("-s"); - } - - if (self.z_nodelete) { - try argv.append("-z"); - try argv.append("nodelete"); - } - if (self.z_notext) { - try argv.append("-z"); - try argv.append("notext"); - } - if (self.z_defs) { - try argv.append("-z"); - try argv.append("defs"); - } - if (self.z_origin) { - try argv.append("-z"); - try argv.append("origin"); - } - if (self.z_nocopyreloc) { - try argv.append("-z"); - try argv.append("nocopyreloc"); - } - if (self.z_now) { - // LLD defaults to -zlazy - try argv.append("-znow"); - } - if (!self.z_relro) { - // LLD defaults to -zrelro - try argv.append("-znorelro"); - } - if (self.z_common_page_size) |size| { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "common-page-size={d}", .{size})); - } - if (self.z_max_page_size) |size| { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "max-page-size={d}", .{size})); - } - - if (getLDMOption(target)) |ldm| { - try argv.append("-m"); - try argv.append(ldm); - } - - if (link_mode == .static) { - if (target.cpu.arch.isArm()) { - try argv.append("-Bstatic"); - } else { - try argv.append("-static"); - } - } else if (switch (target.os.tag) { - else => is_dyn_lib, - .haiku => is_exe_or_dyn_lib, - }) { - try argv.append("-shared"); - } - - if (comp.config.pie and output_mode == .Exe) { - try argv.append("-pie"); - } - - if (is_exe_or_dyn_lib and target.os.tag == .netbsd) { - // Add options to produce shared objects with only 2 PT_LOAD segments. - // NetBSD expects 2 PT_LOAD segments in a shared object, otherwise - // ld.elf_so fails loading dynamic libraries with "not found" error. - // See https://github.com/ziglang/zig/issues/9109 . - try argv.append("--no-rosegment"); - try argv.append("-znorelro"); - } - - try argv.append("-o"); - try argv.append(full_out_path); - - // csu prelude - const csu = try comp.getCrtPaths(arena); - if (csu.crt0) |p| try argv.append(try p.toString(arena)); - if (csu.crti) |p| try argv.append(try p.toString(arena)); - if (csu.crtbegin) |p| try argv.append(try p.toString(arena)); - - for (self.rpath_table.keys()) |rpath| { - try argv.appendSlice(&.{ "-rpath", rpath }); - } - - for (self.symbol_wrap_set.keys()) |symbol_name| { - try argv.appendSlice(&.{ "-wrap", symbol_name }); - } - - if (comp.config.link_libc) { - if (comp.libc_installation) |libc_installation| { - try argv.append("-L"); - try argv.append(libc_installation.crt_dir.?); - } - } - - if (have_dynamic_linker and - (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker)) - { - if (target.dynamic_linker.get()) |dynamic_linker| { - try argv.append("-dynamic-linker"); - try argv.append(dynamic_linker); - } - } - - if (is_dyn_lib) { - if (self.soname) |soname| { - try argv.append("-soname"); - try argv.append(soname); - } - if (self.version_script) |version_script| { - try argv.append("-version-script"); - try argv.append(version_script); - } - if (self.allow_undefined_version) { - try argv.append("--undefined-version"); - } else { - try argv.append("--no-undefined-version"); - } - if (self.enable_new_dtags) |enable_new_dtags| { - if (enable_new_dtags) { - try argv.append("--enable-new-dtags"); - } else { - try argv.append("--disable-new-dtags"); - } - } - } - - // Positional arguments to the linker such as object files. - var whole_archive = false; - - for (self.base.comp.link_inputs) |link_input| switch (link_input) { - .res => unreachable, // Windows-only - .dso => continue, - .object, .archive => |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - try argv.append(try obj.path.toString(arena)); - }, - .dso_exact => |dso_exact| { - assert(dso_exact.name[0] == ':'); - try argv.appendSlice(&.{ "-l", dso_exact.name }); - }, - }; - - if (whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(try key.status.success.object_path.toString(arena)); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - if (comp.tsan_lib) |lib| { - assert(comp.config.any_sanitize_thread); - try argv.append(try lib.full_object_path.toString(arena)); - } - - if (comp.fuzzer_lib) |lib| { - assert(comp.config.any_fuzz); - try argv.append(try lib.full_object_path.toString(arena)); - } - - if (ubsan_rt_path) |p| { - try argv.append(try p.toString(arena)); - } - - // Shared libraries. - if (is_exe_or_dyn_lib) { - // Worst-case, we need an --as-needed argument for every lib, as well - // as one before and one after. - try argv.ensureUnusedCapacity(2 * self.base.comp.link_inputs.len + 2); - argv.appendAssumeCapacity("--as-needed"); - var as_needed = true; - - for (self.base.comp.link_inputs) |link_input| switch (link_input) { - .res => unreachable, // Windows-only - .object, .archive, .dso_exact => continue, - .dso => |dso| { - const lib_as_needed = !dso.needed; - switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { - 0b00, 0b11 => {}, - 0b01 => { - argv.appendAssumeCapacity("--no-as-needed"); - as_needed = false; - }, - 0b10 => { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - }, - } - - // By this time, we depend on these libs being dynamically linked - // libraries and not static libraries (the check for that needs to be earlier), - // but they could be full paths to .so files, in which case we - // want to avoid prepending "-l". - argv.appendAssumeCapacity(try dso.path.toString(arena)); - }, - }; - - if (!as_needed) { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - } - - // libc++ dep - if (comp.config.link_libcpp) { - try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - } - - // libunwind dep - if (comp.config.link_libunwind) { - try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); - } - - // libc dep - diags.flags.missing_libc = false; - if (comp.config.link_libc) { - if (comp.libc_installation != null) { - const needs_grouping = link_mode == .static; - if (needs_grouping) try argv.append("--start-group"); - try argv.appendSlice(target_util.libcFullLinkFlags(target)); - if (needs_grouping) try argv.append("--end-group"); - } else if (target.isGnuLibC()) { - for (glibc.libs) |lib| { - if (lib.removed_in) |rem_in| { - if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue; - } - - const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ - comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a")); - } else if (target.isMuslLibC()) { - try argv.append(try comp.crtFileAsString(arena, switch (link_mode) { - .static => "libc.a", - .dynamic => "libc.so", - })); - } else if (target.isFreeBSDLibC()) { - for (freebsd.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ - comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - } else if (target.isNetBSDLibC()) { - for (netbsd.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ - comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - } else { - diags.flags.missing_libc = true; - } - - if (comp.zigc_static_lib) |zigc| { - try argv.append(try zigc.full_object_path.toString(arena)); - } - } - } - - // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs - // to be after the shared libraries, so they are picked up from the shared - // libraries, not libcompiler_rt. - if (compiler_rt_path) |p| { - try argv.append(try p.toString(arena)); - } - - // crt postlude - if (csu.crtend) |p| try argv.append(try p.toString(arena)); - if (csu.crtn) |p| try argv.append(try p.toString(arena)); - - if (self.base.allow_shlib_undefined) { - try argv.append("--allow-shlib-undefined"); - } - - switch (self.compress_debug_sections) { - .none => {}, - .zlib => try argv.append("--compress-debug-sections=zlib"), - .zstd => try argv.append("--compress-debug-sections=zstd"), - } - - if (self.bind_global_refs_locally) { - try argv.append("-Bsymbolic"); - } - - try link.spawnLld(comp, arena, argv.items); - } - - if (!self.base.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - std.Build.Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = man.toOwnedLock(); - } -} - pub fn writeShdrTable(self: *Elf) !void { const gpa = self.base.comp.gpa; const target_endian = self.getTarget().cpu.arch.endian(); @@ -4121,85 +3432,6 @@ fn shdrTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { }; } -fn getLDMOption(target: std.Target) ?[]const u8 { - // This should only return emulations understood by LLD's parseEmulation(). - return switch (target.cpu.arch) { - .aarch64 => switch (target.os.tag) { - .linux => "aarch64linux", - else => "aarch64elf", - }, - .aarch64_be => switch (target.os.tag) { - .linux => "aarch64linuxb", - else => "aarch64elfb", - }, - .amdgcn => "elf64_amdgpu", - .arm, .thumb => switch (target.os.tag) { - .linux => "armelf_linux_eabi", - else => "armelf", - }, - .armeb, .thumbeb => switch (target.os.tag) { - .linux => "armelfb_linux_eabi", - else => "armelfb", - }, - .hexagon => "hexagonelf", - .loongarch32 => "elf32loongarch", - .loongarch64 => "elf64loongarch", - .mips => switch (target.os.tag) { - .freebsd => "elf32btsmip_fbsd", - else => "elf32btsmip", - }, - .mipsel => switch (target.os.tag) { - .freebsd => "elf32ltsmip_fbsd", - else => "elf32ltsmip", - }, - .mips64 => switch (target.os.tag) { - .freebsd => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32btsmipn32_fbsd", - else => "elf64btsmip_fbsd", - }, - else => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32btsmipn32", - else => "elf64btsmip", - }, - }, - .mips64el => switch (target.os.tag) { - .freebsd => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32ltsmipn32_fbsd", - else => "elf64ltsmip_fbsd", - }, - else => switch (target.abi) { - .gnuabin32, .muslabin32 => "elf32ltsmipn32", - else => "elf64ltsmip", - }, - }, - .msp430 => "msp430elf", - .powerpc => switch (target.os.tag) { - .freebsd => "elf32ppc_fbsd", - .linux => "elf32ppclinux", - else => "elf32ppc", - }, - .powerpcle => switch (target.os.tag) { - .linux => "elf32lppclinux", - else => "elf32lppc", - }, - .powerpc64 => "elf64ppc", - .powerpc64le => "elf64lppc", - .riscv32 => "elf32lriscv", - .riscv64 => "elf64lriscv", - .s390x => "elf64_s390", - .sparc64 => "elf64_sparc", - .x86 => switch (target.os.tag) { - .freebsd => "elf_i386_fbsd", - else => "elf_i386", - }, - .x86_64 => switch (target.abi) { - .gnux32, .muslx32 => "elf32_x86_64", - else => "elf_x86_64", - }, - else => null, - }; -} - pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { return actual_size +| (actual_size / ideal_factor); } @@ -5284,10 +4516,7 @@ const codegen = @import("../codegen.zig"); const dev = @import("../dev.zig"); const eh_frame = @import("Elf/eh_frame.zig"); const gc = @import("Elf/gc.zig"); -const glibc = @import("../libs/glibc.zig"); const musl = @import("../libs/musl.zig"); -const freebsd = @import("../libs/freebsd.zig"); -const netbsd = @import("../libs/netbsd.zig"); const link = @import("../link.zig"); const relocatable = @import("Elf/relocatable.zig"); const relocation = @import("Elf/relocation.zig"); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 49921089f7..e377f3a9af 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -310,7 +310,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .activate(elf_file.base.comp.zcu.?, tid); defer pt.deactivate(); - try dwarf.flushZcu(pt); + try dwarf.flush(pt); const gpa = elf_file.base.comp.gpa; const cpu_arch = elf_file.getTarget().cpu.arch; @@ -481,7 +481,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void { self.debug_str_section_dirty = false; } - // The point of flushZcu() is to commit changes, so in theory, nothing should + // The point of flush() is to commit changes, so in theory, nothing should // be dirty after this. However, it is possible for some things to remain // dirty because they fail to be written in the event of compile errors, // such as debug_line_header_dirty and debug_info_header_dirty. @@ -661,7 +661,7 @@ pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void { if (shdr.sh_type == elf.SHT_NOBITS) continue; if (atom_ptr.scanRelocsRequiresCode(elf_file)) { // TODO ideally we don't have to fetch the code here. - // Perhaps it would make sense to save the code until flushZcu where we + // Perhaps it would make sense to save the code until flush where we // would free all of generated code? const code = try self.codeAlloc(elf_file, atom_index); defer gpa.free(code); @@ -1075,7 +1075,7 @@ pub fn getOrCreateMetadataForLazySymbol( } state_ptr.* = .pending_flush; const symbol_index = symbol_index_ptr.*; - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(elf_file, pt, lazy_sym, symbol_index); return symbol_index; } diff --git a/src/link/Goff.zig b/src/link/Goff.zig index 35821289cd..28da184495 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -46,7 +46,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 0, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, }; @@ -105,10 +104,6 @@ pub fn updateExports( } pub fn flush(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = self; _ = arena; _ = tid; diff --git a/src/link/Lld.zig b/src/link/Lld.zig new file mode 100644 index 0000000000..ba52d0c8d4 --- /dev/null +++ b/src/link/Lld.zig @@ -0,0 +1,2148 @@ +base: link.File, +disable_caching: bool, +ofmt: union(enum) { + elf: Elf, + coff: Coff, + wasm: Wasm, +}, + +const Coff = struct { + image_base: u64, + entry: link.File.OpenOptions.Entry, + pdb_out_path: ?[]const u8, + repro: bool, + tsaware: bool, + nxcompat: bool, + dynamicbase: bool, + /// TODO this and minor_subsystem_version should be combined into one property and left as + /// default or populated together. They should not be separate fields. + major_subsystem_version: u16, + minor_subsystem_version: u16, + lib_directories: []const Cache.Directory, + module_definition_file: ?[]const u8, + subsystem: ?std.Target.SubSystem, + /// These flags are populated by `codegen.llvm.updateExports` to allow us to guess the subsystem. + lld_export_flags: struct { + c_main: bool, + winmain: bool, + wwinmain: bool, + winmain_crt_startup: bool, + wwinmain_crt_startup: bool, + dllmain_crt_startup: bool, + }, + fn init(comp: *Compilation, options: link.File.OpenOptions) !Coff { + const target = comp.root_mod.resolved_target.result; + const output_mode = comp.config.output_mode; + return .{ + .image_base = options.image_base orelse switch (output_mode) { + .Exe => switch (target.cpu.arch) { + .aarch64, .x86_64 => 0x140000000, + .thumb, .x86 => 0x400000, + else => unreachable, + }, + .Lib => switch (target.cpu.arch) { + .aarch64, .x86_64 => 0x180000000, + .thumb, .x86 => 0x10000000, + else => unreachable, + }, + .Obj => 0, + }, + .entry = options.entry, + .pdb_out_path = options.pdb_out_path, + .repro = options.repro, + .tsaware = options.tsaware, + .nxcompat = options.nxcompat, + .dynamicbase = options.dynamicbase, + .major_subsystem_version = options.major_subsystem_version orelse 6, + .minor_subsystem_version = options.minor_subsystem_version orelse 0, + .lib_directories = options.lib_directories, + .module_definition_file = options.module_definition_file, + // Subsystem depends on the set of public symbol names from linked objects. + // See LinkerDriver::inferSubsystem from the LLD project for the flow chart. + .subsystem = options.subsystem, + // These flags are initially all `false`; the LLVM backend populates them when it learns about exports. + .lld_export_flags = .{ + .c_main = false, + .winmain = false, + .wwinmain = false, + .winmain_crt_startup = false, + .wwinmain_crt_startup = false, + .dllmain_crt_startup = false, + }, + }; + } +}; +pub const Elf = struct { + entry_name: ?[]const u8, + hash_style: HashStyle, + image_base: u64, + linker_script: ?[]const u8, + version_script: ?[]const u8, + sort_section: ?SortSection, + print_icf_sections: bool, + print_map: bool, + emit_relocs: bool, + z_nodelete: bool, + z_notext: bool, + z_defs: bool, + z_origin: bool, + z_nocopyreloc: bool, + z_now: bool, + z_relro: bool, + z_common_page_size: ?u64, + z_max_page_size: ?u64, + rpath_list: []const []const u8, + symbol_wrap_set: []const []const u8, + soname: ?[]const u8, + allow_undefined_version: bool, + enable_new_dtags: ?bool, + compress_debug_sections: CompressDebugSections, + bind_global_refs_locally: bool, + pub const HashStyle = enum { sysv, gnu, both }; + pub const SortSection = enum { name, alignment }; + pub const CompressDebugSections = enum { none, zlib, zstd }; + + fn init(comp: *Compilation, options: link.File.OpenOptions) !Elf { + const PtrWidth = enum { p32, p64 }; + const target = comp.root_mod.resolved_target.result; + const output_mode = comp.config.output_mode; + const is_dyn_lib = output_mode == .Lib and comp.config.link_mode == .dynamic; + const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { + 0...32 => .p32, + 33...64 => .p64, + else => return error.UnsupportedElfArchitecture, + }; + const default_entry_name: []const u8 = switch (target.cpu.arch) { + .mips, .mipsel, .mips64, .mips64el => "__start", + else => "_start", + }; + return .{ + .entry_name = switch (options.entry) { + .disabled => null, + .default => if (output_mode != .Exe) null else default_entry_name, + .enabled => default_entry_name, + .named => |name| name, + }, + .hash_style = options.hash_style, + .image_base = b: { + if (is_dyn_lib) break :b 0; + if (output_mode == .Exe and comp.config.pie) break :b 0; + break :b options.image_base orelse switch (ptr_width) { + .p32 => 0x10000, + .p64 => 0x1000000, + }; + }, + .linker_script = options.linker_script, + .version_script = options.version_script, + .sort_section = options.sort_section, + .print_icf_sections = options.print_icf_sections, + .print_map = options.print_map, + .emit_relocs = options.emit_relocs, + .z_nodelete = options.z_nodelete, + .z_notext = options.z_notext, + .z_defs = options.z_defs, + .z_origin = options.z_origin, + .z_nocopyreloc = options.z_nocopyreloc, + .z_now = options.z_now, + .z_relro = options.z_relro, + .z_common_page_size = options.z_common_page_size, + .z_max_page_size = options.z_max_page_size, + .rpath_list = options.rpath_list, + .symbol_wrap_set = options.symbol_wrap_set.keys(), + .soname = options.soname, + .allow_undefined_version = options.allow_undefined_version, + .enable_new_dtags = options.enable_new_dtags, + .compress_debug_sections = options.compress_debug_sections, + .bind_global_refs_locally = options.bind_global_refs_locally, + }; + } +}; +const Wasm = struct { + /// Symbol name of the entry function to export + entry_name: ?[]const u8, + /// When true, will import the function table from the host environment. + import_table: bool, + /// When true, will export the function table to the host environment. + export_table: bool, + /// When defined, sets the initial memory size of the memory. + initial_memory: ?u64, + /// When defined, sets the maximum memory size of the memory. + max_memory: ?u64, + /// When defined, sets the start of the data section. + global_base: ?u64, + /// Set of *global* symbol names to export to the host environment. + export_symbol_names: []const []const u8, + /// When true, will allow undefined symbols + import_symbols: bool, + fn init(comp: *Compilation, options: link.File.OpenOptions) !Wasm { + const default_entry_name: []const u8 = switch (comp.config.wasi_exec_model) { + .reactor => "_initialize", + .command => "_start", + }; + return .{ + .entry_name = switch (options.entry) { + .disabled => null, + .default => if (comp.config.output_mode != .Exe) null else default_entry_name, + .enabled => default_entry_name, + .named => |name| name, + }, + .import_table = options.import_table, + .export_table = options.export_table, + .initial_memory = options.initial_memory, + .max_memory = options.max_memory, + .global_base = options.global_base, + .export_symbol_names = options.export_symbol_names, + .import_symbols = options.import_symbols, + }; + } +}; + +pub fn createEmpty( + arena: Allocator, + comp: *Compilation, + emit: Cache.Path, + options: link.File.OpenOptions, +) !*Lld { + const target = comp.root_mod.resolved_target.result; + const output_mode = comp.config.output_mode; + const optimize_mode = comp.root_mod.optimize_mode; + const is_native_os = comp.root_mod.resolved_target.is_native_os; + + const obj_file_ext: []const u8 = switch (target.ofmt) { + .coff => "obj", + .elf, .wasm => "o", + else => unreachable, + }; + const gc_sections: bool = options.gc_sections orelse switch (target.ofmt) { + .coff => optimize_mode != .Debug, + .elf => optimize_mode != .Debug and output_mode != .Obj, + .wasm => output_mode != .Obj, + else => unreachable, + }; + const stack_size: u64 = options.stack_size orelse default: { + if (target.ofmt == .wasm and target.os.tag == .freestanding) + break :default 1 * 1024 * 1024; // 1 MiB + break :default 16 * 1024 * 1024; // 16 MiB + }; + + const lld = try arena.create(Lld); + lld.* = .{ + .base = .{ + .tag = .lld, + .comp = comp, + .emit = emit, + .zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }), + .gc_sections = gc_sections, + .print_gc_sections = options.print_gc_sections, + .stack_size = stack_size, + .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os, + .file = null, + .build_id = options.build_id, + }, + .disable_caching = options.disable_lld_caching, + .ofmt = switch (target.ofmt) { + .coff => .{ .coff = try .init(comp, options) }, + .elf => .{ .elf = try .init(comp, options) }, + .wasm => .{ .wasm = try .init(comp, options) }, + else => unreachable, + }, + }; + return lld; +} +pub fn deinit(lld: *Lld) void { + _ = lld; +} +pub fn flush( + lld: *Lld, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.File.FlushError!void { + dev.check(.lld_linker); + _ = tid; + + const tracy = trace(@src()); + defer tracy.end(); + + const sub_prog_node = prog_node.start("LLD Link", 0); + defer sub_prog_node.end(); + + const comp = lld.base.comp; + const result = if (comp.config.output_mode == .Lib and comp.config.link_mode == .static) r: { + break :r linkAsArchive(lld, arena); + } else switch (lld.ofmt) { + .coff => coffLink(lld, arena), + .elf => elfLink(lld, arena), + .wasm => wasmLink(lld, arena), + }; + result catch |err| switch (err) { + error.OutOfMemory, error.LinkFailure => |e| return e, + else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), + }; +} + +fn linkAsArchive(lld: *Lld, arena: Allocator) !void { + const base = &lld.base; + const comp = base.comp; + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + const full_out_path_z = try arena.dupeZ(u8, full_out_path); + const opt_zcu = comp.zcu; + + // If there is no Zig code to compile, then we should skip flushing the output file + // because it will not be part of the linker line anyway. + const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { + const dirname = fs.path.dirname(full_out_path_z) orelse "."; + break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else null; + + log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"}); + + const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj) + comp.compiler_rt_obj.?.full_object_path + else + null; + + const ubsan_rt_path: ?Cache.Path = if (comp.ubsan_rt_strat == .obj) + comp.ubsan_rt_obj.?.full_object_path + else + null; + + // This function follows the same pattern as link.Elf.linkWithLLD so if you want some + // insight as to what's going on here you can read that function body which is more + // well-commented. + + const id_symlink_basename = "llvm-ar.id"; + + var man: Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + const link_inputs = comp.link_inputs; + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + base.releaseLock(); + + try link.hashInputs(&man, link_inputs); + + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + for (comp.win32_resource_table.keys()) |key| { + _ = try man.addFile(key.status.success.res_path, null); + } + try man.addOptionalFile(zcu_obj_path); + try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| b: { + log.debug("archive new_digest={s} readFile error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + break :b prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("archive digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + base.lock = man.toOwnedLock(); + return; + } + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty; + + try object_files.ensureUnusedCapacity(arena, link_inputs.len); + for (link_inputs) |input| { + object_files.appendAssumeCapacity(try input.path().?.toStringZ(arena)); + } + + try object_files.ensureUnusedCapacity(arena, comp.c_object_table.count() + + comp.win32_resource_table.count() + 2); + + for (comp.c_object_table.keys()) |key| { + object_files.appendAssumeCapacity(try key.status.success.object_path.toStringZ(arena)); + } + for (comp.win32_resource_table.keys()) |key| { + object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path)); + } + if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); + if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); + if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); + + if (comp.verbose_link) { + std.debug.print("ar rcs {s}", .{full_out_path_z}); + for (object_files.items) |arg| { + std.debug.print(" {s}", .{arg}); + } + std.debug.print("\n", .{}); + } + + const llvm_bindings = @import("../codegen/llvm/bindings.zig"); + const llvm = @import("../codegen/llvm.zig"); + const target = comp.root_mod.resolved_target.result; + llvm.initializeLLVMTarget(target.cpu.arch); + const bad = llvm_bindings.WriteArchive( + full_out_path_z, + object_files.items.ptr, + object_files.items.len, + switch (target.os.tag) { + .aix => .AIXBIG, + .windows => .COFF, + else => if (target.os.tag.isDarwin()) .DARWIN else .GNU, + }, + ); + if (bad) return error.UnableToWriteArchive; + + if (!lld.disable_caching) { + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)}); + }; + + if (man.have_exclusive_lock) { + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)}); + }; + } + + base.lock = man.toOwnedLock(); + } +} + +fn coffLink(lld: *Lld, arena: Allocator) !void { + const comp = lld.base.comp; + const gpa = comp.gpa; + const base = &lld.base; + const coff = &lld.ofmt.coff; + + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else { + break :p base.zcu_object_sub_path.?; + } + } else null; + + const is_lib = comp.config.output_mode == .Lib; + const is_dyn_lib = comp.config.link_mode == .dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or comp.config.output_mode == .Exe; + const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib; + const target = comp.root_mod.resolved_target.result; + const optimize_mode = comp.root_mod.optimize_mode; + const entry_name: ?[]const u8 = switch (coff.entry) { + // This logic isn't quite right for disabled or enabled. No point in fixing it + // when the goal is to eliminate dependency on LLD anyway. + // https://github.com/ziglang/zig/issues/17751 + .disabled, .default, .enabled => null, + .named => |name| name, + }; + + // See link/Elf.zig for comments on how this mechanism works. + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 14); + + try link.hashInputs(&man, comp.link_inputs); + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + for (comp.win32_resource_table.keys()) |key| { + _ = try man.addFile(key.status.success.res_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptionalBytes(entry_name); + man.hash.add(base.stack_size); + man.hash.add(coff.image_base); + man.hash.add(base.build_id); + { + // TODO remove this, libraries must instead be resolved by the frontend. + for (coff.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); + } + man.hash.add(comp.skip_linker_dependencies); + if (comp.config.link_libc) { + man.hash.add(comp.libc_installation != null); + if (comp.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + if (target.abi == .msvc or target.abi == .itanium) { + man.hash.addBytes(libc_installation.msvc_lib_dir.?); + man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + } + } + } + man.hash.addListOfBytes(comp.windows_libs.keys()); + man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); + man.hash.addOptional(coff.subsystem); + man.hash.add(comp.config.is_test); + man.hash.add(coff.tsaware); + man.hash.add(coff.nxcompat); + man.hash.add(coff.dynamicbase); + man.hash.add(base.allow_shlib_undefined); + // strip does not need to go into the linker hash because it is part of the hash namespace + man.hash.add(coff.major_subsystem_version); + man.hash.add(coff.minor_subsystem_version); + man.hash.add(coff.repro); + man.hash.addOptional(comp.version); + try man.addOptionalFile(coff.module_definition_file); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (comp.config.output_mode == .Obj) { + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk Cache.Path.initCwd(p); + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + try std.fs.Dir.copyFile( + the_object_path.root_dir.handle, + the_object_path.sub_path, + directory.handle, + base.emit.sub_path, + .{}, + ); + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + const linker_command = "lld-link"; + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + + if (target.isMinGW()) { + try argv.append("-lldmingw"); + } + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (comp.config.debug_format != .strip) { + try argv.append("-DEBUG"); + + const out_ext = std.fs.path.extension(full_out_path); + const out_pdb = coff.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{ + full_out_path[0 .. full_out_path.len - out_ext.len], + }); + const out_pdb_basename = std.fs.path.basename(out_pdb); + + try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb})); + try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb_basename})); + } + if (comp.version) |version| { + try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor })); + } + + if (target_util.llvmMachineAbi(target)) |mabi| { + try argv.append(try allocPrint(arena, "-MLLVM:-target-abi={s}", .{mabi})); + } + + try argv.append(try allocPrint(arena, "-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"})); + + if (comp.config.lto != .none) { + switch (optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } + if (comp.config.output_mode == .Exe) { + try argv.append(try allocPrint(arena, "-STACK:{d}", .{base.stack_size})); + } + try argv.append(try allocPrint(arena, "-BASE:{d}", .{coff.image_base})); + + switch (base.build_id) { + .none => try argv.append("-BUILD-ID:NO"), + .fast => try argv.append("-BUILD-ID"), + .uuid, .sha1, .md5, .hexstring => {}, + } + + if (target.cpu.arch == .x86) { + try argv.append("-MACHINE:X86"); + } else if (target.cpu.arch == .x86_64) { + try argv.append("-MACHINE:X64"); + } else if (target.cpu.arch == .thumb) { + try argv.append("-MACHINE:ARM"); + } else if (target.cpu.arch == .aarch64) { + try argv.append("-MACHINE:ARM64"); + } + + for (comp.force_undefined_symbols.keys()) |symbol| { + try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + if (entry_name) |name| { + try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name})); + } + + if (coff.repro) { + try argv.append("-BREPRO"); + } + + if (coff.tsaware) { + try argv.append("-tsaware"); + } + if (coff.nxcompat) { + try argv.append("-nxcompat"); + } + if (!coff.dynamicbase) { + try argv.append("-dynamicbase:NO"); + } + if (base.allow_shlib_undefined) { + try argv.append("-FORCE:UNRESOLVED"); + } + + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + + if (comp.implib_emit) |emit| { + const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + + if (comp.config.link_libc) { + if (comp.libc_installation) |libc_installation| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + + if (target.abi == .msvc or target.abi == .itanium) { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); + } + } + } + + for (coff.lib_directories) |lib_directory| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."})); + } + + try argv.ensureUnusedCapacity(comp.link_inputs.len); + for (comp.link_inputs) |link_input| switch (link_input) { + .dso_exact => unreachable, // not applicable to PE/COFF + inline .dso, .res => |x| { + argv.appendAssumeCapacity(try x.path.toString(arena)); + }, + .object, .archive => |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Cache.Path, obj.path)})); + } else { + argv.appendAssumeCapacity(try obj.path.toString(arena)); + } + }, + }; + + for (comp.c_object_table.keys()) |key| { + try argv.append(try key.status.success.object_path.toString(arena)); + } + + for (comp.win32_resource_table.keys()) |key| { + try argv.append(key.status.success.res_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + if (coff.module_definition_file) |def| { + try argv.append(try allocPrint(arena, "-DEF:{s}", .{def})); + } + + const resolved_subsystem: ?std.Target.SubSystem = blk: { + if (coff.subsystem) |explicit| break :blk explicit; + switch (target.os.tag) { + .windows => { + if (comp.zcu != null) { + if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (coff.lld_export_flags.c_main or comp.config.is_test or + coff.lld_export_flags.winmain_crt_startup or + coff.lld_export_flags.wwinmain_crt_startup) + { + break :blk .Console; + } + if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) + break :blk .Windows; + } + }, + .uefi => break :blk .EfiApplication, + else => {}, + } + break :blk null; + }; + + const Mode = enum { uefi, win32 }; + const mode: Mode = mode: { + if (resolved_subsystem) |subsystem| { + const subsystem_suffix = try allocPrint(arena, ",{d}.{d}", .{ + coff.major_subsystem_version, coff.minor_subsystem_version, + }); + + switch (subsystem) { + .Console => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .EfiApplication => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRom => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .Native => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Posix => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Windows => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + } + } else if (target.os.tag == .uefi) { + break :mode .uefi; + } else { + break :mode .win32; + } + }; + + switch (mode) { + .uefi => try argv.appendSlice(&[_][]const u8{ + "-BASE:0", + "-ENTRY:EfiMain", + "-OPT:REF", + "-SAFESEH:NO", + "-MERGE:.rdata=.data", + "-NODEFAULTLIB", + "-SECTION:.xdata,D", + }), + .win32 => { + if (link_in_crt) { + if (target.abi.isGnu()) { + if (target.cpu.arch == .x86) { + try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); + } else { + try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); + } + + if (is_dyn_lib) { + try argv.append(try comp.crtFileAsString(arena, "dllcrt2.obj")); + if (target.cpu.arch == .x86) { + try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); + } else { + try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); + } + } else { + try argv.append(try comp.crtFileAsString(arena, "crt2.obj")); + } + + try argv.append(try comp.crtFileAsString(arena, "libmingw32.lib")); + } else { + try argv.append(switch (comp.config.link_mode) { + .static => "libcmt.lib", + .dynamic => "msvcrt.lib", + }); + + const lib_str = switch (comp.config.link_mode) { + .static => "lib", + .dynamic => "", + }; + try argv.append(try allocPrint(arena, "{s}vcruntime.lib", .{lib_str})); + try argv.append(try allocPrint(arena, "{s}ucrt.lib", .{lib_str})); + + //Visual C++ 2015 Conformance Changes + //https://msdn.microsoft.com/en-us/library/bb531344.aspx + try argv.append("legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 and ntdll + try argv.append("kernel32.lib"); + try argv.append("ntdll.lib"); + } + } else { + try argv.append("-NODEFAULTLIB"); + if (!is_lib and entry_name == null) { + if (comp.zcu != null) { + if (coff.lld_export_flags.winmain_crt_startup) { + try argv.append("-ENTRY:WinMainCRTStartup"); + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } + } + }, + } + + if (comp.config.link_libc and link_in_crt) { + if (comp.zigc_static_lib) |zigc| { + try argv.append(try zigc.full_object_path.toString(arena)); + } + } + + // libc++ dep + if (comp.config.link_libcpp) { + try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); + try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); + } + + // libunwind dep + if (comp.config.link_libunwind) { + try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); + } + + if (comp.config.any_fuzz) { + try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); + } + + const ubsan_rt_path: ?Cache.Path = blk: { + if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; + if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + if (ubsan_rt_path) |path| { + try argv.append(try path.toString(arena)); + } + + if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { + // MSVC compiler_rt is missing some stuff, so we build it unconditionally but + // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. + if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); + if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); + } + + try argv.ensureUnusedCapacity(comp.windows_libs.count()); + for (comp.windows_libs.keys()) |key| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); + continue; + } + if (try findLib(arena, lib_basename, coff.lib_directories)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + if (target.abi.isGnu()) { + const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); + if (try findLib(arena, fallback_name, coff.lib_directories)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + } + if (target.abi == .msvc or target.abi == .itanium) { + argv.appendAssumeCapacity(lib_basename); + continue; + } + + log.err("DLL import library for -l{s} not found", .{key}); + return error.DllImportLibraryNotFound; + } + + try spawnLld(comp, arena, argv.items); + } + + if (!lld.disable_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + base.lock = man.toOwnedLock(); + } +} +fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { + for (lib_directories) |lib_directory| { + lib_directory.handle.access(name, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + return try lib_directory.join(arena, &.{name}); + } + return null; +} + +fn elfLink(lld: *Lld, arena: Allocator) !void { + const comp = lld.base.comp; + const gpa = comp.gpa; + const diags = &comp.link_diags; + const base = &lld.base; + const elf = &lld.ofmt.elf; + + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else { + break :p base.zcu_object_sub_path.?; + } + } else null; + + const output_mode = comp.config.output_mode; + const is_obj = output_mode == .Obj; + const is_lib = output_mode == .Lib; + const link_mode = comp.config.link_mode; + const is_dyn_lib = link_mode == .dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; + const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib; + const target = comp.root_mod.resolved_target.result; + const compiler_rt_path: ?Cache.Path = blk: { + if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; + if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + const ubsan_rt_path: ?Cache.Path = blk: { + if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; + if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + + // Here we want to determine whether we can save time by not invoking LLD when the + // output is unchanged. None of the linker options or the object files that are being + // linked are in the hash that namespaces the directory we are outputting to. Therefore, + // we must hash those now, and the resulting digest will form the "id" of the linking + // job we are about to perform. + // After a successful link, we store the id in the metadata of a symlink named "lld.id" in + // the artifact directory. So, now, we check if this symlink exists, and if it matches + // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD. + const id_symlink_basename = "lld.id"; + + var man: std.Build.Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + var digest: [std.Build.Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 14); + + try man.addOptionalFile(elf.linker_script); + try man.addOptionalFile(elf.version_script); + man.hash.add(elf.allow_undefined_version); + man.hash.addOptional(elf.enable_new_dtags); + try link.hashInputs(&man, comp.link_inputs); + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); + try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null); + try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null); + + // We can skip hashing libc and libc++ components that we are in charge of building from Zig + // installation sources because they are always a product of the compiler version + target information. + man.hash.addOptionalBytes(elf.entry_name); + man.hash.add(elf.image_base); + man.hash.add(base.gc_sections); + man.hash.addOptional(elf.sort_section); + man.hash.add(comp.link_eh_frame_hdr); + man.hash.add(elf.emit_relocs); + man.hash.add(comp.config.rdynamic); + man.hash.addListOfBytes(elf.rpath_list); + if (output_mode == .Exe) { + man.hash.add(base.stack_size); + } + man.hash.add(base.build_id); + man.hash.addListOfBytes(elf.symbol_wrap_set); + man.hash.add(comp.skip_linker_dependencies); + man.hash.add(elf.z_nodelete); + man.hash.add(elf.z_notext); + man.hash.add(elf.z_defs); + man.hash.add(elf.z_origin); + man.hash.add(elf.z_nocopyreloc); + man.hash.add(elf.z_now); + man.hash.add(elf.z_relro); + man.hash.add(elf.z_common_page_size orelse 0); + man.hash.add(elf.z_max_page_size orelse 0); + man.hash.add(elf.hash_style); + // strip does not need to go into the linker hash because it is part of the hash namespace + if (comp.config.link_libc) { + man.hash.add(comp.libc_installation != null); + if (comp.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + } + } + if (have_dynamic_linker) { + man.hash.addOptionalBytes(target.dynamic_linker.get()); + } + man.hash.addOptionalBytes(elf.soname); + man.hash.addOptional(comp.version); + man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); + man.hash.add(base.allow_shlib_undefined); + man.hash.add(elf.bind_global_refs_locally); + man.hash.add(elf.compress_debug_sections); + man.hash.add(comp.config.any_sanitize_thread); + man.hash.add(comp.config.any_fuzz); + man.hash.addOptionalBytes(comp.sysroot); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = std.Build.Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("ELF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("ELF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + base.lock = man.toOwnedLock(); + return; + } + log.debug("ELF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + // Due to a deficiency in LLD, we need to special-case BPF to a simple file + // copy when generating relocatables. Normally, we would expect `lld -r` to work. + // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails + // before even generating the relocatable. + // + // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can + // produce usable object files. + if (output_mode == .Obj and + (comp.config.lto != .none or + target.cpu.arch.isBpf() or + target.cpu.arch == .lanai or + target.cpu.arch == .m68k or + target.cpu.arch.isSPARC() or + target.cpu.arch == .ve or + target.cpu.arch == .xcore)) + { + // In this case we must do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk Cache.Path.initCwd(p); + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + try std.fs.Dir.copyFile( + the_object_path.root_dir.handle, + the_object_path.sub_path, + directory.handle, + base.emit.sub_path, + .{}, + ); + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + const linker_command = "ld.lld"; + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + if (is_obj) { + try argv.append("-r"); + } + + try argv.append("--error-limit=0"); + + if (comp.sysroot) |sysroot| { + try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot})); + } + + if (target_util.llvmMachineAbi(target)) |mabi| { + try argv.appendSlice(&.{ + "-mllvm", + try std.fmt.allocPrint(arena, "-target-abi={s}", .{mabi}), + }); + } + + try argv.appendSlice(&.{ + "-mllvm", + try std.fmt.allocPrint(arena, "-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}), + }); + + if (comp.config.lto != .none) { + switch (comp.root_mod.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("--lto-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"), + } + } + switch (comp.root_mod.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + + if (elf.entry_name) |name| { + try argv.appendSlice(&.{ "--entry", name }); + } + + for (comp.force_undefined_symbols.keys()) |sym| { + try argv.append("-u"); + try argv.append(sym); + } + + switch (elf.hash_style) { + .gnu => try argv.append("--hash-style=gnu"), + .sysv => try argv.append("--hash-style=sysv"), + .both => {}, // this is the default + } + + if (output_mode == .Exe) { + try argv.appendSlice(&.{ + "-z", + try std.fmt.allocPrint(arena, "stack-size={d}", .{base.stack_size}), + }); + } + + switch (base.build_id) { + .none => try argv.append("--build-id=none"), + .fast, .uuid, .sha1, .md5 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ + @tagName(base.build_id), + })), + .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + })), + } + + try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{elf.image_base})); + + if (elf.linker_script) |linker_script| { + try argv.append("-T"); + try argv.append(linker_script); + } + + if (elf.sort_section) |how| { + const arg = try std.fmt.allocPrint(arena, "--sort-section={s}", .{@tagName(how)}); + try argv.append(arg); + } + + if (base.gc_sections) { + try argv.append("--gc-sections"); + } + + if (base.print_gc_sections) { + try argv.append("--print-gc-sections"); + } + + if (elf.print_icf_sections) { + try argv.append("--print-icf-sections"); + } + + if (elf.print_map) { + try argv.append("--print-map"); + } + + if (comp.link_eh_frame_hdr) { + try argv.append("--eh-frame-hdr"); + } + + if (elf.emit_relocs) { + try argv.append("--emit-relocs"); + } + + if (comp.config.rdynamic) { + try argv.append("--export-dynamic"); + } + + if (comp.config.debug_format == .strip) { + try argv.append("-s"); + } + + if (elf.z_nodelete) { + try argv.append("-z"); + try argv.append("nodelete"); + } + if (elf.z_notext) { + try argv.append("-z"); + try argv.append("notext"); + } + if (elf.z_defs) { + try argv.append("-z"); + try argv.append("defs"); + } + if (elf.z_origin) { + try argv.append("-z"); + try argv.append("origin"); + } + if (elf.z_nocopyreloc) { + try argv.append("-z"); + try argv.append("nocopyreloc"); + } + if (elf.z_now) { + // LLD defaults to -zlazy + try argv.append("-znow"); + } + if (!elf.z_relro) { + // LLD defaults to -zrelro + try argv.append("-znorelro"); + } + if (elf.z_common_page_size) |size| { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "common-page-size={d}", .{size})); + } + if (elf.z_max_page_size) |size| { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "max-page-size={d}", .{size})); + } + + if (getLDMOption(target)) |ldm| { + try argv.append("-m"); + try argv.append(ldm); + } + + if (link_mode == .static) { + if (target.cpu.arch.isArm()) { + try argv.append("-Bstatic"); + } else { + try argv.append("-static"); + } + } else if (switch (target.os.tag) { + else => is_dyn_lib, + .haiku => is_exe_or_dyn_lib, + }) { + try argv.append("-shared"); + } + + if (comp.config.pie and output_mode == .Exe) { + try argv.append("-pie"); + } + + if (is_exe_or_dyn_lib and target.os.tag == .netbsd) { + // Add options to produce shared objects with only 2 PT_LOAD segments. + // NetBSD expects 2 PT_LOAD segments in a shared object, otherwise + // ld.elf_so fails loading dynamic libraries with "not found" error. + // See https://github.com/ziglang/zig/issues/9109 . + try argv.append("--no-rosegment"); + try argv.append("-znorelro"); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + // csu prelude + const csu = try comp.getCrtPaths(arena); + if (csu.crt0) |p| try argv.append(try p.toString(arena)); + if (csu.crti) |p| try argv.append(try p.toString(arena)); + if (csu.crtbegin) |p| try argv.append(try p.toString(arena)); + + for (elf.rpath_list) |rpath| { + try argv.appendSlice(&.{ "-rpath", rpath }); + } + + for (elf.symbol_wrap_set) |symbol_name| { + try argv.appendSlice(&.{ "-wrap", symbol_name }); + } + + if (comp.config.link_libc) { + if (comp.libc_installation) |libc_installation| { + try argv.append("-L"); + try argv.append(libc_installation.crt_dir.?); + } + } + + if (have_dynamic_linker and + (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker)) + { + if (target.dynamic_linker.get()) |dynamic_linker| { + try argv.append("-dynamic-linker"); + try argv.append(dynamic_linker); + } + } + + if (is_dyn_lib) { + if (elf.soname) |soname| { + try argv.append("-soname"); + try argv.append(soname); + } + if (elf.version_script) |version_script| { + try argv.append("-version-script"); + try argv.append(version_script); + } + if (elf.allow_undefined_version) { + try argv.append("--undefined-version"); + } else { + try argv.append("--no-undefined-version"); + } + if (elf.enable_new_dtags) |enable_new_dtags| { + if (enable_new_dtags) { + try argv.append("--enable-new-dtags"); + } else { + try argv.append("--disable-new-dtags"); + } + } + } + + // Positional arguments to the linker such as object files. + var whole_archive = false; + + for (base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, // Windows-only + .dso => continue, + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso_exact => |dso_exact| { + assert(dso_exact.name[0] == ':'); + try argv.appendSlice(&.{ "-l", dso_exact.name }); + }, + }; + + if (whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(try key.status.success.object_path.toString(arena)); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + if (comp.tsan_lib) |lib| { + assert(comp.config.any_sanitize_thread); + try argv.append(try lib.full_object_path.toString(arena)); + } + + if (comp.fuzzer_lib) |lib| { + assert(comp.config.any_fuzz); + try argv.append(try lib.full_object_path.toString(arena)); + } + + if (ubsan_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + + // Shared libraries. + if (is_exe_or_dyn_lib) { + // Worst-case, we need an --as-needed argument for every lib, as well + // as one before and one after. + try argv.ensureUnusedCapacity(2 * base.comp.link_inputs.len + 2); + argv.appendAssumeCapacity("--as-needed"); + var as_needed = true; + + for (base.comp.link_inputs) |link_input| switch (link_input) { + .res => unreachable, // Windows-only + .object, .archive, .dso_exact => continue, + .dso => |dso| { + const lib_as_needed = !dso.needed; + switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { + 0b00, 0b11 => {}, + 0b01 => { + argv.appendAssumeCapacity("--no-as-needed"); + as_needed = false; + }, + 0b10 => { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + }, + } + + // By this time, we depend on these libs being dynamically linked + // libraries and not static libraries (the check for that needs to be earlier), + // but they could be full paths to .so files, in which case we + // want to avoid prepending "-l". + argv.appendAssumeCapacity(try dso.path.toString(arena)); + }, + }; + + if (!as_needed) { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + } + + // libc++ dep + if (comp.config.link_libcpp) { + try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); + try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); + } + + // libunwind dep + if (comp.config.link_libunwind) { + try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); + } + + // libc dep + diags.flags.missing_libc = false; + if (comp.config.link_libc) { + if (comp.libc_installation != null) { + const needs_grouping = link_mode == .static; + if (needs_grouping) try argv.append("--start-group"); + try argv.appendSlice(target_util.libcFullLinkFlags(target)); + if (needs_grouping) try argv.append("--end-group"); + } else if (target.isGnuLibC()) { + for (glibc.libs) |lib| { + if (lib.removed_in) |rem_in| { + if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue; + } + + const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a")); + } else if (target.isMuslLibC()) { + try argv.append(try comp.crtFileAsString(arena, switch (link_mode) { + .static => "libc.a", + .dynamic => "libc.so", + })); + } else if (target.isFreeBSDLibC()) { + for (freebsd.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ + comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + } else if (target.isNetBSDLibC()) { + for (netbsd.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{ + comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + } else { + diags.flags.missing_libc = true; + } + + if (comp.zigc_static_lib) |zigc| { + try argv.append(try zigc.full_object_path.toString(arena)); + } + } + } + + // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs + // to be after the shared libraries, so they are picked up from the shared + // libraries, not libcompiler_rt. + if (compiler_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + + // crt postlude + if (csu.crtend) |p| try argv.append(try p.toString(arena)); + if (csu.crtn) |p| try argv.append(try p.toString(arena)); + + if (base.allow_shlib_undefined) { + try argv.append("--allow-shlib-undefined"); + } + + switch (elf.compress_debug_sections) { + .none => {}, + .zlib => try argv.append("--compress-debug-sections=zlib"), + .zstd => try argv.append("--compress-debug-sections=zstd"), + } + + if (elf.bind_global_refs_locally) { + try argv.append("-Bsymbolic"); + } + + try spawnLld(comp, arena, argv.items); + } + + if (!lld.disable_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + std.Build.Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + base.lock = man.toOwnedLock(); + } +} +fn getLDMOption(target: std.Target) ?[]const u8 { + // This should only return emulations understood by LLD's parseEmulation(). + return switch (target.cpu.arch) { + .aarch64 => switch (target.os.tag) { + .linux => "aarch64linux", + else => "aarch64elf", + }, + .aarch64_be => switch (target.os.tag) { + .linux => "aarch64linuxb", + else => "aarch64elfb", + }, + .amdgcn => "elf64_amdgpu", + .arm, .thumb => switch (target.os.tag) { + .linux => "armelf_linux_eabi", + else => "armelf", + }, + .armeb, .thumbeb => switch (target.os.tag) { + .linux => "armelfb_linux_eabi", + else => "armelfb", + }, + .hexagon => "hexagonelf", + .loongarch32 => "elf32loongarch", + .loongarch64 => "elf64loongarch", + .mips => switch (target.os.tag) { + .freebsd => "elf32btsmip_fbsd", + else => "elf32btsmip", + }, + .mipsel => switch (target.os.tag) { + .freebsd => "elf32ltsmip_fbsd", + else => "elf32ltsmip", + }, + .mips64 => switch (target.os.tag) { + .freebsd => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32btsmipn32_fbsd", + else => "elf64btsmip_fbsd", + }, + else => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32btsmipn32", + else => "elf64btsmip", + }, + }, + .mips64el => switch (target.os.tag) { + .freebsd => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32ltsmipn32_fbsd", + else => "elf64ltsmip_fbsd", + }, + else => switch (target.abi) { + .gnuabin32, .muslabin32 => "elf32ltsmipn32", + else => "elf64ltsmip", + }, + }, + .msp430 => "msp430elf", + .powerpc => switch (target.os.tag) { + .freebsd => "elf32ppc_fbsd", + .linux => "elf32ppclinux", + else => "elf32ppc", + }, + .powerpcle => switch (target.os.tag) { + .linux => "elf32lppclinux", + else => "elf32lppc", + }, + .powerpc64 => "elf64ppc", + .powerpc64le => "elf64lppc", + .riscv32 => "elf32lriscv", + .riscv64 => "elf64lriscv", + .s390x => "elf64_s390", + .sparc64 => "elf64_sparc", + .x86 => switch (target.os.tag) { + .freebsd => "elf_i386_fbsd", + else => "elf_i386", + }, + .x86_64 => switch (target.abi) { + .gnux32, .muslx32 => "elf32_x86_64", + else => "elf_x86_64", + }, + else => null, + }; +} +fn wasmLink(lld: *Lld, arena: Allocator) !void { + const comp = lld.base.comp; + const shared_memory = comp.config.shared_memory; + const export_memory = comp.config.export_memory; + const import_memory = comp.config.import_memory; + const target = comp.root_mod.resolved_target.result; + const base = &lld.base; + const wasm = &lld.ofmt.wasm; + + const gpa = comp.gpa; + + const directory = base.emit.root_dir; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + } else { + break :p base.zcu_object_sub_path.?; + } + } else null; + + const is_obj = comp.config.output_mode == .Obj; + const compiler_rt_path: ?Cache.Path = blk: { + if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; + if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; + break :blk null; + }; + const ubsan_rt_path: ?Cache.Path = blk: { + if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; + if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; + break :blk null; + }; + + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!lld.disable_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!lld.disable_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 14); + + try link.hashInputs(&man, comp.link_inputs); + for (comp.c_object_table.keys()) |key| { + _ = try man.addFilePath(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + try man.addOptionalFilePath(compiler_rt_path); + try man.addOptionalFilePath(ubsan_rt_path); + man.hash.addOptionalBytes(wasm.entry_name); + man.hash.add(base.stack_size); + man.hash.add(base.build_id); + man.hash.add(import_memory); + man.hash.add(export_memory); + man.hash.add(wasm.import_table); + man.hash.add(wasm.export_table); + man.hash.addOptional(wasm.initial_memory); + man.hash.addOptional(wasm.max_memory); + man.hash.add(shared_memory); + man.hash.addOptional(wasm.global_base); + man.hash.addListOfBytes(wasm.export_symbol_names); + // strip does not need to go into the linker hash because it is part of the hash namespace + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + base.lock = man.toOwnedLock(); + return; + } + log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (is_obj) { + // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk Cache.Path.initCwd(p); + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + try fs.Dir.copyFile( + the_object_path.root_dir.handle, + the_object_path.sub_path, + directory.handle, + base.emit.sub_path, + .{}, + ); + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + const linker_command = "wasm-ld"; + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + try argv.append("--error-limit=0"); + + if (comp.config.lto != .none) { + switch (comp.root_mod.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } + + if (import_memory) { + try argv.append("--import-memory"); + } + + if (export_memory) { + try argv.append("--export-memory"); + } + + if (wasm.import_table) { + assert(!wasm.export_table); + try argv.append("--import-table"); + } + + if (wasm.export_table) { + assert(!wasm.import_table); + try argv.append("--export-table"); + } + + // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly + // specified it as garbage collection is enabled by default. + if (!base.gc_sections) { + try argv.append("--no-gc-sections"); + } + + if (comp.config.debug_format == .strip) { + try argv.append("-s"); + } + + if (wasm.initial_memory) |initial_memory| { + const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory}); + try argv.append(arg); + } + + if (wasm.max_memory) |max_memory| { + const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory}); + try argv.append(arg); + } + + if (shared_memory) { + try argv.append("--shared-memory"); + } + + if (wasm.global_base) |global_base| { + const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base}); + try argv.append(arg); + } else { + // We prepend it by default, so when a stack overflow happens the runtime will trap correctly, + // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496 + // + // The user can overwrite this behavior by setting the global-base + try argv.append("--stack-first"); + } + + // Users are allowed to specify which symbols they want to export to the wasm host. + for (wasm.export_symbol_names) |symbol_name| { + const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}); + try argv.append(arg); + } + + if (comp.config.rdynamic) { + try argv.append("--export-dynamic"); + } + + if (wasm.entry_name) |entry_name| { + try argv.appendSlice(&.{ "--entry", entry_name }); + } else { + try argv.append("--no-entry"); + } + + try argv.appendSlice(&.{ + "-z", + try std.fmt.allocPrint(arena, "stack-size={d}", .{base.stack_size}), + }); + + switch (base.build_id) { + .none => try argv.append("--build-id=none"), + .fast, .uuid, .sha1 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ + @tagName(base.build_id), + })), + .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + })), + .md5 => {}, + } + + if (wasm.import_symbols) { + try argv.append("--allow-undefined"); + } + + if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) { + try argv.append("--shared"); + } + if (comp.config.pie) { + try argv.append("--pie"); + } + + try argv.appendSlice(&.{ "-o", full_out_path }); + + if (target.cpu.arch == .wasm64) { + try argv.append("-mwasm64"); + } + + const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or + (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); + + if (comp.config.link_libc and is_exe_or_dyn_lib) { + if (target.os.tag == .wasi) { + for (comp.wasi_emulated_libs) |crt_file| { + try argv.append(try comp.crtFileAsString( + arena, + wasi_libc.emulatedLibCRFileLibName(crt_file), + )); + } + + try argv.append(try comp.crtFileAsString( + arena, + wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model), + )); + try argv.append(try comp.crtFileAsString(arena, "libc.a")); + } + + if (comp.zigc_static_lib) |zigc| { + try argv.append(try zigc.full_object_path.toString(arena)); + } + + if (comp.config.link_libcpp) { + try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); + try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); + } + } + + // Positional arguments to the linker such as object files. + var whole_archive = false; + for (comp.link_inputs) |link_input| switch (link_input) { + .object, .archive => |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + try argv.append(try obj.path.toString(arena)); + }, + .dso => |dso| { + try argv.append(try dso.path.toString(arena)); + }, + .dso_exact => unreachable, + .res => unreachable, + }; + if (whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(try key.status.success.object_path.toString(arena)); + } + if (module_obj_path) |p| { + try argv.append(p); + } + + if (compiler_rt_path) |p| { + try argv.append(try p.toString(arena)); + } + + if (ubsan_rt_path) |p| { + try argv.append(try p.toStringZ(arena)); + } + + try spawnLld(comp, arena, argv.items); + + // Give +x to the .wasm file if it is an executable and the OS is WASI. + // Some systems may be configured to execute such binaries directly. Even if that + // is not the case, it means we will get "exec format error" when trying to run + // it, and then can react to that in the same way as trying to run an ELF file + // from a foreign CPU architecture. + if (fs.has_executable_bit and target.os.tag == .wasi and + comp.config.output_mode == .Exe) + { + // TODO: what's our strategy for reporting linker errors from this function? + // report a nice error here with the file path if it fails instead of + // just returning the error code. + // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. + std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { + error.OperationNotSupported => unreachable, // Not a symlink. + else => |e| return e, + }; + } + } + + if (!lld.disable_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + base.lock = man.toOwnedLock(); + } +} + +fn spawnLld( + comp: *Compilation, + arena: Allocator, + argv: []const []const u8, +) !void { + if (comp.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv[1..]); + } + + // If possible, we run LLD as a child process because it does not always + // behave properly as a library, unfortunately. + // https://github.com/ziglang/zig/issues/3825 + if (!std.process.can_spawn) { + const exit_code = try lldMain(arena, argv, false); + if (exit_code == 0) return; + if (comp.clang_passthrough_mode) std.process.exit(exit_code); + return error.LinkFailure; + } + + var stderr: []u8 = &.{}; + defer comp.gpa.free(stderr); + + var child = std.process.Child.init(argv, arena); + const term = (if (comp.clang_passthrough_mode) term: { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + break :term child.spawnAndWait(); + } else term: { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + child.spawn() catch |err| break :term err; + stderr = try child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize)); + break :term child.wait(); + }) catch |first_err| term: { + const err = switch (first_err) { + error.NameTooLong => err: { + const s = fs.path.sep_str; + const rand_int = std.crypto.random.int(u64); + const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; + + const rsp_file = try comp.dirs.local_cache.handle.createFileZ(rsp_path, .{}); + defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err| + log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) }); + { + defer rsp_file.close(); + var rsp_buf = std.io.bufferedWriter(rsp_file.writer()); + const rsp_writer = rsp_buf.writer(); + for (argv[2..]) |arg| { + try rsp_writer.writeByte('"'); + for (arg) |c| { + switch (c) { + '\"', '\\' => try rsp_writer.writeByte('\\'), + else => {}, + } + try rsp_writer.writeByte(c); + } + try rsp_writer.writeByte('"'); + try rsp_writer.writeByte('\n'); + } + try rsp_buf.flush(); + } + + var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint( + arena, + "@{s}", + .{try comp.dirs.local_cache.join(arena, &.{rsp_path})}, + ) }, arena); + if (comp.clang_passthrough_mode) { + rsp_child.stdin_behavior = .Inherit; + rsp_child.stdout_behavior = .Inherit; + rsp_child.stderr_behavior = .Inherit; + + break :term rsp_child.spawnAndWait() catch |err| break :err err; + } else { + rsp_child.stdin_behavior = .Ignore; + rsp_child.stdout_behavior = .Ignore; + rsp_child.stderr_behavior = .Pipe; + + rsp_child.spawn() catch |err| break :err err; + stderr = try rsp_child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize)); + break :term rsp_child.wait() catch |err| break :err err; + } + }, + else => first_err, + }; + log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + const diags = &comp.link_diags; + switch (term) { + .Exited => |code| if (code != 0) { + if (comp.clang_passthrough_mode) std.process.exit(code); + diags.lockAndParseLldStderr(argv[1], stderr); + return error.LinkFailure; + }, + else => { + if (comp.clang_passthrough_mode) std.process.abort(); + return diags.fail("{s} terminated with stderr:\n{s}", .{ argv[0], stderr }); + }, + } + + if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr}); +} + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Cache = std.Build.Cache; +const allocPrint = std.fmt.allocPrint; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const Compilation = @import("../Compilation.zig"); +const Zcu = @import("../Zcu.zig"); +const dev = @import("../dev.zig"); +const freebsd = @import("../libs/freebsd.zig"); +const glibc = @import("../libs/glibc.zig"); +const netbsd = @import("../libs/netbsd.zig"); +const wasi_libc = @import("../libs/wasi_libc.zig"); +const link = @import("../link.zig"); +const lldMain = @import("../main.zig").lldMain; +const target_util = @import("../target.zig"); +const trace = @import("../tracy.zig").trace; +const Lld = @This(); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6667ed6a63..2c30b34215 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -194,7 +194,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = allow_shlib_undefined, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .rpath_list = options.rpath_list, @@ -227,7 +226,7 @@ pub fn createEmpty( self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, - .mode = link.File.determineMode(false, output_mode, link_mode), + .mode = link.File.determineMode(output_mode, link_mode), }); // Append null file @@ -341,15 +340,6 @@ pub fn flush( arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, -) link.File.FlushError!void { - try self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu( - self: *MachO, - arena: Allocator, - tid: Zcu.PerThread.Id, - prog_node: std.Progress.Node, ) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -373,7 +363,7 @@ pub fn flushZcu( // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); - if (self.getZigObject()) |zo| try zo.flushZcu(self, tid); + if (self.getZigObject()) |zo| try zo.flush(self, tid); if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); @@ -617,7 +607,7 @@ pub fn flushZcu( error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to calculate and write uuid: {s}", .{@errorName(e)}), }; - if (self.getDebugSymbols()) |dsym| dsym.flushZcu(self) catch |err| switch (err) { + if (self.getDebugSymbols()) |dsym| dsym.flush(self) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}), }; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 8579863d03..eef3492b48 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -178,7 +178,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 return offset; } -pub fn flushZcu(self: *DebugSymbols, macho_file: *MachO) !void { +pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void { const zo = macho_file.getZigObject().?; for (self.relocs.items) |*reloc| { const sym = zo.symbols.items[reloc.target]; diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 4d99afc61a..13ebb40cf9 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -550,7 +550,7 @@ pub fn getInputSection(self: ZigObject, atom: Atom, macho_file: *MachO) macho.se return sect; } -pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void { +pub fn flush(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void { const diags = &macho_file.base.comp.link_diags; // Handle any lazy symbols that were emitted by incremental compilation. @@ -589,7 +589,7 @@ pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) lin if (self.dwarf) |*dwarf| { const pt: Zcu.PerThread = .activate(macho_file.base.comp.zcu.?, tid); defer pt.deactivate(); - dwarf.flushZcu(pt) catch |err| switch (err) { + dwarf.flush(pt) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to flush dwarf module: {s}", .{@errorName(e)}), }; @@ -599,7 +599,7 @@ pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) lin self.debug_strtab_dirty = false; } - // The point of flushZcu() is to commit changes, so in theory, nothing should + // The point of flush() is to commit changes, so in theory, nothing should // be dirty after this. However, it is possible for some things to remain // dirty because they fail to be written in the event of compile errors, // such as debug_line_header_dirty and debug_info_header_dirty. @@ -1537,7 +1537,7 @@ pub fn getOrCreateMetadataForLazySymbol( } state_ptr.* = .pending_flush; const symbol_index = symbol_index_ptr.*; - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(macho_file, pt, lazy_sym, symbol_index); return symbol_index; } diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 0a940cb0b3..c487169b3f 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -301,7 +301,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .sixtyfour_bit = sixtyfour_bit, @@ -494,7 +493,7 @@ fn updateFinish(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index // write the symbol // we already have the got index const sym: aout.Sym = .{ - .value = undefined, // the value of stuff gets filled in in flushZcu + .value = undefined, // the value of stuff gets filled in in flush .type = atom.type, .name = try gpa.dupe(u8, nav.name.toSlice(ip)), }; @@ -527,25 +526,6 @@ fn allocateGotIndex(self: *Plan9) usize { } } -pub fn flush( - self: *Plan9, - arena: Allocator, - tid: Zcu.PerThread.Id, - prog_node: std.Progress.Node, -) link.File.FlushError!void { - const comp = self.base.comp; - const diags = &comp.link_diags; - const use_lld = build_options.have_llvm and comp.config.use_lld; - assert(!use_lld); - - switch (link.File.effectiveOutputMode(use_lld, comp.config.output_mode)) { - .Exe => {}, - .Obj => return diags.fail("writing plan9 object files unimplemented", .{}), - .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}), - } - return self.flushZcu(arena, tid, prog_node); -} - pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void { if (delta_line > 0 and delta_line < 65) { const toappend = @as(u8, @intCast(delta_line)); @@ -586,7 +566,7 @@ fn atomCount(self: *Plan9) usize { return data_nav_count + fn_nav_count + lazy_atom_count + extern_atom_count + uav_atom_count; } -pub fn flushZcu( +pub fn flush( self: *Plan9, arena: Allocator, /// TODO: stop using this @@ -607,10 +587,16 @@ pub fn flushZcu( const gpa = comp.gpa; const target = comp.root_mod.resolved_target.result; + switch (comp.config.output_mode) { + .Exe => {}, + .Obj => return diags.fail("writing plan9 object files unimplemented", .{}), + .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}), + } + const sub_prog_node = prog_node.start("Flush Module", 0); defer sub_prog_node.end(); - log.debug("flushZcu", .{}); + log.debug("flush", .{}); defer assert(self.hdr.entry != 0x0); @@ -1039,7 +1025,7 @@ pub fn getOrCreateAtomForLazySymbol(self: *Plan9, pt: Zcu.PerThread, lazy_sym: F const atom = atom_ptr.*; _ = try self.getAtomPtr(atom).getOrCreateSymbolTableEntry(self); _ = self.getAtomPtr(atom).getOrCreateOffsetTableEntry(self); - // anyerror needs to be deferred until flushZcu + // anyerror needs to be deferred until flush if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbolAtom(pt, lazy_sym, atom); return atom; } @@ -1182,11 +1168,7 @@ pub fn open( const file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .read = true, - .mode = link.File.determineMode( - use_lld, - comp.config.output_mode, - comp.config.link_mode, - ), + .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode), }); errdefer file.close(); self.base.file = file; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index c6e86895f5..8b6b99525f 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -17,7 +17,7 @@ //! All regular functions. // Because SPIR-V requires re-compilation anyway, and so hot swapping will not work -// anyway, we simply generate all the code in flushZcu. This keeps +// anyway, we simply generate all the code in flush. This keeps // things considerably simpler. const SpirV = @This(); @@ -83,7 +83,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 0, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .object = codegen.Object.init(gpa, comp.getTarget()), @@ -193,18 +192,14 @@ pub fn updateExports( // TODO: Export regular functions, variables, etc using Linkage attributes. } -pub fn flush(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu( +pub fn flush( self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { // The goal is to never use this because it's only needed if we need to - // write to InternPool, but flushZcu is too late to be writing to the + // write to InternPool, but flush is too late to be writing to the // InternPool. _ = tid; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e92be32b7d..5c804ed21f 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -40,7 +40,6 @@ const Zcu = @import("../Zcu.zig"); const codegen = @import("../codegen.zig"); const dev = @import("../dev.zig"); const link = @import("../link.zig"); -const lldMain = @import("../main.zig").lldMain; const trace = @import("../tracy.zig").trace; const wasi_libc = @import("../libs/wasi_libc.zig"); const Value = @import("../Value.zig"); @@ -74,8 +73,6 @@ global_base: ?u64, initial_memory: ?u64, /// When defined, sets the maximum memory size of the memory. max_memory: ?u64, -/// When true, will import the function table from the host environment. -import_table: bool, /// When true, will export the function table to the host environment. export_table: bool, /// Output name of the file @@ -2935,17 +2932,14 @@ pub fn createEmpty( const target = comp.root_mod.resolved_target.result; assert(target.ofmt == .wasm); - const use_lld = build_options.have_llvm and comp.config.use_lld; const use_llvm = comp.config.use_llvm; const output_mode = comp.config.output_mode; const wasi_exec_model = comp.config.wasi_exec_model; - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. // If using LLVM to generate the object file for the zig compilation unit, // we need a place to put the object file so that it can be subsequently // handled. - const zcu_object_sub_path = if (!use_lld and !use_llvm) + const zcu_object_sub_path = if (!use_llvm) null else try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); @@ -2970,13 +2964,11 @@ pub fn createEmpty( }, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, .name = undefined, .string_table = .empty, .string_bytes = .empty, - .import_table = options.import_table, .export_table = options.export_table, .import_symbols = options.import_symbols, .export_symbol_names = options.export_symbol_names, @@ -3004,17 +2996,7 @@ pub fn createEmpty( .named => |name| (try wasm.internString(name)).toOptional(), }; - if (use_lld and (use_llvm or !comp.config.have_zcu)) { - // LLVM emits the object file (if any); LLD links it into the final product. - return wasm; - } - - // What path should this Wasm linker code output to? - // If using LLD to link, this code should produce an object file so that it - // can be passed to LLD. - const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path; - - wasm.base.file = try emit.root_dir.handle.createFile(sub_path, .{ + wasm.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, .mode = if (fs.has_executable_bit) @@ -3025,7 +3007,7 @@ pub fn createEmpty( else 0, }); - wasm.name = sub_path; + wasm.name = emit.sub_path; return wasm; } @@ -3367,21 +3349,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { } } -pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - const comp = wasm.base.comp; - const use_lld = build_options.have_llvm and comp.config.use_lld; - const diags = &comp.link_diags; - - if (use_lld) { - return wasm.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LinkFailure => return error.LinkFailure, - else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), - }; - } - return wasm.flushZcu(arena, tid, prog_node); -} - pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -3773,14 +3740,14 @@ fn markTable(wasm: *Wasm, i: ObjectTableIndex) link.File.FlushError!void { try wasm.tables.put(wasm.base.comp.gpa, .fromObjectTable(i), {}); } -pub fn flushZcu( +pub fn flush( wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { // The goal is to never use this because it's only needed if we need to - // write to InternPool, but flushZcu is too late to be writing to the + // write to InternPool, but flush is too late to be writing to the // InternPool. _ = tid; const comp = wasm.base.comp; @@ -3832,436 +3799,6 @@ pub fn flushZcu( }; } -fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void { - dev.check(.lld_linker); - - const tracy = trace(@src()); - defer tracy.end(); - - const comp = wasm.base.comp; - const diags = &comp.link_diags; - const shared_memory = comp.config.shared_memory; - const export_memory = comp.config.export_memory; - const import_memory = comp.config.import_memory; - const target = comp.root_mod.resolved_target.result; - - const gpa = comp.gpa; - - const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu) |zcu| blk: { - if (zcu.llvm_object == null) { - try wasm.flushZcu(arena, tid, prog_node); - } else { - // `Compilation.flush` has already made LLVM emit this object file for us. - } - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? }); - } else { - break :blk wasm.base.zcu_object_sub_path.?; - } - } else null; - - const sub_prog_node = prog_node.start("LLD Link", 0); - defer sub_prog_node.end(); - - const is_obj = comp.config.output_mode == .Obj; - const compiler_rt_path: ?Path = blk: { - if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; - if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; - break :blk null; - }; - const ubsan_rt_path: ?Path = blk: { - if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; - if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; - break :blk null; - }; - - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!wasm.base.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!wasm.base.disable_lld_caching) { - man = comp.cache_parent.obtain(); - - // We are about to obtain this lock, so here we give other processes a chance first. - wasm.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 14); - - try link.hashInputs(&man, comp.link_inputs); - for (comp.c_object_table.keys()) |key| { - _ = try man.addFilePath(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - try man.addOptionalFilePath(compiler_rt_path); - try man.addOptionalFilePath(ubsan_rt_path); - man.hash.addOptionalBytes(wasm.entry_name.slice(wasm)); - man.hash.add(wasm.base.stack_size); - man.hash.add(wasm.base.build_id); - man.hash.add(import_memory); - man.hash.add(export_memory); - man.hash.add(wasm.import_table); - man.hash.add(wasm.export_table); - man.hash.addOptional(wasm.initial_memory); - man.hash.addOptional(wasm.max_memory); - man.hash.add(shared_memory); - man.hash.addOptional(wasm.global_base); - man.hash.addListOfBytes(wasm.export_symbol_names); - // strip does not need to go into the linker hash because it is part of the hash namespace - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - wasm.base.lock = man.toOwnedLock(); - return; - } - log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (is_obj) { - // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk Path.initCwd(p); - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - try fs.Dir.copyFile( - the_object_path.root_dir.handle, - the_object_path.sub_path, - directory.handle, - wasm.base.emit.sub_path, - .{}, - ); - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(gpa); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - const linker_command = "wasm-ld"; - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); - try argv.append("--error-limit=0"); - - if (comp.config.lto != .none) { - switch (comp.root_mod.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), - } - } - - if (import_memory) { - try argv.append("--import-memory"); - } - - if (export_memory) { - try argv.append("--export-memory"); - } - - if (wasm.import_table) { - assert(!wasm.export_table); - try argv.append("--import-table"); - } - - if (wasm.export_table) { - assert(!wasm.import_table); - try argv.append("--export-table"); - } - - // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly - // specified it as garbage collection is enabled by default. - if (!wasm.base.gc_sections) { - try argv.append("--no-gc-sections"); - } - - if (comp.config.debug_format == .strip) { - try argv.append("-s"); - } - - if (wasm.initial_memory) |initial_memory| { - const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory}); - try argv.append(arg); - } - - if (wasm.max_memory) |max_memory| { - const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory}); - try argv.append(arg); - } - - if (shared_memory) { - try argv.append("--shared-memory"); - } - - if (wasm.global_base) |global_base| { - const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base}); - try argv.append(arg); - } else { - // We prepend it by default, so when a stack overflow happens the runtime will trap correctly, - // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496 - // - // The user can overwrite this behavior by setting the global-base - try argv.append("--stack-first"); - } - - // Users are allowed to specify which symbols they want to export to the wasm host. - for (wasm.export_symbol_names) |symbol_name| { - const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}); - try argv.append(arg); - } - - if (comp.config.rdynamic) { - try argv.append("--export-dynamic"); - } - - if (wasm.entry_name.slice(wasm)) |entry_name| { - try argv.appendSlice(&.{ "--entry", entry_name }); - } else { - try argv.append("--no-entry"); - } - - try argv.appendSlice(&.{ - "-z", - try std.fmt.allocPrint(arena, "stack-size={d}", .{wasm.base.stack_size}), - }); - - switch (wasm.base.build_id) { - .none => try argv.append("--build-id=none"), - .fast, .uuid, .sha1 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ - @tagName(wasm.base.build_id), - })), - .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ - std.fmt.fmtSliceHexLower(hs.toSlice()), - })), - .md5 => {}, - } - - if (wasm.import_symbols) { - try argv.append("--allow-undefined"); - } - - if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) { - try argv.append("--shared"); - } - if (comp.config.pie) { - try argv.append("--pie"); - } - - try argv.appendSlice(&.{ "-o", full_out_path }); - - if (target.cpu.arch == .wasm64) { - try argv.append("-mwasm64"); - } - - const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or - (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); - - if (comp.config.link_libc and is_exe_or_dyn_lib) { - if (target.os.tag == .wasi) { - for (comp.wasi_emulated_libs) |crt_file| { - try argv.append(try comp.crtFileAsString( - arena, - wasi_libc.emulatedLibCRFileLibName(crt_file), - )); - } - - try argv.append(try comp.crtFileAsString( - arena, - wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model), - )); - try argv.append(try comp.crtFileAsString(arena, "libc.a")); - } - - if (comp.zigc_static_lib) |zigc| { - try argv.append(try zigc.full_object_path.toString(arena)); - } - - if (comp.config.link_libcpp) { - try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - } - } - - // Positional arguments to the linker such as object files. - var whole_archive = false; - for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive => |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - try argv.append(try obj.path.toString(arena)); - }, - .dso => |dso| { - try argv.append(try dso.path.toString(arena)); - }, - .dso_exact => unreachable, - .res => unreachable, - }; - if (whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(try key.status.success.object_path.toString(arena)); - } - if (module_obj_path) |p| { - try argv.append(p); - } - - if (compiler_rt_path) |p| { - try argv.append(try p.toString(arena)); - } - - if (ubsan_rt_path) |p| { - try argv.append(try p.toStringZ(arena)); - } - - if (comp.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } - - if (std.process.can_spawn) { - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - var child = std.process.Child.init(argv.items, arena); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - const term = child.spawnAndWait() catch |err| { - log.err("failed to spawn (passthrough mode) LLD {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnWasm; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - std.process.exit(code); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const stderr = try child.stderr.?.reader().readAllAlloc(arena, std.math.maxInt(usize)); - - const term = child.wait() catch |err| { - log.err("failed to spawn LLD {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnWasm; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - diags.lockAndParseLldStderr(linker_command, stderr); - return error.LinkFailure; - } - }, - else => { - return diags.fail("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - }, - } - - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); - } - } - } else { - const exit_code = try lldMain(arena, argv.items, false); - if (exit_code != 0) { - if (comp.clang_passthrough_mode) { - std.process.exit(exit_code); - } else { - return diags.fail("{s} returned exit code {d}:\n{s}", .{ argv.items[0], exit_code }); - } - } - } - - // Give +x to the .wasm file if it is an executable and the OS is WASI. - // Some systems may be configured to execute such binaries directly. Even if that - // is not the case, it means we will get "exec format error" when trying to run - // it, and then can react to that in the same way as trying to run an ELF file - // from a foreign CPU architecture. - if (fs.has_executable_bit and target.os.tag == .wasi and - comp.config.output_mode == .Exe) - { - // TODO: what's our strategy for reporting linker errors from this function? - // report a nice error here with the file path if it fails instead of - // just returning the error code. - // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. - std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) { - error.OperationNotSupported => unreachable, // Not a symlink. - else => |e| return e, - }; - } - } - - if (!wasm.base.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - wasm.base.lock = man.toOwnedLock(); - } -} - fn defaultEntrySymbolName( preloaded_strings: *const PreloadedStrings, wasi_exec_model: std.builtin.WasiExecModel, diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index e2f81e015e..7fe714ce6e 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -46,7 +46,6 @@ pub fn createEmpty( .stack_size = options.stack_size orelse 0, .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, - .disable_lld_caching = options.disable_lld_caching, .build_id = options.build_id, }, }; @@ -105,10 +104,6 @@ pub fn updateExports( } pub fn flush(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { - return self.flushZcu(arena, tid, prog_node); -} - -pub fn flushZcu(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { _ = self; _ = arena; _ = tid; diff --git a/src/main.zig b/src/main.zig index 20ccf4b7ec..f7ad35d7cd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -867,9 +867,9 @@ fn buildOutputType( var linker_allow_undefined_version: bool = false; var linker_enable_new_dtags: ?bool = null; var disable_c_depfile = false; - var linker_sort_section: ?link.File.Elf.SortSection = null; + var linker_sort_section: ?link.File.Lld.Elf.SortSection = null; var linker_gc_sections: ?bool = null; - var linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null; + var linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null; var linker_allow_shlib_undefined: ?bool = null; var allow_so_scripts: bool = false; var linker_bind_global_refs_locally: ?bool = null; @@ -921,7 +921,7 @@ fn buildOutputType( var debug_compiler_runtime_libs = false; var opt_incremental: ?bool = null; var install_name: ?[]const u8 = null; - var hash_style: link.File.Elf.HashStyle = .both; + var hash_style: link.File.Lld.Elf.HashStyle = .both; var entitlements: ?[]const u8 = null; var pagezero_size: ?u64 = null; var lib_search_strategy: link.UnresolvedInput.SearchStrategy = .paths_first; @@ -1196,11 +1196,11 @@ fn buildOutputType( install_name = args_iter.nextOrFatal(); } else if (mem.startsWith(u8, arg, "--compress-debug-sections=")) { const param = arg["--compress-debug-sections=".len..]; - linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, param) orelse { + linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, param) orelse { fatal("expected --compress-debug-sections=[none|zlib|zstd], found '{s}'", .{param}); }; } else if (mem.eql(u8, arg, "--compress-debug-sections")) { - linker_compress_debug_sections = link.File.Elf.CompressDebugSections.zlib; + linker_compress_debug_sections = link.File.Lld.Elf.CompressDebugSections.zlib; } else if (mem.eql(u8, arg, "-pagezero_size")) { const next_arg = args_iter.nextOrFatal(); pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { @@ -2368,7 +2368,7 @@ fn buildOutputType( if (it.only_arg.len == 0) { linker_compress_debug_sections = .zlib; } else { - linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, it.only_arg) orelse { + linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, it.only_arg) orelse { fatal("expected [none|zlib|zstd] after --compress-debug-sections, found '{s}'", .{it.only_arg}); }; } @@ -2505,7 +2505,7 @@ fn buildOutputType( linker_print_map = true; } else if (mem.eql(u8, arg, "--sort-section")) { const arg1 = linker_args_it.nextOrFatal(); - linker_sort_section = std.meta.stringToEnum(link.File.Elf.SortSection, arg1) orelse { + linker_sort_section = std.meta.stringToEnum(link.File.Lld.Elf.SortSection, arg1) orelse { fatal("expected [name|alignment] after --sort-section, found '{s}'", .{arg1}); }; } else if (mem.eql(u8, arg, "--allow-shlib-undefined") or @@ -2551,7 +2551,7 @@ fn buildOutputType( try linker_export_symbol_names.append(arena, linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "--compress-debug-sections")) { const arg1 = linker_args_it.nextOrFatal(); - linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, arg1) orelse { + linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, arg1) orelse { fatal("expected [none|zlib|zstd] after --compress-debug-sections, found '{s}'", .{arg1}); }; } else if (mem.startsWith(u8, arg, "-z")) { @@ -2764,7 +2764,7 @@ fn buildOutputType( mem.eql(u8, arg, "--hash-style")) { const next_arg = linker_args_it.nextOrFatal(); - hash_style = std.meta.stringToEnum(link.File.Elf.HashStyle, next_arg) orelse { + hash_style = std.meta.stringToEnum(link.File.Lld.Elf.HashStyle, next_arg) orelse { fatal("expected [sysv|gnu|both] after --hash-style, found '{s}'", .{ next_arg, }); -- cgit v1.2.3 From 66d15d9d0974e1b493b717cf02deb435ebd13858 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 29 May 2025 01:27:37 +0100 Subject: link: make checking for failed types the responsibility of Compilation --- src/Compilation.zig | 21 +++++++++++++++++++++ src/Zcu/PerThread.zig | 8 -------- src/link.zig | 13 ------------- 3 files changed, 21 insertions(+), 21 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index f51020c0ff..e51b3de1ad 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4553,12 +4553,33 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { } } assert(nav.status == .fully_resolved); + if (!Air.valFullyResolved(zcu.navValue(nav_index), zcu)) { + // Type resolution failed in a way which affects this `Nav`. This is a transitive + // failure, but it doesn't need recording, because this `Nav` semantically depends + // on the failed type, so when it is changed the `Nav` will be updated. + return; + } comp.dispatchLinkTask(tid, .{ .link_nav = nav_index }); }, .link_func => |func| { + const zcu = comp.zcu.?; + if (!func.air.typesFullyResolved(zcu)) { + // Type resolution failed in a way which affects this function. This is a transitive + // failure, but it doesn't need recording, because this function semantically depends + // on the failed type, so when it is changed the function is updated. + return; + } comp.dispatchLinkTask(tid, .{ .link_func = func }); }, .link_type => |ty| { + const zcu = comp.zcu.?; + if (zcu.failed_types.fetchSwapRemove(ty)) |*entry| entry.value.deinit(zcu.gpa); + if (!Air.typeFullyResolved(.fromInterned(ty), zcu)) { + // Type resolution failed in a way which affects this type. This is a transitive + // failure, but it doesn't need recording, because this type semantically depends + // on the failed type, so when that is changed, this type will be updated. + return; + } comp.dispatchLinkTask(tid, .{ .link_type = ty }); }, .update_line_number => |ti| { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index b10e6d7c41..137d93b82a 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1739,14 +1739,6 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: *A const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0); defer codegen_prog_node.end(); - if (!air.typesFullyResolved(zcu)) { - // A type we depend on failed to resolve. This is a transitive failure. - // Correcting this failure will involve changing a type this function - // depends on, hence triggering re-analysis of this function, so this - // interacts correctly with incremental compilation. - return; - } - legalize: { try air.legalize(pt, @import("../codegen.zig").legalizeFeatures(pt, nav_index) orelse break :legalize); } diff --git a/src/link.zig b/src/link.zig index 68ea533eed..4b4c3c611b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1424,12 +1424,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { const zcu = comp.zcu.?; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); - if (!Air.valFullyResolved(zcu.navValue(nav_index), zcu)) { - // Type resolution failed in a way which affects this `Nav`. This is a transitive - // failure, but it doesn't need recording, because this `Nav` semantically depends - // on the failed type, so when it is changed the `Nav` will be updated. - return; - } if (zcu.llvm_object) |llvm_object| { llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { error.OutOfMemory => diags.setAllocFailure(), @@ -1473,13 +1467,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { const zcu = comp.zcu.?; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); - if (zcu.failed_types.fetchSwapRemove(ty)) |*entry| entry.value.deinit(zcu.gpa); - if (!Air.typeFullyResolved(.fromInterned(ty), zcu)) { - // Type resolution failed in a way which affects this type. This is a transitive - // failure, but it doesn't need recording, because this type semantically depends - // on the failed type, so when that is changed, this type will be updated. - return; - } if (zcu.llvm_object == null) { if (comp.bin_file) |lf| { lf.updateContainerType(pt, ty) catch |err| switch (err) { -- cgit v1.2.3 From 9eb400ef19391261a3b61129d8665602c89959c5 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 29 May 2025 05:38:55 +0100 Subject: compiler: rework backend pipeline to separate codegen and link The idea here is that instead of the linker calling into codegen, instead codegen should run before we touch the linker, and after MIR is produced, it is sent to the linker. Aside from simplifying the call graph (by preventing N linkers from each calling into M codegen backends!), this has the huge benefit that it is possible to parallellize codegen separately from linking. The threading model can look like this: * 1 semantic analysis thread, which generates AIR * N codegen threads, which process AIR into MIR * 1 linker thread, which emits MIR to the binary The codegen threads are also responsible for `Air.Legalize` and `Air.Liveness`; it's more efficient to do this work here instead of blocking the main thread for this trivially parallel task. I have repurposed the `Zcu.Feature.separate_thread` backend feature to indicate support for this 1:N:1 threading pattern. This commit makes the C backend support this feature, since it was relatively easy to divorce from `link.C`: it just required eliminating some shared buffers. Other backends don't currently support this feature. In fact, they don't even compile -- the next few commits will fix them back up. --- src/Compilation.zig | 234 +++++++++++++++++++++++++++------------------ src/ThreadSafeQueue.zig | 72 -------------- src/Zcu.zig | 50 ++++++++-- src/Zcu/PerThread.zig | 162 ++++++++++++++++--------------- src/codegen.zig | 97 ++++++++++++++++++- src/codegen/c.zig | 146 +++++++++++++++++++++++----- src/codegen/llvm.zig | 22 ++--- src/codegen/spirv.zig | 5 +- src/dev.zig | 9 ++ src/libs/freebsd.zig | 2 +- src/libs/glibc.zig | 2 +- src/libs/libcxx.zig | 4 +- src/libs/libtsan.zig | 2 +- src/libs/libunwind.zig | 2 +- src/libs/musl.zig | 2 +- src/libs/netbsd.zig | 2 +- src/link.zig | 189 ++++++++++++++++++------------------ src/link/C.zig | 143 +++++++++++---------------- src/link/Coff.zig | 17 +--- src/link/Elf.zig | 6 +- src/link/Elf/ZigObject.zig | 12 ++- src/link/Queue.zig | 234 +++++++++++++++++++++++++++++++++++++++++++++ src/target.zig | 4 +- 23 files changed, 918 insertions(+), 500 deletions(-) delete mode 100644 src/ThreadSafeQueue.zig create mode 100644 src/link/Queue.zig (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index e51b3de1ad..64ec1ab0a8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -43,7 +43,6 @@ const Air = @import("Air.zig"); const Builtin = @import("Builtin.zig"); const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); -const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue; pub const Config = @import("Compilation/Config.zig"); @@ -113,17 +112,7 @@ win32_resource_table: if (dev.env.supports(.win32_resource)) std.AutoArrayHashMa } = .{}, link_diags: link.Diags, -link_task_queue: ThreadSafeQueue(link.Task) = .empty, -/// Ensure only 1 simultaneous call to `flushTaskQueue`. -link_task_queue_safety: std.debug.SafetyLock = .{}, -/// If any tasks are queued up that depend on prelink being finished, they are moved -/// here until prelink finishes. -link_task_queue_postponed: std.ArrayListUnmanaged(link.Task) = .empty, -/// Initialized with how many link input tasks are expected. After this reaches zero -/// the linker will begin the prelink phase. -/// Initialized in the Compilation main thread before the pipeline; modified only in -/// the linker task thread. -remaining_prelink_tasks: u32, +link_task_queue: link.Queue = .empty, /// Set of work that can be represented by only flags to determine whether the /// work is queued or not. @@ -846,15 +835,24 @@ pub const RcIncludes = enum { }; const Job = union(enum) { - /// Corresponds to the task in `link.Task`. - /// Only needed for backends that haven't yet been updated to not race against Sema. + /// Given the generated AIR for a function, put it onto the code generation queue. + /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that + /// all types are resolved before the linker task is queued. + /// If the backend does not support `Zcu.Feature.separate_thread`, codegen and linking happen immediately. + codegen_func: struct { + func: InternPool.Index, + /// The AIR emitted from analyzing `func`; owned by this `Job` in `gpa`. + air: Air, + }, + /// Queue a `link.ZcuTask` to emit this non-function `Nav` into the output binary. + /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that + /// all types are resolved before the linker task is queued. + /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately. link_nav: InternPool.Nav.Index, - /// Corresponds to the task in `link.Task`. - /// TODO: this is currently also responsible for performing codegen. - /// Only needed for backends that haven't yet been updated to not race against Sema. - link_func: link.Task.CodegenFunc, - /// Corresponds to the task in `link.Task`. - /// Only needed for backends that haven't yet been updated to not race against Sema. + /// Queue a `link.ZcuTask` to emit debug information for this container type. + /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that + /// all types are resolved before the linker task is queued. + /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately. link_type: InternPool.Index, update_line_number: InternPool.TrackedInst.Index, /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed. @@ -880,13 +878,13 @@ const Job = union(enum) { return switch (tag) { // Prioritize functions so that codegen can get to work on them on a // separate thread, while Sema goes back to its own work. - .resolve_type_fully, .analyze_func, .link_func => 0, + .resolve_type_fully, .analyze_func, .codegen_func => 0, else => 1, }; } comptime { // Job dependencies - assert(stage(.resolve_type_fully) <= stage(.link_func)); + assert(stage(.resolve_type_fully) <= stage(.codegen_func)); } }; @@ -2004,7 +2002,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .file_system_inputs = options.file_system_inputs, .parent_whole_cache = options.parent_whole_cache, .link_diags = .init(gpa), - .remaining_prelink_tasks = 0, }; // Prevent some footguns by making the "any" fields of config reflect @@ -2213,7 +2210,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; comp.c_object_table.putAssumeCapacityNoClobber(c_object, {}); } - comp.remaining_prelink_tasks += @intCast(comp.c_object_table.count()); + comp.link_task_queue.pending_prelink_tasks += @intCast(comp.c_object_table.count()); // Add a `Win32Resource` for each `rc_source_files` and one for `manifest_file`. const win32_resource_count = @@ -2224,7 +2221,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // Add this after adding logic to updateWin32Resource to pass the // result into link.loadInput. loadInput integration is not implemented // for Windows linking logic yet. - //comp.remaining_prelink_tasks += @intCast(win32_resource_count); + //comp.link_task_queue.pending_prelink_tasks += @intCast(win32_resource_count); for (options.rc_source_files) |rc_source_file| { const win32_resource = try gpa.create(Win32Resource); errdefer gpa.destroy(win32_resource); @@ -2275,78 +2272,76 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const paths = try lci.resolveCrtPaths(arena, basenames, target); const fields = @typeInfo(@TypeOf(paths)).@"struct".fields; - try comp.link_task_queue.shared.ensureUnusedCapacity(gpa, fields.len + 1); + try comp.link_task_queue.queued_prelink.ensureUnusedCapacity(gpa, fields.len + 1); inline for (fields) |field| { if (@field(paths, field.name)) |path| { - comp.link_task_queue.shared.appendAssumeCapacity(.{ .load_object = path }); - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.queued_prelink.appendAssumeCapacity(.{ .load_object = path }); } } // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`. - comp.link_task_queue.shared.appendAssumeCapacity(.load_host_libc); - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.queued_prelink.appendAssumeCapacity(.load_host_libc); } else if (target.isMuslLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (musl.needsCrt0(comp.config.output_mode, comp.config.link_mode, comp.config.pie)) |f| { comp.queued_jobs.musl_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } switch (comp.config.link_mode) { .static => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_a)] = true, .dynamic => comp.queued_jobs.musl_crt_file[@intFromEnum(musl.CrtFile.libc_so)] = true, } - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (target.isGnuLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (glibc.needsCrt0(comp.config.output_mode)) |f| { comp.queued_jobs.glibc_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } comp.queued_jobs.glibc_shared_objects = true; - comp.remaining_prelink_tasks += glibc.sharedObjectsCount(&target); + comp.link_task_queue.pending_prelink_tasks += glibc.sharedObjectsCount(&target); comp.queued_jobs.glibc_crt_file[@intFromEnum(glibc.CrtFile.libc_nonshared_a)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (target.isFreeBSDLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (freebsd.needsCrt0(comp.config.output_mode)) |f| { comp.queued_jobs.freebsd_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } comp.queued_jobs.freebsd_shared_objects = true; - comp.remaining_prelink_tasks += freebsd.sharedObjectsCount(); + comp.link_task_queue.pending_prelink_tasks += freebsd.sharedObjectsCount(); } else if (target.isNetBSDLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; if (netbsd.needsCrt0(comp.config.output_mode)) |f| { comp.queued_jobs.netbsd_crt_file[@intFromEnum(f)] = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } comp.queued_jobs.netbsd_shared_objects = true; - comp.remaining_prelink_tasks += netbsd.sharedObjectsCount(); + comp.link_task_queue.pending_prelink_tasks += netbsd.sharedObjectsCount(); } else if (target.isWasiLibC()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; for (comp.wasi_emulated_libs) |crt_file| { comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = true; } - comp.remaining_prelink_tasks += @intCast(comp.wasi_emulated_libs.len); + comp.link_task_queue.pending_prelink_tasks += @intCast(comp.wasi_emulated_libs.len); comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.execModelCrtFile(comp.config.wasi_exec_model))] = true; comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(wasi_libc.CrtFile.libc_a)] = true; - comp.remaining_prelink_tasks += 2; + comp.link_task_queue.pending_prelink_tasks += 2; } else if (target.isMinGW()) { if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; const main_crt_file: mingw.CrtFile = if (is_dyn_lib) .dllcrt2_o else .crt2_o; comp.queued_jobs.mingw_crt_file[@intFromEnum(main_crt_file)] = true; comp.queued_jobs.mingw_crt_file[@intFromEnum(mingw.CrtFile.libmingw32_lib)] = true; - comp.remaining_prelink_tasks += 2; + comp.link_task_queue.pending_prelink_tasks += 2; // When linking mingw-w64 there are some import libs we always need. try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len); @@ -2360,7 +2355,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil target.isMinGW()) { comp.queued_jobs.zigc_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } } @@ -2377,53 +2372,53 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } if (comp.wantBuildLibUnwindFromSource()) { comp.queued_jobs.libunwind = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) { comp.queued_jobs.libcxx = true; comp.queued_jobs.libcxxabi = true; - comp.remaining_prelink_tasks += 2; + comp.link_task_queue.pending_prelink_tasks += 2; } if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.any_sanitize_thread) { comp.queued_jobs.libtsan = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (can_build_compiler_rt) { if (comp.compiler_rt_strat == .lib) { log.debug("queuing a job to build compiler_rt_lib", .{}); comp.queued_jobs.compiler_rt_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (comp.compiler_rt_strat == .obj) { log.debug("queuing a job to build compiler_rt_obj", .{}); // In this case we are making a static library, so we ask // for a compiler-rt object to put in it. comp.queued_jobs.compiler_rt_obj = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (comp.ubsan_rt_strat == .lib) { log.debug("queuing a job to build ubsan_rt_lib", .{}); comp.queued_jobs.ubsan_rt_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } else if (comp.ubsan_rt_strat == .obj) { log.debug("queuing a job to build ubsan_rt_obj", .{}); comp.queued_jobs.ubsan_rt_obj = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } if (is_exe_or_dyn_lib and comp.config.any_fuzz) { log.debug("queuing a job to build libfuzzer", .{}); comp.queued_jobs.fuzzer_lib = true; - comp.remaining_prelink_tasks += 1; + comp.link_task_queue.pending_prelink_tasks += 1; } } } - try comp.link_task_queue.shared.append(gpa, .load_explicitly_provided); - comp.remaining_prelink_tasks += 1; + try comp.link_task_queue.queued_prelink.append(gpa, .load_explicitly_provided); } - log.debug("total prelink tasks: {d}", .{comp.remaining_prelink_tasks}); + log.debug("queued prelink tasks: {d}", .{comp.link_task_queue.queued_prelink.items.len}); + log.debug("pending prelink tasks: {d}", .{comp.link_task_queue.pending_prelink_tasks}); return comp; } @@ -2431,6 +2426,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil pub fn destroy(comp: *Compilation) void { const gpa = comp.gpa; + // This needs to be destroyed first, because it might contain MIR which we only know + // how to interpret (which kind of MIR it is) from `comp.bin_file`. + comp.link_task_queue.deinit(comp); + if (comp.bin_file) |lf| lf.destroy(); if (comp.zcu) |zcu| zcu.deinit(); comp.cache_use.deinit(); @@ -2512,8 +2511,6 @@ pub fn destroy(comp: *Compilation) void { comp.failed_win32_resources.deinit(gpa); comp.link_diags.deinit(); - comp.link_task_queue.deinit(gpa); - comp.link_task_queue_postponed.deinit(gpa); comp.clearMiscFailures(); @@ -4180,9 +4177,7 @@ fn performAllTheWorkInner( comp.link_task_wait_group.reset(); defer comp.link_task_wait_group.wait(); - if (comp.link_task_queue.start()) { - comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); - } + comp.link_task_queue.start(comp); if (comp.docs_emit != null) { dev.check(.docs_emit); @@ -4498,7 +4493,7 @@ fn performAllTheWorkInner( comp.link_task_wait_group.wait(); comp.link_task_wait_group.reset(); std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{}); - if (comp.remaining_prelink_tasks > 0) { + if (comp.link_task_queue.pending_prelink_tasks > 0) { // Indicates an error occurred preventing prelink phase from completing. return; } @@ -4543,6 +4538,45 @@ pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void { fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { switch (job) { + .codegen_func => |func| { + const zcu = comp.zcu.?; + const gpa = zcu.gpa; + var air = func.air; + errdefer air.deinit(gpa); + if (!air.typesFullyResolved(zcu)) { + // Type resolution failed in a way which affects this function. This is a transitive + // failure, but it doesn't need recording, because this function semantically depends + // on the failed type, so when it is changed the function is updated. + air.deinit(gpa); + return; + } + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); + defer pt.deactivate(); + const shared_mir = try gpa.create(link.ZcuTask.LinkFunc.SharedMir); + shared_mir.* = .{ + .status = .init(.pending), + .value = undefined, + }; + if (comp.separateCodegenThreadOk()) { + // `workerZcuCodegen` takes ownership of `air`. + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, workerZcuCodegen, .{ comp, func.func, air, shared_mir }); + comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ + .func = func.func, + .mir = shared_mir, + .air = undefined, + } }); + } else { + const emit_needs_air = !zcu.backendSupportsFeature(.separate_thread); + pt.runCodegen(func.func, &air, shared_mir); + assert(shared_mir.status.load(.monotonic) != .pending); + comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ + .func = func.func, + .mir = shared_mir, + .air = if (emit_needs_air) &air else undefined, + } }); + air.deinit(gpa); + } + }, .link_nav => |nav_index| { const zcu = comp.zcu.?; const nav = zcu.intern_pool.getNav(nav_index); @@ -4559,17 +4593,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { // on the failed type, so when it is changed the `Nav` will be updated. return; } - comp.dispatchLinkTask(tid, .{ .link_nav = nav_index }); - }, - .link_func => |func| { - const zcu = comp.zcu.?; - if (!func.air.typesFullyResolved(zcu)) { - // Type resolution failed in a way which affects this function. This is a transitive - // failure, but it doesn't need recording, because this function semantically depends - // on the failed type, so when it is changed the function is updated. - return; - } - comp.dispatchLinkTask(tid, .{ .link_func = func }); + comp.dispatchZcuLinkTask(tid, .{ .link_nav = nav_index }); }, .link_type => |ty| { const zcu = comp.zcu.?; @@ -4580,10 +4604,10 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { // on the failed type, so when that is changed, this type will be updated. return; } - comp.dispatchLinkTask(tid, .{ .link_type = ty }); + comp.dispatchZcuLinkTask(tid, .{ .link_type = ty }); }, .update_line_number => |ti| { - comp.dispatchLinkTask(tid, .{ .update_line_number = ti }); + comp.dispatchZcuLinkTask(tid, .{ .update_line_number = ti }); }, .analyze_func => |func| { const named_frame = tracy.namedFrame("analyze_func"); @@ -4675,18 +4699,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { } } -/// The reason for the double-queue here is that the first queue ensures any -/// resolve_type_fully tasks are complete before this dispatch function is called. -fn dispatchLinkTask(comp: *Compilation, tid: usize, link_task: link.Task) void { - if (comp.separateCodegenThreadOk()) { - comp.queueLinkTasks(&.{link_task}); - } else { - assert(comp.remaining_prelink_tasks == 0); - link.doTask(comp, tid, link_task); - } -} - -fn separateCodegenThreadOk(comp: *const Compilation) bool { +pub fn separateCodegenThreadOk(comp: *const Compilation) bool { if (InternPool.single_threaded) return false; const zcu = comp.zcu orelse return true; return zcu.backendSupportsFeature(.separate_thread); @@ -5273,6 +5286,21 @@ pub const RtOptions = struct { allow_lto: bool = true, }; +fn workerZcuCodegen( + tid: usize, + comp: *Compilation, + func_index: InternPool.Index, + orig_air: Air, + out: *link.ZcuTask.LinkFunc.SharedMir, +) void { + var air = orig_air; + // We own `air` now, so we are responsbile for freeing it. + defer air.deinit(comp.gpa); + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); + defer pt.deactivate(); + pt.runCodegen(func_index, &air, out); +} + fn buildRt( comp: *Compilation, root_source_name: []const u8, @@ -5804,7 +5832,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr }, }; - comp.queueLinkTasks(&.{.{ .load_object = c_object.status.success.object_path }}); + comp.queuePrelinkTasks(&.{.{ .load_object = c_object.status.success.object_path }}); } fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32_resource_prog_node: std.Progress.Node) !void { @@ -7237,7 +7265,7 @@ fn buildOutputFromZig( assert(out.* == null); out.* = crt_file; - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); } pub const CrtFileOptions = struct { @@ -7361,7 +7389,7 @@ pub fn build_crt_file( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); const crt_file = try sub_compilation.toCrtFile(); - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); { comp.mutex.lock(); @@ -7371,8 +7399,8 @@ pub fn build_crt_file( } } -pub fn queueLinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Compilation.Config) void { - comp.queueLinkTasks(switch (config.output_mode) { +pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Compilation.Config) void { + comp.queuePrelinkTasks(switch (config.output_mode) { .Exe => unreachable, .Obj => &.{.{ .load_object = path }}, .Lib => &.{switch (config.link_mode) { @@ -7384,12 +7412,30 @@ pub fn queueLinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Co /// Only valid to call during `update`. Automatically handles queuing up a /// linker worker task if there is not already one. -pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.Task) void { - if (comp.link_task_queue.enqueue(comp.gpa, tasks) catch |err| switch (err) { +pub fn queuePrelinkTasks(comp: *Compilation, tasks: []const link.PrelinkTask) void { + comp.link_task_queue.enqueuePrelink(comp, tasks) catch |err| switch (err) { error.OutOfMemory => return comp.setAllocFailure(), - }) { - comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); + }; +} + +/// The reason for the double-queue here is that the first queue ensures any +/// resolve_type_fully tasks are complete before this dispatch function is called. +fn dispatchZcuLinkTask(comp: *Compilation, tid: usize, task: link.ZcuTask) void { + if (!comp.separateCodegenThreadOk()) { + assert(tid == 0); + if (task == .link_func) { + assert(task.link_func.mir.status.load(.monotonic) != .pending); + } + link.doZcuTask(comp, tid, task); + task.deinit(comp.zcu.?); + return; } + comp.link_task_queue.enqueueZcu(comp, task) catch |err| switch (err) { + error.OutOfMemory => { + task.deinit(comp.zcu.?); + comp.setAllocFailure(); + }, + }; } pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile { diff --git a/src/ThreadSafeQueue.zig b/src/ThreadSafeQueue.zig deleted file mode 100644 index 74bbdc418f..0000000000 --- a/src/ThreadSafeQueue.zig +++ /dev/null @@ -1,72 +0,0 @@ -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - -pub fn ThreadSafeQueue(comptime T: type) type { - return struct { - worker_owned: std.ArrayListUnmanaged(T), - /// Protected by `mutex`. - shared: std.ArrayListUnmanaged(T), - mutex: std.Thread.Mutex, - state: State, - - const Self = @This(); - - pub const State = enum { wait, run }; - - pub const empty: Self = .{ - .worker_owned = .empty, - .shared = .empty, - .mutex = .{}, - .state = .wait, - }; - - pub fn deinit(self: *Self, gpa: Allocator) void { - self.worker_owned.deinit(gpa); - self.shared.deinit(gpa); - self.* = undefined; - } - - /// Must be called from the worker thread. - pub fn check(self: *Self) ?[]T { - assert(self.worker_owned.items.len == 0); - { - self.mutex.lock(); - defer self.mutex.unlock(); - assert(self.state == .run); - if (self.shared.items.len == 0) { - self.state = .wait; - return null; - } - std.mem.swap(std.ArrayListUnmanaged(T), &self.worker_owned, &self.shared); - } - const result = self.worker_owned.items; - self.worker_owned.clearRetainingCapacity(); - return result; - } - - /// Adds items to the queue, returning true if and only if the worker - /// thread is waiting. Thread-safe. - /// Not safe to call from the worker thread. - pub fn enqueue(self: *Self, gpa: Allocator, items: []const T) error{OutOfMemory}!bool { - self.mutex.lock(); - defer self.mutex.unlock(); - try self.shared.appendSlice(gpa, items); - return switch (self.state) { - .run => false, - .wait => { - self.state = .run; - return true; - }, - }; - } - - /// Safe only to call exactly once when initially starting the worker. - pub fn start(self: *Self) bool { - assert(self.state == .wait); - if (self.shared.items.len == 0) return false; - self.state = .run; - return true; - } - }; -} diff --git a/src/Zcu.zig b/src/Zcu.zig index 6a6a74e260..91d2c0ffff 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -171,6 +171,8 @@ transitive_failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .emp /// This `Nav` succeeded analysis, but failed codegen. /// This may be a simple "value" `Nav`, or it may be a function. /// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. +/// While multiple threads are active (most of the time!), this is guarded by `zcu.comp.mutex`, as +/// codegen and linking run on a separate thread. failed_codegen: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, *ErrorMsg) = .empty, failed_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, *ErrorMsg) = .empty, /// Keep track of `@compileLog`s per `AnalUnit`. @@ -3817,7 +3819,36 @@ pub const Feature = enum { is_named_enum_value, error_set_has_value, field_reordering, - /// If the backend supports running from another thread. + /// In theory, backends are supposed to work like this: + /// + /// * The AIR emitted by `Sema` is converted into MIR by `codegen.generateFunction`. This pass + /// is "pure", in that it does not depend on or modify any external mutable state. + /// + /// * That MIR is sent to the linker, which calls `codegen.emitFunction` to convert the MIR to + /// finalized machine code. This process is permitted to query and modify linker state. + /// + /// * The linker stores the resulting machine code in the binary as needed. + /// + /// The first stage described above can run in parallel to the rest of the compiler, and even to + /// other code generation work; we can run as many codegen threads as we want in parallel because + /// of the fact that this pass is pure. Emit and link must be single-threaded, but are generally + /// very fast, so that isn't a problem. + /// + /// Unfortunately, some code generation implementations currently query and/or mutate linker state + /// or even (in the case of the LLVM backend) semantic analysis state. Such backends cannot be run + /// in parallel with each other, with linking, or (potentially) with semantic analysis. + /// + /// Additionally, some backends continue to need the AIR in the "emit" stage, despite this pass + /// operating on MIR. This complicates memory management under the threading model above. + /// + /// These are both **bugs** in backend implementations, left over from legacy code. However, they + /// are difficult to fix. So, this `Feature` currently guards correct threading of code generation: + /// + /// * With this feature enabled, the backend is threaded as described above. The "emit" stage does + /// not have access to AIR (it will be `undefined`; see `codegen.emitFunction`). + /// + /// * With this feature disabled, semantic analysis, code generation, and linking all occur on the + /// same thread, and the "emit" stage has access to AIR. separate_thread, }; @@ -4566,22 +4597,29 @@ pub fn codegenFail( comptime format: []const u8, args: anytype, ) CodegenFailError { - const gpa = zcu.gpa; - try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1); - const msg = try Zcu.ErrorMsg.create(gpa, zcu.navSrcLoc(nav_index), format, args); - zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, msg); - return error.CodegenFail; + const msg = try Zcu.ErrorMsg.create(zcu.gpa, zcu.navSrcLoc(nav_index), format, args); + return zcu.codegenFailMsg(nav_index, msg); } +/// Takes ownership of `msg`, even on OOM. pub fn codegenFailMsg(zcu: *Zcu, nav_index: InternPool.Nav.Index, msg: *ErrorMsg) CodegenFailError { const gpa = zcu.gpa; { + zcu.comp.mutex.lock(); + defer zcu.comp.mutex.unlock(); errdefer msg.deinit(gpa); try zcu.failed_codegen.putNoClobber(gpa, nav_index, msg); } return error.CodegenFail; } +/// Asserts that `zcu.failed_codegen` contains the key `nav`, with the necessary lock held. +pub fn assertCodegenFailed(zcu: *Zcu, nav: InternPool.Nav.Index) void { + zcu.comp.mutex.lock(); + defer zcu.comp.mutex.unlock(); + assert(zcu.failed_codegen.contains(nav)); +} + pub fn codegenFailType( zcu: *Zcu, ty_index: InternPool.Index, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 137d93b82a..92f1adbf2a 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -27,6 +27,7 @@ const Type = @import("../Type.zig"); const Value = @import("../Value.zig"); const Zcu = @import("../Zcu.zig"); const Compilation = @import("../Compilation.zig"); +const codegen = @import("../codegen.zig"); const Zir = std.zig.Zir; const Zoir = std.zig.Zoir; const ZonGen = std.zig.ZonGen; @@ -1716,7 +1717,7 @@ fn analyzeFuncBody( } // This job depends on any resolve_type_fully jobs queued up before it. - try comp.queueJob(.{ .link_func = .{ + try comp.queueJob(.{ .codegen_func = .{ .func = func_index, .air = air, } }); @@ -1724,79 +1725,6 @@ fn analyzeFuncBody( return .{ .ies_outdated = ies_outdated }; } -/// Takes ownership of `air`, even on error. -/// If any types referenced by `air` are unresolved, marks the codegen as failed. -pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Allocator.Error!void { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const comp = zcu.comp; - - const func = zcu.funcInfo(func_index); - const nav_index = func.owner_nav; - const nav = ip.getNav(nav_index); - - const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0); - defer codegen_prog_node.end(); - - legalize: { - try air.legalize(pt, @import("../codegen.zig").legalizeFeatures(pt, nav_index) orelse break :legalize); - } - - var liveness = try Air.Liveness.analyze(zcu, air.*, ip); - defer liveness.deinit(gpa); - - if (build_options.enable_debug_extensions and comp.verbose_air) { - std.debug.print("# Begin Function AIR: {}:\n", .{nav.fqn.fmt(ip)}); - air.dump(pt, liveness); - std.debug.print("# End Function AIR: {}\n\n", .{nav.fqn.fmt(ip)}); - } - - if (std.debug.runtime_safety) { - var verify: Air.Liveness.Verify = .{ - .gpa = gpa, - .zcu = zcu, - .air = air.*, - .liveness = liveness, - .intern_pool = ip, - }; - defer verify.deinit(); - - verify.verify() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "invalid liveness: {s}", - .{@errorName(err)}, - )); - return; - }, - }; - } - - if (zcu.llvm_object) |llvm_object| { - llvm_object.updateFunc(pt, func_index, air.*, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - }; - } else if (comp.bin_file) |lf| { - lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)), - error.Overflow, error.RelocationNotByteAligned => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - // Not a retryable failure. - }, - }; - } -} - pub fn semaMod(pt: Zcu.PerThread, mod: *Module) !void { dev.check(.sema); const file_index = pt.zcu.module_roots.get(mod).?.unwrap().?; @@ -3449,7 +3377,7 @@ pub fn populateTestFunctions( } // The linker thread is not running, so we actually need to dispatch this task directly. - @import("../link.zig").doTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index }); + @import("../link.zig").doZcuTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index }); } } @@ -4442,3 +4370,87 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep try info.deps.append(gpa, dependee); } } + +/// Performs code generation, which comes after `Sema` but before `link` in the pipeline. +/// This part of the pipeline is self-contained/"pure", so can be run in parallel with most +/// other code. This function is currently run either on the main thread, or on a separate +/// codegen thread, depending on whether the backend supports `Zcu.Feature.separate_thread`. +pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void { + if (runCodegenInner(pt, func_index, air)) |mir| { + out.value = mir; + out.status.store(.ready, .release); + } else |err| switch (err) { + error.OutOfMemory => { + pt.zcu.comp.setAllocFailure(); + out.status.store(.failed, .monotonic); + }, + error.CodegenFail => { + pt.zcu.assertCodegenFailed(pt.zcu.funcInfo(func_index).owner_nav); + out.status.store(.failed, .monotonic); + }, + error.NoLinkFile => { + assert(pt.zcu.comp.bin_file == null); + out.status.store(.failed, .monotonic); + }, + } + pt.zcu.comp.link_task_queue.mirReady(pt.zcu.comp, out); +} +fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{ OutOfMemory, CodegenFail, NoLinkFile }!codegen.AnyMir { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const comp = zcu.comp; + + const nav = zcu.funcInfo(func_index).owner_nav; + const fqn = ip.getNav(nav).fqn; + + const codegen_prog_node = zcu.codegen_prog_node.start(fqn.toSlice(ip), 0); + defer codegen_prog_node.end(); + + if (codegen.legalizeFeatures(pt, nav)) |features| { + try air.legalize(pt, features); + } + + var liveness: Air.Liveness = try .analyze(zcu, air.*, ip); + defer liveness.deinit(gpa); + + // TODO: surely writing to stderr from n threads simultaneously will work flawlessly + if (build_options.enable_debug_extensions and comp.verbose_air) { + std.debug.print("# Begin Function AIR: {}:\n", .{fqn.fmt(ip)}); + air.dump(pt, liveness); + std.debug.print("# End Function AIR: {}\n\n", .{fqn.fmt(ip)}); + } + + if (std.debug.runtime_safety) { + var verify: Air.Liveness.Verify = .{ + .gpa = gpa, + .zcu = zcu, + .air = air.*, + .liveness = liveness, + .intern_pool = ip, + }; + defer verify.deinit(); + + verify.verify() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => return zcu.codegenFail(nav, "invalid liveness: {s}", .{@errorName(err)}), + }; + } + + // The LLVM backend is special, because we only need to do codegen. There is no equivalent to the + // "emit" step because LLVM does not support incremental linking. Our linker (LLD or self-hosted) + // will just see the ZCU object file which LLVM ultimately emits. + if (zcu.llvm_object) |llvm_object| { + return llvm_object.updateFunc(pt, func_index, air, &liveness); + } + + const lf = comp.bin_file orelse return error.NoLinkFile; + return codegen.generateFunction(lf, pt, zcu.navSrcLoc(nav), func_index, air, &liveness) catch |err| switch (err) { + error.OutOfMemory, + error.CodegenFail, + => |e| return e, + error.Overflow, + error.RelocationNotByteAligned, + => return zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)}), + }; +} diff --git a/src/codegen.zig b/src/codegen.zig index a2de3e2d01..2c2524257c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -85,16 +85,104 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co } } +/// Every code generation backend has a different MIR representation. However, we want to pass +/// MIR from codegen to the linker *regardless* of which backend is in use. So, we use this: a +/// union of all MIR types. The active tag is known from the backend in use; see `AnyMir.tag`. +pub const AnyMir = union { + aarch64: @import("arch/aarch64/Mir.zig"), + arm: @import("arch/arm/Mir.zig"), + powerpc: noreturn, //@import("arch/powerpc/Mir.zig"), + riscv64: @import("arch/riscv64/Mir.zig"), + sparc64: @import("arch/sparc64/Mir.zig"), + x86_64: @import("arch/x86_64/Mir.zig"), + wasm: @import("arch/wasm/Mir.zig"), + c: @import("codegen/c.zig").Mir, + + pub inline fn tag(comptime backend: std.builtin.CompilerBackend) []const u8 { + return switch (backend) { + .stage2_aarch64 => "aarch64", + .stage2_arm => "arm", + .stage2_powerpc => "powerpc", + .stage2_riscv64 => "riscv64", + .stage2_sparc64 => "sparc64", + .stage2_x86_64 => "x86_64", + .stage2_wasm => "wasm", + .stage2_c => "c", + else => unreachable, + }; + } + + pub fn deinit(mir: *AnyMir, zcu: *const Zcu) void { + const gpa = zcu.gpa; + const backend = target_util.zigBackend(zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm); + switch (backend) { + else => unreachable, + inline .stage2_aarch64, + .stage2_arm, + .stage2_powerpc, + .stage2_riscv64, + .stage2_sparc64, + .stage2_x86_64, + .stage2_c, + => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa), + } + } +}; + +/// Runs code generation for a function. This process converts the `Air` emitted by `Sema`, +/// alongside annotated `Liveness` data, to machine code in the form of MIR (see `AnyMir`). +/// +/// This is supposed to be a "pure" process, but some backends are currently buggy; see +/// `Zcu.Feature.separate_thread` for details. pub fn generateFunction( lf: *link.File, pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + air: *const Air, + liveness: *const Air.Liveness, +) CodeGenError!AnyMir { + const zcu = pt.zcu; + const func = zcu.funcInfo(func_index); + const target = zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result; + switch (target_util.zigBackend(target, false)) { + else => unreachable, + inline .stage2_aarch64, + .stage2_arm, + .stage2_powerpc, + .stage2_riscv64, + .stage2_sparc64, + .stage2_x86_64, + .stage2_c, + => |backend| { + dev.check(devFeatureForBackend(backend)); + const CodeGen = importBackend(backend); + const mir = try CodeGen.generate(lf, pt, src_loc, func_index, air, liveness); + return @unionInit(AnyMir, AnyMir.tag(backend), mir); + }, + } +} + +/// Converts the MIR returned by `generateFunction` to finalized machine code to be placed in +/// the output binary. This is called from linker implementations, and may query linker state. +/// +/// This function is not called for the C backend, as `link.C` directly understands its MIR. +/// +/// The `air` parameter is not supposed to exist, but some backends are currently buggy; see +/// `Zcu.Feature.separate_thread` for details. +pub fn emitFunction( + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + any_mir: *const AnyMir, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, -) CodeGenError!void { + /// TODO: this parameter needs to be removed. We should not still hold AIR this late + /// in the pipeline. Any information needed to call emit must be stored in MIR. + /// This is `undefined` if the backend supports the `separate_thread` feature. + air: *const Air, +) Allocator.Error!void { const zcu = pt.zcu; const func = zcu.funcInfo(func_index); const target = zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result; @@ -108,7 +196,8 @@ pub fn generateFunction( .stage2_x86_64, => |backend| { dev.check(devFeatureForBackend(backend)); - return importBackend(backend).generate(lf, pt, src_loc, func_index, air, liveness, code, debug_output); + const mir = &@field(any_mir, AnyMir.tag(backend)); + return mir.emit(lf, pt, src_loc, func_index, code, debug_output, air); }, } } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3b8ab52982..f4952d4a58 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const mem = std.mem; const log = std.log.scoped(.c); +const Allocator = mem.Allocator; const dev = @import("../dev.zig"); const link = @import("../link.zig"); @@ -30,6 +31,35 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { }) else null; // we don't currently ask zig1 to use safe optimization modes } +/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some +/// "pseudo instructions" thrown in. For the C backend, it is instead the generated C code for a +/// single function. We also need to track some information to get merged into the global `link.C` +/// state, including: +/// * The UAVs used, so declarations can be emitted in `flush` +/// * The types used, so declarations can be emitted in `flush` +/// * The lazy functions used, so definitions can be emitted in `flush` +pub const Mir = struct { + /// This map contains all the UAVs we saw generating this function. + /// `link.C` will merge them into its `uavs`/`aligned_uavs` fields. + /// Key is the value of the UAV; value is the UAV's alignment, or + /// `.none` for natural alignment. The specified alignment is never + /// less than the natural alignment. + uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment), + // These remaining fields are essentially just an owned version of `link.C.AvBlock`. + code: []u8, + fwd_decl: []u8, + ctype_pool: CType.Pool, + lazy_fns: LazyFnMap, + + pub fn deinit(mir: *Mir, gpa: Allocator) void { + mir.uavs.deinit(gpa); + gpa.free(mir.code); + gpa.free(mir.fwd_decl); + mir.ctype_pool.deinit(gpa); + mir.lazy_fns.deinit(gpa); + } +}; + pub const CType = @import("c/Type.zig"); pub const CValue = union(enum) { @@ -671,7 +701,7 @@ pub const Object = struct { /// This data is available both when outputting .c code and when outputting an .h file. pub const DeclGen = struct { - gpa: mem.Allocator, + gpa: Allocator, pt: Zcu.PerThread, mod: *Module, pass: Pass, @@ -682,10 +712,12 @@ pub const DeclGen = struct { error_msg: ?*Zcu.ErrorMsg, ctype_pool: CType.Pool, scratch: std.ArrayListUnmanaged(u32), - /// Keeps track of anonymous decls that need to be rendered before this - /// (named) Decl in the output C code. - uav_deps: std.AutoArrayHashMapUnmanaged(InternPool.Index, C.AvBlock), - aligned_uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment), + /// This map contains all the UAVs we saw generating this function. + /// `link.C` will merge them into its `uavs`/`aligned_uavs` fields. + /// Key is the value of the UAV; value is the UAV's alignment, or + /// `.none` for natural alignment. The specified alignment is never + /// less than the natural alignment. + uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment), pub const Pass = union(enum) { nav: InternPool.Nav.Index, @@ -753,21 +785,17 @@ pub const DeclGen = struct { // Indicate that the anon decl should be rendered to the output so that // our reference above is not undefined. const ptr_type = ip.indexToKey(uav.orig_ty).ptr_type; - const gop = try dg.uav_deps.getOrPut(dg.gpa, uav.val); - if (!gop.found_existing) gop.value_ptr.* = .{}; - - // Only insert an alignment entry if the alignment is greater than ABI - // alignment. If there is already an entry, keep the greater alignment. - const explicit_alignment = ptr_type.flags.alignment; - if (explicit_alignment != .none) { - const abi_alignment = Type.fromInterned(ptr_type.child).abiAlignment(zcu); - if (explicit_alignment.order(abi_alignment).compare(.gt)) { - const aligned_gop = try dg.aligned_uavs.getOrPut(dg.gpa, uav.val); - aligned_gop.value_ptr.* = if (aligned_gop.found_existing) - aligned_gop.value_ptr.maxStrict(explicit_alignment) - else - explicit_alignment; - } + const gop = try dg.uavs.getOrPut(dg.gpa, uav.val); + if (!gop.found_existing) gop.value_ptr.* = .none; + // If there is an explicit alignment, greater than the current one, use it. + // Note that we intentionally start at `.none`, so `gop.value_ptr.*` is never + // underaligned, so we don't need to worry about the `.none` case here. + if (ptr_type.flags.alignment != .none) { + // Resolve the current alignment so we can choose the bigger one. + const cur_alignment: Alignment = if (gop.value_ptr.* == .none) abi: { + break :abi Type.fromInterned(ptr_type.child).abiAlignment(zcu); + } else gop.value_ptr.*; + gop.value_ptr.* = cur_alignment.maxStrict(ptr_type.flags.alignment); } } @@ -2895,7 +2923,79 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn } } -pub fn genFunc(f: *Function) !void { +pub fn generate( + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + air: *const Air, + liveness: *const Air.Liveness, +) @import("../codegen.zig").CodeGenError!Mir { + const zcu = pt.zcu; + const gpa = zcu.gpa; + + _ = src_loc; + assert(lf.tag == .c); + + const func = zcu.funcInfo(func_index); + + var function: Function = .{ + .value_map = .init(gpa), + .air = air.*, + .liveness = liveness.*, + .func_index = func_index, + .object = .{ + .dg = .{ + .gpa = gpa, + .pt = pt, + .mod = zcu.navFileScope(func.owner_nav).mod.?, + .error_msg = null, + .pass = .{ .nav = func.owner_nav }, + .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked, + .expected_block = null, + .fwd_decl = .init(gpa), + .ctype_pool = .empty, + .scratch = .empty, + .uavs = .empty, + }, + .code = .init(gpa), + .indent_writer = undefined, // set later so we can get a pointer to object.code + }, + .lazy_fns = .empty, + }; + defer { + function.object.code.deinit(); + function.object.dg.fwd_decl.deinit(); + function.object.dg.ctype_pool.deinit(gpa); + function.object.dg.scratch.deinit(gpa); + function.object.dg.uavs.deinit(gpa); + function.deinit(); + } + try function.object.dg.ctype_pool.init(gpa); + function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() }; + + genFunc(&function) catch |err| switch (err) { + error.AnalysisFail => return zcu.codegenFailMsg(func.owner_nav, function.object.dg.error_msg.?), + error.OutOfMemory => |e| return e, + }; + + var mir: Mir = .{ + .uavs = .empty, + .code = &.{}, + .fwd_decl = &.{}, + .ctype_pool = .empty, + .lazy_fns = .empty, + }; + errdefer mir.deinit(gpa); + mir.uavs = function.object.dg.uavs.move(); + mir.code = try function.object.code.toOwnedSlice(); + mir.fwd_decl = try function.object.dg.fwd_decl.toOwnedSlice(); + mir.ctype_pool = function.object.dg.ctype_pool.move(); + mir.lazy_fns = function.lazy_fns.move(); + return mir; +} + +fn genFunc(f: *Function) !void { const tracy = trace(@src()); defer tracy.end(); @@ -8482,7 +8582,7 @@ fn iterateBigTomb(f: *Function, inst: Air.Inst.Index) BigTomb { /// A naive clone of this map would create copies of the ArrayList which is /// stored in the values. This function additionally clones the values. -fn cloneFreeLocalsMap(gpa: mem.Allocator, map: *LocalsMap) !LocalsMap { +fn cloneFreeLocalsMap(gpa: Allocator, map: *LocalsMap) !LocalsMap { var cloned = try map.clone(gpa); const values = cloned.values(); var i: usize = 0; @@ -8499,7 +8599,7 @@ fn cloneFreeLocalsMap(gpa: mem.Allocator, map: *LocalsMap) !LocalsMap { return cloned; } -fn deinitFreeLocalsMap(gpa: mem.Allocator, map: *LocalsMap) void { +fn deinitFreeLocalsMap(gpa: Allocator, map: *LocalsMap) void { for (map.values()) |*value| { value.deinit(gpa); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 37c13c7211..e30e8f70a3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1121,8 +1121,8 @@ pub const Object = struct { o: *Object, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + air: *const Air, + liveness: *const Air.Liveness, ) !void { assert(std.meta.eql(pt, o.pt)); const zcu = pt.zcu; @@ -1479,8 +1479,8 @@ pub const Object = struct { var fg: FuncGen = .{ .gpa = gpa, - .air = air, - .liveness = liveness, + .air = air.*, + .liveness = liveness.*, .ng = &ng, .wip = wip, .is_naked = fn_info.cc == .naked, @@ -1506,10 +1506,9 @@ pub const Object = struct { deinit_wip = false; fg.genBody(air.getMainBody(), .poi) catch |err| switch (err) { - error.CodegenFail => { - try zcu.failed_codegen.put(gpa, func.owner_nav, ng.err_msg.?); - ng.err_msg = null; - return; + error.CodegenFail => switch (zcu.codegenFailMsg(func.owner_nav, ng.err_msg.?)) { + error.CodegenFail => return, + error.OutOfMemory => |e| return e, }, else => |e| return e, }; @@ -1561,10 +1560,9 @@ pub const Object = struct { .err_msg = null, }; ng.genDecl() catch |err| switch (err) { - error.CodegenFail => { - try pt.zcu.failed_codegen.put(pt.zcu.gpa, nav_index, ng.err_msg.?); - ng.err_msg = null; - return; + error.CodegenFail => switch (pt.zcu.codegenFailMsg(nav_index, ng.err_msg.?)) { + error.CodegenFail => return, + error.OutOfMemory => |e| return e, }, else => |e| return e, }; diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f83c6979ff..e6c06d9f20 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -230,8 +230,9 @@ pub const Object = struct { defer nav_gen.deinit(); nav_gen.genNav(do_codegen) catch |err| switch (err) { - error.CodegenFail => { - try zcu.failed_codegen.put(gpa, nav_index, nav_gen.error_msg.?); + error.CodegenFail => switch (zcu.codegenFailMsg(nav_index, nav_gen.error_msg.?)) { + error.CodegenFail => {}, + error.OutOfMemory => |e| return e, }, else => |other| { // There might be an error that happened *after* self.error_msg diff --git a/src/dev.zig b/src/dev.zig index 1dc8264ebc..2438ae6df7 100644 --- a/src/dev.zig +++ b/src/dev.zig @@ -25,6 +25,9 @@ pub const Env = enum { /// - `zig build-* -fno-emit-bin` sema, + /// - `zig build-* -ofmt=c` + cbe, + /// - sema /// - `zig build-* -fincremental -fno-llvm -fno-lld -target x86_64-linux --listen=-` @"x86_64-linux", @@ -144,6 +147,12 @@ pub const Env = enum { => true, else => Env.ast_gen.supports(feature), }, + .cbe => switch (feature) { + .c_backend, + .c_linker, + => true, + else => Env.sema.supports(feature), + }, .@"x86_64-linux" => switch (feature) { .build_command, .stdio_listen, diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index 47fef32773..98d4a42f91 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -1004,7 +1004,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { } } - comp.queueLinkTasks(task_buffer[0..task_buffer_i]); + comp.queuePrelinkTasks(task_buffer[0..task_buffer_i]); } fn buildSharedLib( diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index cc781c5472..c1146d933d 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -1170,7 +1170,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { } } - comp.queueLinkTasks(task_buffer[0..task_buffer_i]); + comp.queuePrelinkTasks(task_buffer[0..task_buffer_i]); } fn buildSharedLib( diff --git a/src/libs/libcxx.zig b/src/libs/libcxx.zig index 17a7d3d29e..eb9f5df855 100644 --- a/src/libs/libcxx.zig +++ b/src/libs/libcxx.zig @@ -308,7 +308,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! assert(comp.libcxx_static_lib == null); const crt_file = try sub_compilation.toCrtFile(); comp.libcxx_static_lib = crt_file; - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); } pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void { @@ -504,7 +504,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr assert(comp.libcxxabi_static_lib == null); const crt_file = try sub_compilation.toCrtFile(); comp.libcxxabi_static_lib = crt_file; - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); } pub fn addCxxArgs( diff --git a/src/libs/libtsan.zig b/src/libs/libtsan.zig index 8a5ffd2eab..0c59d85bc5 100644 --- a/src/libs/libtsan.zig +++ b/src/libs/libtsan.zig @@ -325,7 +325,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo }; const crt_file = try sub_compilation.toCrtFile(); - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); assert(comp.tsan_lib == null); comp.tsan_lib = crt_file; } diff --git a/src/libs/libunwind.zig b/src/libs/libunwind.zig index 945689ebab..ccea649c17 100644 --- a/src/libs/libunwind.zig +++ b/src/libs/libunwind.zig @@ -195,7 +195,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr }; const crt_file = try sub_compilation.toCrtFile(); - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); assert(comp.libunwind_static_lib == null); comp.libunwind_static_lib = crt_file; } diff --git a/src/libs/musl.zig b/src/libs/musl.zig index d208b09827..21aeee98b5 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -278,7 +278,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro errdefer comp.gpa.free(basename); const crt_file = try sub_compilation.toCrtFile(); - comp.queueLinkTaskMode(crt_file.full_object_path, &config); + comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); { comp.mutex.lock(); defer comp.mutex.unlock(); diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 718861bf5c..aab75cce49 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -669,7 +669,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { } } - comp.queueLinkTasks(task_buffer[0..task_buffer_i]); + comp.queuePrelinkTasks(task_buffer[0..task_buffer_i]); } fn buildSharedLib( diff --git a/src/link.zig b/src/link.zig index 4b4c3c611b..31fd0a4a4e 100644 --- a/src/link.zig +++ b/src/link.zig @@ -21,11 +21,11 @@ const Type = @import("Type.zig"); const Value = @import("Value.zig"); const Package = @import("Package.zig"); const dev = @import("dev.zig"); -const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue; const target_util = @import("target.zig"); const codegen = @import("codegen.zig"); pub const LdScript = @import("link/LdScript.zig"); +pub const Queue = @import("link/Queue.zig"); pub const Diags = struct { /// Stored here so that function definitions can distinguish between @@ -741,21 +741,26 @@ pub const File = struct { } /// May be called before or after updateExports for any given Decl. - /// TODO: currently `pub` because `Zcu.PerThread` is calling this. + /// The active tag of `mir` is determined by the backend used for the module this function is in. /// Never called when LLVM is codegenning the ZCU. - pub fn updateFunc( + fn updateFunc( base: *File, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + /// This is owned by the caller, but the callee is permitted to mutate it provided + /// that `mir.deinit` remains legal for the caller. For instance, the callee can + /// take ownership of an embedded slice and replace it with `&.{}` in `mir`. + mir: *codegen.AnyMir, + /// This may be `undefined`; only pass it to `emitFunction`. + /// This parameter will eventually be removed. + maybe_undef_air: *const Air, ) UpdateNavError!void { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { .lld => unreachable, inline else => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, air, liveness); + return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, mir, maybe_undef_air); }, } } @@ -1213,40 +1218,7 @@ pub const File = struct { pub const Dwarf = @import("link/Dwarf.zig"); }; -/// Does all the tasks in the queue. Runs in exactly one separate thread -/// from the rest of compilation. All tasks performed here are -/// single-threaded with respect to one another. -pub fn flushTaskQueue(tid: usize, comp: *Compilation) void { - const diags = &comp.link_diags; - // As soon as check() is called, another `flushTaskQueue` call could occur, - // so the safety lock must go after the check. - while (comp.link_task_queue.check()) |tasks| { - comp.link_task_queue_safety.lock(); - defer comp.link_task_queue_safety.unlock(); - - if (comp.remaining_prelink_tasks > 0) { - comp.link_task_queue_postponed.ensureUnusedCapacity(comp.gpa, tasks.len) catch |err| switch (err) { - error.OutOfMemory => return diags.setAllocFailure(), - }; - } - - for (tasks) |task| doTask(comp, tid, task); - - if (comp.remaining_prelink_tasks == 0) { - if (comp.bin_file) |base| if (!base.post_prelink) { - base.prelink(comp.work_queue_progress_node) catch |err| switch (err) { - error.OutOfMemory => diags.setAllocFailure(), - error.LinkFailure => continue, - }; - base.post_prelink = true; - for (comp.link_task_queue_postponed.items) |task| doTask(comp, tid, task); - comp.link_task_queue_postponed.clearRetainingCapacity(); - }; - } - } -} - -pub const Task = union(enum) { +pub const PrelinkTask = union(enum) { /// Loads the objects, shared objects, and archives that are already /// known from the command line. load_explicitly_provided, @@ -1264,31 +1236,70 @@ pub const Task = union(enum) { /// Tells the linker to load an input which could be an object file, /// archive, or shared library. load_input: Input, - +}; +pub const ZcuTask = union(enum) { /// Write the constant value for a Decl to the output file. link_nav: InternPool.Nav.Index, /// Write the machine code for a function to the output file. - link_func: CodegenFunc, + link_func: LinkFunc, link_type: InternPool.Index, - update_line_number: InternPool.TrackedInst.Index, - - pub const CodegenFunc = struct { + pub fn deinit(task: ZcuTask, zcu: *const Zcu) void { + switch (task) { + .link_nav, + .link_type, + .update_line_number, + => {}, + .link_func => |link_func| { + switch (link_func.mir.status.load(.monotonic)) { + .pending => unreachable, // cannot deinit until MIR done + .failed => {}, // MIR not populated so doesn't need freeing + .ready => link_func.mir.value.deinit(zcu), + } + zcu.gpa.destroy(link_func.mir); + }, + } + } + pub const LinkFunc = struct { /// This will either be a non-generic `func_decl` or a `func_instance`. func: InternPool.Index, - /// This `Air` is owned by the `Job` and allocated with `gpa`. - /// It must be deinited when the job is processed. - air: Air, + /// This pointer is allocated into `gpa` and must be freed when the `ZcuTask` is processed. + /// The pointer is shared with the codegen worker, which will populate the MIR inside once + /// it has been generated. It's important that the `link_func` is queued at the same time as + /// the codegen job to ensure that the linker receives functions in a deterministic order, + /// allowing reproducible builds. + mir: *SharedMir, + /// This field exists only due to deficiencies in some codegen implementations; it should + /// be removed when the corresponding parameter of `CodeGen.emitFunction` can be removed. + /// This is `undefined` if `Zcu.Feature.separate_thread` is supported. + /// If this is defined, its memory is owned externally; do not `deinit` this `air`. + air: *const Air, + + pub const SharedMir = struct { + /// This is initially `.pending`. When `value` is populated, the codegen thread will set + /// this to `.ready`, and alert the queue if needed. It could also end up `.failed`. + /// The action of storing a value (other than `.pending`) to this atomic transfers + /// ownership of memory assoicated with `value` to this `ZcuTask`. + status: std.atomic.Value(enum(u8) { + /// We are waiting on codegen to generate MIR (or die trying). + pending, + /// `value` is not populated and will not be populated. Just drop the task from the queue and move on. + failed, + /// `value` is populated with the MIR from the backend in use, which is not LLVM. + ready, + }), + /// This is `undefined` until `ready` is set to `true`. Once populated, this MIR belongs + /// to the `ZcuTask`, and must be `deinit`ed when it is processed. Allocated into `gpa`. + value: codegen.AnyMir, + }; }; }; -pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { +pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { const diags = &comp.link_diags; + const base = comp.bin_file orelse return; switch (task) { .load_explicitly_provided => { - comp.remaining_prelink_tasks -= 1; - const base = comp.bin_file orelse return; - const prog_node = comp.work_queue_progress_node.start("Parse Linker Inputs", comp.link_inputs.len); defer prog_node.end(); for (comp.link_inputs) |input| { @@ -1306,9 +1317,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { } }, .load_host_libc => { - comp.remaining_prelink_tasks -= 1; - const base = comp.bin_file orelse return; - const prog_node = comp.work_queue_progress_node.start("Linker Parse Host libc", 0); defer prog_node.end(); @@ -1368,8 +1376,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { } }, .load_object => |path| { - comp.remaining_prelink_tasks -= 1; - const base = comp.bin_file orelse return; const prog_node = comp.work_queue_progress_node.start("Linker Parse Object", 0); defer prog_node.end(); base.openLoadObject(path) catch |err| switch (err) { @@ -1378,8 +1384,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { }; }, .load_archive => |path| { - comp.remaining_prelink_tasks -= 1; - const base = comp.bin_file orelse return; const prog_node = comp.work_queue_progress_node.start("Linker Parse Archive", 0); defer prog_node.end(); base.openLoadArchive(path, null) catch |err| switch (err) { @@ -1388,8 +1392,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { }; }, .load_dso => |path| { - comp.remaining_prelink_tasks -= 1; - const base = comp.bin_file orelse return; const prog_node = comp.work_queue_progress_node.start("Linker Parse Shared Library", 0); defer prog_node.end(); base.openLoadDso(path, .{ @@ -1401,8 +1403,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { }; }, .load_input => |input| { - comp.remaining_prelink_tasks -= 1; - const base = comp.bin_file orelse return; const prog_node = comp.work_queue_progress_node.start("Linker Parse Input", 0); defer prog_node.end(); base.loadInput(input) catch |err| switch (err) { @@ -1416,11 +1416,12 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { }, }; }, + } +} +pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { + const diags = &comp.link_diags; + switch (task) { .link_nav => |nav_index| { - if (comp.remaining_prelink_tasks != 0) { - comp.link_task_queue_postponed.appendAssumeCapacity(task); - return; - } const zcu = comp.zcu.?; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); @@ -1431,39 +1432,43 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { } else if (comp.bin_file) |lf| { lf.updateNav(pt, nav_index) catch |err| switch (err) { error.OutOfMemory => diags.setAllocFailure(), - error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)), + error.CodegenFail => zcu.assertCodegenFailed(nav_index), error.Overflow, error.RelocationNotByteAligned => { - zcu.failed_codegen.ensureUnusedCapacity(zcu.gpa, 1) catch return diags.setAllocFailure(); - const msg = Zcu.ErrorMsg.create( - zcu.gpa, - zcu.navSrcLoc(nav_index), - "unable to codegen: {s}", - .{@errorName(err)}, - ) catch return diags.setAllocFailure(); - zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, msg); + switch (zcu.codegenFail(nav_index, "unable to codegen: {s}", .{@errorName(err)})) { + error.CodegenFail => return, + error.OutOfMemory => return diags.setAllocFailure(), + } // Not a retryable failure. }, }; } }, .link_func => |func| { - if (comp.remaining_prelink_tasks != 0) { - comp.link_task_queue_postponed.appendAssumeCapacity(task); - return; - } - const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); + const zcu = comp.zcu.?; + const nav = zcu.funcInfo(func.func).owner_nav; + const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); - var air = func.air; - defer air.deinit(comp.gpa); - pt.linkerUpdateFunc(func.func, &air) catch |err| switch (err) { - error.OutOfMemory => diags.setAllocFailure(), - }; + assert(zcu.llvm_object == null); // LLVM codegen doesn't produce MIR + switch (func.mir.status.load(.monotonic)) { + .pending => unreachable, + .ready => {}, + .failed => return, + } + const mir = &func.mir.value; + if (comp.bin_file) |lf| { + lf.updateFunc(pt, func.func, mir, func.air) catch |err| switch (err) { + error.OutOfMemory => return diags.setAllocFailure(), + error.CodegenFail => return zcu.assertCodegenFailed(nav), + error.Overflow, error.RelocationNotByteAligned => { + switch (zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)})) { + error.OutOfMemory => return diags.setAllocFailure(), + error.CodegenFail => return, + } + }, + }; + } }, .link_type => |ty| { - if (comp.remaining_prelink_tasks != 0) { - comp.link_task_queue_postponed.appendAssumeCapacity(task); - return; - } const zcu = comp.zcu.?; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); @@ -1477,10 +1482,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { } }, .update_line_number => |ti| { - if (comp.remaining_prelink_tasks != 0) { - comp.link_task_queue_postponed.appendAssumeCapacity(task); - return; - } const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); if (pt.zcu.llvm_object == null) { diff --git a/src/link/C.zig b/src/link/C.zig index 34fc1d3775..417ebcdee6 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -18,6 +18,7 @@ const trace = @import("../tracy.zig").trace; const Type = @import("../Type.zig"); const Value = @import("../Value.zig"); const Air = @import("../Air.zig"); +const AnyMir = @import("../codegen.zig").AnyMir; pub const zig_h = "#include \"zig.h\"\n"; @@ -166,6 +167,9 @@ pub fn deinit(self: *C) void { self.uavs.deinit(gpa); self.aligned_uavs.deinit(gpa); + self.exported_navs.deinit(gpa); + self.exported_uavs.deinit(gpa); + self.string_bytes.deinit(gpa); self.fwd_decl_buf.deinit(gpa); self.code_buf.deinit(gpa); @@ -177,73 +181,28 @@ pub fn updateFunc( self: *C, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *AnyMir, + /// This may be `undefined`; only pass it to `emitFunction`. + /// This parameter will eventually be removed. + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { + _ = maybe_undef_air; // It would be a bug to use this argument. + const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); - const gop = try self.navs.getOrPut(gpa, func.owner_nav); - if (!gop.found_existing) gop.value_ptr.* = .{}; - const ctype_pool = &gop.value_ptr.ctype_pool; - const lazy_fns = &gop.value_ptr.lazy_fns; - const fwd_decl = &self.fwd_decl_buf; - const code = &self.code_buf; - try ctype_pool.init(gpa); - ctype_pool.clearRetainingCapacity(); - lazy_fns.clearRetainingCapacity(); - fwd_decl.clearRetainingCapacity(); - code.clearRetainingCapacity(); - var function: codegen.Function = .{ - .value_map = codegen.CValueMap.init(gpa), - .air = air, - .liveness = liveness, - .func_index = func_index, - .object = .{ - .dg = .{ - .gpa = gpa, - .pt = pt, - .mod = zcu.navFileScope(func.owner_nav).mod.?, - .error_msg = null, - .pass = .{ .nav = func.owner_nav }, - .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked, - .expected_block = null, - .fwd_decl = fwd_decl.toManaged(gpa), - .ctype_pool = ctype_pool.*, - .scratch = .{}, - .uav_deps = self.uavs, - .aligned_uavs = self.aligned_uavs, - }, - .code = code.toManaged(gpa), - .indent_writer = undefined, // set later so we can get a pointer to object.code - }, - .lazy_fns = lazy_fns.*, - }; - function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() }; - defer { - self.uavs = function.object.dg.uav_deps; - self.aligned_uavs = function.object.dg.aligned_uavs; - fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged(); - ctype_pool.* = function.object.dg.ctype_pool.move(); - ctype_pool.freeUnusedCapacity(gpa); - function.object.dg.scratch.deinit(gpa); - lazy_fns.* = function.lazy_fns.move(); - lazy_fns.shrinkAndFree(gpa, lazy_fns.count()); - code.* = function.object.code.moveToUnmanaged(); - function.deinit(); - } - - try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1); - codegen.genFunc(&function) catch |err| switch (err) { - error.AnalysisFail => { - zcu.failed_codegen.putAssumeCapacityNoClobber(func.owner_nav, function.object.dg.error_msg.?); - return; - }, - else => |e| return e, + const gop = try self.navs.getOrPut(gpa, func.owner_nav); + if (gop.found_existing) gop.value_ptr.deinit(gpa); + gop.value_ptr.* = .{ + .code = .empty, + .fwd_decl = .empty, + .ctype_pool = mir.c.ctype_pool.move(), + .lazy_fns = mir.c.lazy_fns.move(), }; - gop.value_ptr.fwd_decl = try self.addString(function.object.dg.fwd_decl.items); - gop.value_ptr.code = try self.addString(function.object.code.items); + gop.value_ptr.code = try self.addString(mir.c.code); + gop.value_ptr.fwd_decl = try self.addString(mir.c.fwd_decl); + try self.addUavsFromCodegen(&mir.c.uavs); } fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void { @@ -267,16 +226,14 @@ fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void { .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = codegen.CType.Pool.empty, .scratch = .{}, - .uav_deps = self.uavs, - .aligned_uavs = self.aligned_uavs, + .uavs = .empty, }, .code = code.toManaged(gpa), .indent_writer = undefined, // set later so we can get a pointer to object.code }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - self.uavs = object.dg.uav_deps; - self.aligned_uavs = object.dg.aligned_uavs; + object.dg.uavs.deinit(gpa); fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); object.dg.ctype_pool.deinit(object.dg.gpa); object.dg.scratch.deinit(gpa); @@ -295,8 +252,10 @@ fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void { else => |e| return e, }; + try self.addUavsFromCodegen(&object.dg.uavs); + object.dg.ctype_pool.freeUnusedCapacity(gpa); - object.dg.uav_deps.values()[i] = .{ + self.uavs.values()[i] = .{ .code = try self.addString(object.code.items), .fwd_decl = try self.addString(object.dg.fwd_decl.items), .ctype_pool = object.dg.ctype_pool.move(), @@ -343,16 +302,14 @@ pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) l .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = ctype_pool.*, .scratch = .{}, - .uav_deps = self.uavs, - .aligned_uavs = self.aligned_uavs, + .uavs = .empty, }, .code = code.toManaged(gpa), .indent_writer = undefined, // set later so we can get a pointer to object.code }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - self.uavs = object.dg.uav_deps; - self.aligned_uavs = object.dg.aligned_uavs; + object.dg.uavs.deinit(gpa); fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); ctype_pool.* = object.dg.ctype_pool.move(); ctype_pool.freeUnusedCapacity(gpa); @@ -360,16 +317,16 @@ pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) l code.* = object.code.moveToUnmanaged(); } - try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1); codegen.genDecl(&object) catch |err| switch (err) { - error.AnalysisFail => { - zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, object.dg.error_msg.?); - return; + error.AnalysisFail => switch (zcu.codegenFailMsg(nav_index, object.dg.error_msg.?)) { + error.CodegenFail => return, + error.OutOfMemory => |e| return e, }, else => |e| return e, }; gop.value_ptr.code = try self.addString(object.code.items); gop.value_ptr.fwd_decl = try self.addString(object.dg.fwd_decl.items); + try self.addUavsFromCodegen(&object.dg.uavs); } pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { @@ -671,16 +628,14 @@ fn flushErrDecls(self: *C, pt: Zcu.PerThread, ctype_pool: *codegen.CType.Pool) F .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = ctype_pool.*, .scratch = .{}, - .uav_deps = self.uavs, - .aligned_uavs = self.aligned_uavs, + .uavs = .empty, }, .code = code.toManaged(gpa), .indent_writer = undefined, // set later so we can get a pointer to object.code }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - self.uavs = object.dg.uav_deps; - self.aligned_uavs = object.dg.aligned_uavs; + object.dg.uavs.deinit(gpa); fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); ctype_pool.* = object.dg.ctype_pool.move(); ctype_pool.freeUnusedCapacity(gpa); @@ -692,6 +647,8 @@ fn flushErrDecls(self: *C, pt: Zcu.PerThread, ctype_pool: *codegen.CType.Pool) F error.AnalysisFail => unreachable, else => |e| return e, }; + + try self.addUavsFromCodegen(&object.dg.uavs); } fn flushLazyFn( @@ -719,8 +676,7 @@ fn flushLazyFn( .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = ctype_pool.*, .scratch = .{}, - .uav_deps = .{}, - .aligned_uavs = .{}, + .uavs = .empty, }, .code = code.toManaged(gpa), .indent_writer = undefined, // set later so we can get a pointer to object.code @@ -729,8 +685,7 @@ fn flushLazyFn( defer { // If this assert trips just handle the anon_decl_deps the same as // `updateFunc()` does. - assert(object.dg.uav_deps.count() == 0); - assert(object.dg.aligned_uavs.count() == 0); + assert(object.dg.uavs.count() == 0); fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); ctype_pool.* = object.dg.ctype_pool.move(); ctype_pool.freeUnusedCapacity(gpa); @@ -866,12 +821,10 @@ pub fn updateExports( .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = decl_block.ctype_pool, .scratch = .{}, - .uav_deps = .{}, - .aligned_uavs = .{}, + .uavs = .empty, }; defer { - assert(dg.uav_deps.count() == 0); - assert(dg.aligned_uavs.count() == 0); + assert(dg.uavs.count() == 0); fwd_decl.* = dg.fwd_decl.moveToUnmanaged(); ctype_pool.* = dg.ctype_pool.move(); ctype_pool.freeUnusedCapacity(gpa); @@ -891,3 +844,21 @@ pub fn deleteExport( .uav => |uav| _ = self.exported_uavs.swapRemove(uav), } } + +fn addUavsFromCodegen(c: *C, uavs: *const std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment)) Allocator.Error!void { + const gpa = c.base.comp.gpa; + try c.uavs.ensureUnusedCapacity(gpa, uavs.count()); + try c.aligned_uavs.ensureUnusedCapacity(gpa, uavs.count()); + for (uavs.keys(), uavs.values()) |uav_val, uav_align| { + { + const gop = c.uavs.getOrPutAssumeCapacity(uav_val); + if (!gop.found_existing) gop.value_ptr.* = .{}; + } + if (uav_align != .none) { + const gop = c.aligned_uavs.getOrPutAssumeCapacity(uav_val); + gop.value_ptr.* = if (gop.found_existing) max: { + break :max gop.value_ptr.*.maxStrict(uav_align); + } else uav_align; + } + } +} diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e7dcbcdf2a..9a040754ef 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1079,7 +1079,7 @@ pub fn updateFunc( var code_buffer: std.ArrayListUnmanaged(u8) = .empty; defer code_buffer.deinit(gpa); - codegen.generateFunction( + try codegen.generateFunction( &coff.base, pt, zcu.navSrcLoc(nav_index), @@ -1088,20 +1088,7 @@ pub fn updateFunc( liveness, &code_buffer, .none, - ) catch |err| switch (err) { - error.CodegenFail => return error.CodegenFail, - error.OutOfMemory => return error.OutOfMemory, - error.Overflow, error.RelocationNotByteAligned => |e| { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "unable to codegen: {s}", - .{@errorName(e)}, - )); - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); - return error.CodegenFail; - }, - }; + ); try coff.updateNavCode(pt, nav_index, code_buffer.items, .FUNCTION); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 1702ef200c..34e04ad557 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1691,13 +1691,13 @@ pub fn updateFunc( self: *Elf, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - return self.zigObjectPtr().?.updateFunc(self, pt, func_index, air, liveness); + return self.zigObjectPtr().?.updateFunc(self, pt, func_index, mir, maybe_undef_air); } pub fn updateNav( diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index e377f3a9af..1a5ef4b408 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -1416,8 +1416,10 @@ pub fn updateFunc( elf_file: *Elf, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + /// This may be `undefined`; only pass it to `emitFunction`. + /// This parameter will eventually be removed. + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1438,15 +1440,15 @@ pub fn updateFunc( var debug_wip_nav = if (self.dwarf) |*dwarf| try dwarf.initWipNav(pt, func.owner_nav, sym_index) else null; defer if (debug_wip_nav) |*wip_nav| wip_nav.deinit(); - try codegen.generateFunction( + try codegen.emitFunction( &elf_file.base, pt, zcu.navSrcLoc(func.owner_nav), func_index, - air, - liveness, + mir, &code_buffer, if (debug_wip_nav) |*dn| .{ .dwarf = dn } else .none, + maybe_undef_air, ); const code = code_buffer.items; diff --git a/src/link/Queue.zig b/src/link/Queue.zig new file mode 100644 index 0000000000..c73a0e9684 --- /dev/null +++ b/src/link/Queue.zig @@ -0,0 +1,234 @@ +//! Stores and manages the queue of link tasks. Each task is either a `PrelinkTask` or a `ZcuTask`. +//! +//! There must be at most one link thread (the thread processing these tasks) active at a time. If +//! `!comp.separateCodegenThreadOk()`, then ZCU tasks will be run on the main thread, bypassing this +//! queue entirely. +//! +//! All prelink tasks must be processed before any ZCU tasks are processed. After all prelink tasks +//! are run, but before any ZCU tasks are run, `prelink` must be called on the `link.File`. +//! +//! There will sometimes be a `ZcuTask` in the queue which is not yet ready because it depends on +//! MIR which has not yet been generated by any codegen thread. In this case, we must pause +//! processing of linker tasks until the MIR is ready. It would be incorrect to run any other link +//! tasks first, since this would make builds unreproducible. + +mutex: std.Thread.Mutex, +/// Validates that only one `flushTaskQueue` thread is running at a time. +flush_safety: std.debug.SafetyLock, + +/// This is the number of prelink tasks which are expected but have not yet been enqueued. +/// Guarded by `mutex`. +pending_prelink_tasks: u32, + +/// Prelink tasks which have been enqueued and are not yet owned by the worker thread. +/// Allocated into `gpa`, guarded by `mutex`. +queued_prelink: std.ArrayListUnmanaged(PrelinkTask), +/// The worker thread moves items from `queued_prelink` into this array in order to process them. +/// Allocated into `gpa`, accessed only by the worker thread. +wip_prelink: std.ArrayListUnmanaged(PrelinkTask), + +/// Like `queued_prelink`, but for ZCU tasks. +/// Allocated into `gpa`, guarded by `mutex`. +queued_zcu: std.ArrayListUnmanaged(ZcuTask), +/// Like `wip_prelink`, but for ZCU tasks. +/// Allocated into `gpa`, accessed only by the worker thread. +wip_zcu: std.ArrayListUnmanaged(ZcuTask), + +/// When processing ZCU link tasks, we might have to block due to unpopulated MIR. When this +/// happens, some tasks in `wip_zcu` have been run, and some are still pending. This is the +/// index into `wip_zcu` which we have reached. +wip_zcu_idx: usize, + +/// Guarded by `mutex`. +state: union(enum) { + /// The link thread is currently running or queued to run. + running, + /// The link thread is not running or queued, because it has exhausted all immediately available + /// tasks. It should be spawned when more tasks are enqueued. If `pending_prelink_tasks` is not + /// zero, we are specifically waiting for prelink tasks. + finished, + /// The link thread is not running or queued, because it is waiting for this MIR to be populated. + /// Once codegen completes, it must call `mirReady` which will restart the link thread. + wait_for_mir: *ZcuTask.LinkFunc.SharedMir, +}, + +/// The initial `Queue` state, containing no tasks, expecting no prelink tasks, and with no running worker thread. +/// The `pending_prelink_tasks` and `queued_prelink` fields may be modified as needed before calling `start`. +pub const empty: Queue = .{ + .mutex = .{}, + .flush_safety = .{}, + .pending_prelink_tasks = 0, + .queued_prelink = .empty, + .wip_prelink = .empty, + .queued_zcu = .empty, + .wip_zcu = .empty, + .wip_zcu_idx = 0, + .state = .finished, +}; +/// `lf` is needed to correctly deinit any pending `ZcuTask`s. +pub fn deinit(q: *Queue, comp: *Compilation) void { + const gpa = comp.gpa; + for (q.queued_zcu.items) |t| t.deinit(comp.zcu.?); + for (q.wip_zcu.items[q.wip_zcu_idx..]) |t| t.deinit(comp.zcu.?); + q.queued_prelink.deinit(gpa); + q.wip_prelink.deinit(gpa); + q.queued_zcu.deinit(gpa); + q.wip_zcu.deinit(gpa); +} + +/// This is expected to be called exactly once, after which the caller must not directly access +/// `queued_prelink` or `pending_prelink_tasks` any longer. This will spawn the link thread if +/// necessary. +pub fn start(q: *Queue, comp: *Compilation) void { + assert(q.state == .finished); + assert(q.queued_zcu.items.len == 0); + if (q.queued_prelink.items.len != 0) { + q.state = .running; + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp }); + } +} + +/// Called by codegen workers after they have populated a `ZcuTask.LinkFunc.SharedMir`. If the link +/// thread was waiting for this MIR, it can resume. +pub fn mirReady(q: *Queue, comp: *Compilation, mir: *ZcuTask.LinkFunc.SharedMir) void { + // We would like to assert that `mir` is not pending, but that would race with a worker thread + // potentially freeing it. + { + q.mutex.lock(); + defer q.mutex.unlock(); + switch (q.state) { + .finished => unreachable, // there's definitely a task queued + .running => return, + .wait_for_mir => |wait_for| if (wait_for != mir) return, + } + // We were waiting for `mir`, so we will restart the linker thread. + q.state = .running; + } + assert(mir.status.load(.monotonic) != .pending); + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp }); +} + +/// Enqueues all prelink tasks in `tasks`. Asserts that they were expected, i.e. that `tasks.len` is +/// less than or equal to `q.pending_prelink_tasks`. Also asserts that `tasks.len` is not 0. +pub fn enqueuePrelink(q: *Queue, comp: *Compilation, tasks: []const PrelinkTask) Allocator.Error!void { + { + q.mutex.lock(); + defer q.mutex.unlock(); + try q.queued_prelink.appendSlice(comp.gpa, tasks); + q.pending_prelink_tasks -= @intCast(tasks.len); + switch (q.state) { + .wait_for_mir => unreachable, // we've not started zcu tasks yet + .running => return, + .finished => {}, + } + // Restart the linker thread, because it was waiting for a task + q.state = .running; + } + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp }); +} + +pub fn enqueueZcu(q: *Queue, comp: *Compilation, task: ZcuTask) Allocator.Error!void { + assert(comp.separateCodegenThreadOk()); + { + q.mutex.lock(); + defer q.mutex.unlock(); + try q.queued_zcu.append(comp.gpa, task); + switch (q.state) { + .running, .wait_for_mir => return, + .finished => if (q.pending_prelink_tasks != 0) return, + } + // Restart the linker thread, unless it would immediately be blocked + if (task == .link_func and task.link_func.mir.status.load(.monotonic) == .pending) { + q.state = .{ .wait_for_mir = task.link_func.mir }; + return; + } + q.state = .running; + } + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, flushTaskQueue, .{ q, comp }); +} + +fn flushTaskQueue(tid: usize, q: *Queue, comp: *Compilation) void { + q.flush_safety.lock(); + defer q.flush_safety.unlock(); + + if (std.debug.runtime_safety) { + q.mutex.lock(); + defer q.mutex.unlock(); + assert(q.state == .running); + } + prelink: while (true) { + assert(q.wip_prelink.items.len == 0); + { + q.mutex.lock(); + defer q.mutex.unlock(); + std.mem.swap(std.ArrayListUnmanaged(PrelinkTask), &q.queued_prelink, &q.wip_prelink); + if (q.wip_prelink.items.len == 0) { + if (q.pending_prelink_tasks == 0) { + break :prelink; // prelink is done + } else { + // We're expecting more prelink tasks so can't move on to ZCU tasks. + q.state = .finished; + return; + } + } + } + for (q.wip_prelink.items) |task| { + link.doPrelinkTask(comp, task); + } + q.wip_prelink.clearRetainingCapacity(); + } + + // We've finished the prelink tasks, so run prelink if necessary. + if (comp.bin_file) |lf| { + if (!lf.post_prelink) { + if (lf.prelink(comp.work_queue_progress_node)) |_| { + lf.post_prelink = true; + } else |err| switch (err) { + error.OutOfMemory => comp.link_diags.setAllocFailure(), + error.LinkFailure => {}, + } + } + } + + // Now we can run ZCU tasks. + while (true) { + if (q.wip_zcu.items.len == q.wip_zcu_idx) { + q.wip_zcu.clearRetainingCapacity(); + q.wip_zcu_idx = 0; + q.mutex.lock(); + defer q.mutex.unlock(); + std.mem.swap(std.ArrayListUnmanaged(ZcuTask), &q.queued_zcu, &q.wip_zcu); + if (q.wip_zcu.items.len == 0) { + // We've exhausted all available tasks. + q.state = .finished; + return; + } + } + const task = q.wip_zcu.items[q.wip_zcu_idx]; + // If the task is a `link_func`, we might have to stop until its MIR is populated. + pending: { + if (task != .link_func) break :pending; + const status_ptr = &task.link_func.mir.status; + // First check without the mutex to optimize for the common case where MIR is ready. + if (status_ptr.load(.monotonic) != .pending) break :pending; + q.mutex.lock(); + defer q.mutex.unlock(); + if (status_ptr.load(.monotonic) != .pending) break :pending; + // We will stop for now, and get restarted once this MIR is ready. + q.state = .{ .wait_for_mir = task.link_func.mir }; + return; + } + link.doZcuTask(comp, tid, task); + task.deinit(comp.zcu.?); + q.wip_zcu_idx += 1; + } +} + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Compilation = @import("../Compilation.zig"); +const link = @import("../link.zig"); +const PrelinkTask = link.PrelinkTask; +const ZcuTask = link.ZcuTask; +const Queue = @This(); diff --git a/src/target.zig b/src/target.zig index 6172b5e7e9..01c6a6cbf0 100644 --- a/src/target.zig +++ b/src/target.zig @@ -850,7 +850,9 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt }, .separate_thread => switch (backend) { .stage2_llvm => false, - else => true, + // MLUGG TODO + .stage2_c => true, + else => false, }, }; } -- cgit v1.2.3 From 5ab307cf47b1f0418d9ed4ab56df6fb798305c20 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 1 Jun 2025 22:57:59 +0100 Subject: compiler: get most backends compiling again As of this commit, every backend other than self-hosted Wasm and self-hosted SPIR-V compiles and (at least somewhat) functions again. Those two backends are currently disabled with panics. Note that `Zcu.Feature.separate_thread` is *not* enabled for the fixed backends. Avoiding linker references from codegen is a non-trivial task, and can be done after this branch. --- src/Compilation.zig | 8 ++-- src/Zcu/PerThread.zig | 28 +++++++++--- src/arch/aarch64/CodeGen.zig | 46 +++++++------------ src/arch/aarch64/Mir.zig | 43 ++++++++++++++++++ src/arch/arm/CodeGen.zig | 48 +++++++------------- src/arch/arm/Mir.zig | 43 ++++++++++++++++++ src/arch/powerpc/CodeGen.zig | 10 ++--- src/arch/riscv64/CodeGen.zig | 51 ++++++--------------- src/arch/riscv64/Mir.zig | 50 +++++++++++++++++++++ src/arch/sparc64/CodeGen.zig | 45 ++++++------------- src/arch/sparc64/Mir.zig | 39 +++++++++++++++- src/arch/x86_64/CodeGen.zig | 104 ++++++++++++++----------------------------- src/arch/x86_64/Mir.zig | 65 +++++++++++++++++++++++++++ src/codegen.zig | 2 +- src/libs/freebsd.zig | 2 +- src/libs/glibc.zig | 2 +- src/libs/netbsd.zig | 2 +- src/link.zig | 2 + src/link/Coff.zig | 12 ++--- src/link/Goff.zig | 9 ++-- src/link/MachO.zig | 6 +-- src/link/MachO/ZigObject.zig | 12 ++--- src/link/Plan9.zig | 12 ++--- src/link/Queue.zig | 3 +- src/link/Xcoff.zig | 9 ++-- 25 files changed, 402 insertions(+), 251 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 64ec1ab0a8..e967935539 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4550,8 +4550,6 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { air.deinit(gpa); return; } - const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); - defer pt.deactivate(); const shared_mir = try gpa.create(link.ZcuTask.LinkFunc.SharedMir); shared_mir.* = .{ .status = .init(.pending), @@ -4567,7 +4565,11 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { } }); } else { const emit_needs_air = !zcu.backendSupportsFeature(.separate_thread); - pt.runCodegen(func.func, &air, shared_mir); + { + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); + defer pt.deactivate(); + pt.runCodegen(func.func, &air, shared_mir); + } assert(shared_mir.status.load(.monotonic) != .pending); comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ .func = func.func, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 92f1adbf2a..6475649a68 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -4376,26 +4376,40 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep /// other code. This function is currently run either on the main thread, or on a separate /// codegen thread, depending on whether the backend supports `Zcu.Feature.separate_thread`. pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void { + const zcu = pt.zcu; if (runCodegenInner(pt, func_index, air)) |mir| { out.value = mir; out.status.store(.ready, .release); } else |err| switch (err) { error.OutOfMemory => { - pt.zcu.comp.setAllocFailure(); + zcu.comp.setAllocFailure(); out.status.store(.failed, .monotonic); }, error.CodegenFail => { - pt.zcu.assertCodegenFailed(pt.zcu.funcInfo(func_index).owner_nav); + zcu.assertCodegenFailed(zcu.funcInfo(func_index).owner_nav); out.status.store(.failed, .monotonic); }, error.NoLinkFile => { - assert(pt.zcu.comp.bin_file == null); + assert(zcu.comp.bin_file == null); + out.status.store(.failed, .monotonic); + }, + error.BackendDoesNotProduceMir => { + const backend = target_util.zigBackend(zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm); + switch (backend) { + else => unreachable, // assertion failure + .stage2_llvm => {}, + } out.status.store(.failed, .monotonic); }, } - pt.zcu.comp.link_task_queue.mirReady(pt.zcu.comp, out); + zcu.comp.link_task_queue.mirReady(zcu.comp, out); } -fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{ OutOfMemory, CodegenFail, NoLinkFile }!codegen.AnyMir { +fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{ + OutOfMemory, + CodegenFail, + NoLinkFile, + BackendDoesNotProduceMir, +}!codegen.AnyMir { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; @@ -4441,7 +4455,9 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e // "emit" step because LLVM does not support incremental linking. Our linker (LLD or self-hosted) // will just see the ZCU object file which LLVM ultimately emits. if (zcu.llvm_object) |llvm_object| { - return llvm_object.updateFunc(pt, func_index, air, &liveness); + assert(pt.tid == .main); // LLVM has a lot of shared state + try llvm_object.updateFunc(pt, func_index, air, &liveness); + return error.BackendDoesNotProduceMir; } const lf = comp.bin_file orelse return error.NoLinkFile; diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 00cceb0c67..0c29fd96e2 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -49,7 +49,6 @@ pt: Zcu.PerThread, air: Air, liveness: Air.Liveness, bin_file: *link.File, -debug_output: link.File.DebugInfoOutput, target: *const std.Target, func_index: InternPool.Index, owner_nav: InternPool.Nav.Index, @@ -185,6 +184,9 @@ const DbgInfoReloc = struct { } fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (function.debug_output) { .dwarf => |dw| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -213,6 +215,9 @@ const DbgInfoReloc = struct { } fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (function.debug_output) { .dwarf => |dwarf| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -326,11 +331,9 @@ pub fn generate( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, - code: *std.ArrayListUnmanaged(u8), - debug_output: link.File.DebugInfoOutput, -) CodeGenError!void { + air: *const Air, + liveness: *const Air.Liveness, +) CodeGenError!Mir { const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); @@ -349,9 +352,8 @@ pub fn generate( var function: Self = .{ .gpa = gpa, .pt = pt, - .air = air, - .liveness = liveness, - .debug_output = debug_output, + .air = air.*, + .liveness = liveness.*, .target = target, .bin_file = lf, .func_index = func_index, @@ -395,29 +397,13 @@ pub fn generate( var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), - .extra = try function.mir_extra.toOwnedSlice(gpa), - }; - defer mir.deinit(gpa); - - var emit: Emit = .{ - .mir = mir, - .bin_file = lf, - .debug_output = debug_output, - .target = target, - .src_loc = src_loc, - .code = code, - .prev_di_pc = 0, - .prev_di_line = func.lbrace_line, - .prev_di_column = func.lbrace_column, - .stack_size = function.max_end_stack, + .extra = &.{}, // fallible, so assign after errdefer + .max_end_stack = function.max_end_stack, .saved_regs_stack_space = function.saved_regs_stack_space, }; - defer emit.deinit(); - - emit.emitMir() catch |err| switch (err) { - error.EmitFail => return function.failMsg(emit.err_msg.?), - else => |e| return e, - }; + errdefer mir.deinit(gpa); + mir.extra = try function.mir_extra.toOwnedSlice(gpa); + return mir; } fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { diff --git a/src/arch/aarch64/Mir.zig b/src/arch/aarch64/Mir.zig index edf05f625e..34fcc64c7e 100644 --- a/src/arch/aarch64/Mir.zig +++ b/src/arch/aarch64/Mir.zig @@ -13,6 +13,14 @@ const assert = std.debug.assert; const bits = @import("bits.zig"); const Register = bits.Register; +const InternPool = @import("../../InternPool.zig"); +const Emit = @import("Emit.zig"); +const codegen = @import("../../codegen.zig"); +const link = @import("../../link.zig"); +const Zcu = @import("../../Zcu.zig"); + +max_end_stack: u32, +saved_regs_stack_space: u32, instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. @@ -498,6 +506,41 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } +pub fn emit( + mir: Mir, + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + code: *std.ArrayListUnmanaged(u8), + debug_output: link.File.DebugInfoOutput, + air: *const @import("../../Air.zig"), +) codegen.CodeGenError!void { + _ = air; // using this would be a bug + const zcu = pt.zcu; + const func = zcu.funcInfo(func_index); + const nav = func.owner_nav; + const mod = zcu.navFileScope(nav).mod.?; + var e: Emit = .{ + .mir = mir, + .bin_file = lf, + .debug_output = debug_output, + .target = &mod.resolved_target.result, + .src_loc = src_loc, + .code = code, + .prev_di_pc = 0, + .prev_di_line = func.lbrace_line, + .prev_di_column = func.lbrace_column, + .stack_size = mir.max_end_stack, + .saved_regs_stack_space = mir.saved_regs_stack_space, + }; + defer e.deinit(); + e.emitMir() catch |err| switch (err) { + error.EmitFail => return zcu.codegenFailMsg(nav, e.err_msg.?), + else => |e1| return e1, + }; +} + /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 421ba7d753..3868011557 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -50,7 +50,6 @@ pt: Zcu.PerThread, air: Air, liveness: Air.Liveness, bin_file: *link.File, -debug_output: link.File.DebugInfoOutput, target: *const std.Target, func_index: InternPool.Index, err_msg: ?*ErrorMsg, @@ -264,6 +263,9 @@ const DbgInfoReloc = struct { } fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (function.debug_output) { .dwarf => |dw| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -292,6 +294,9 @@ const DbgInfoReloc = struct { } fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void { + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (function.debug_output) { .dwarf => |dw| { const loc: link.File.Dwarf.Loc = switch (reloc.mcv) { @@ -335,11 +340,9 @@ pub fn generate( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, - code: *std.ArrayListUnmanaged(u8), - debug_output: link.File.DebugInfoOutput, -) CodeGenError!void { + air: *const Air, + liveness: *const Air.Liveness, +) CodeGenError!Mir { const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); @@ -358,11 +361,10 @@ pub fn generate( var function: Self = .{ .gpa = gpa, .pt = pt, - .air = air, - .liveness = liveness, + .air = air.*, + .liveness = liveness.*, .target = target, .bin_file = lf, - .debug_output = debug_output, .func_index = func_index, .err_msg = null, .args = undefined, // populated after `resolveCallingConventionValues` @@ -402,31 +404,15 @@ pub fn generate( return function.fail("failed to generate debug info: {s}", .{@errorName(err)}); } - var mir = Mir{ + var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), - .extra = try function.mir_extra.toOwnedSlice(gpa), - }; - defer mir.deinit(gpa); - - var emit = Emit{ - .mir = mir, - .bin_file = lf, - .debug_output = debug_output, - .target = target, - .src_loc = src_loc, - .code = code, - .prev_di_pc = 0, - .prev_di_line = func.lbrace_line, - .prev_di_column = func.lbrace_column, - .stack_size = function.max_end_stack, + .extra = &.{}, // fallible, so assign after errdefer + .max_end_stack = function.max_end_stack, .saved_regs_stack_space = function.saved_regs_stack_space, }; - defer emit.deinit(); - - emit.emitMir() catch |err| switch (err) { - error.EmitFail => return function.failMsg(emit.err_msg.?), - else => |e| return e, - }; + errdefer mir.deinit(gpa); + mir.extra = try function.mir_extra.toOwnedSlice(gpa); + return mir; } fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { diff --git a/src/arch/arm/Mir.zig b/src/arch/arm/Mir.zig index 5e651b7939..0366663eae 100644 --- a/src/arch/arm/Mir.zig +++ b/src/arch/arm/Mir.zig @@ -13,6 +13,14 @@ const assert = std.debug.assert; const bits = @import("bits.zig"); const Register = bits.Register; +const InternPool = @import("../../InternPool.zig"); +const Emit = @import("Emit.zig"); +const codegen = @import("../../codegen.zig"); +const link = @import("../../link.zig"); +const Zcu = @import("../../Zcu.zig"); + +max_end_stack: u32, +saved_regs_stack_space: u32, instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. @@ -278,6 +286,41 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } +pub fn emit( + mir: Mir, + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + code: *std.ArrayListUnmanaged(u8), + debug_output: link.File.DebugInfoOutput, + air: *const @import("../../Air.zig"), +) codegen.CodeGenError!void { + _ = air; // using this would be a bug + const zcu = pt.zcu; + const func = zcu.funcInfo(func_index); + const nav = func.owner_nav; + const mod = zcu.navFileScope(nav).mod.?; + var e: Emit = .{ + .mir = mir, + .bin_file = lf, + .debug_output = debug_output, + .target = &mod.resolved_target.result, + .src_loc = src_loc, + .code = code, + .prev_di_pc = 0, + .prev_di_line = func.lbrace_line, + .prev_di_column = func.lbrace_column, + .stack_size = mir.max_end_stack, + .saved_regs_stack_space = mir.saved_regs_stack_space, + }; + defer e.deinit(); + e.emitMir() catch |err| switch (err) { + error.EmitFail => return zcu.codegenFailMsg(nav, e.err_msg.?), + else => |e1| return e1, + }; +} + /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { diff --git a/src/arch/powerpc/CodeGen.zig b/src/arch/powerpc/CodeGen.zig index 0cfee67ebd..4964fe19f4 100644 --- a/src/arch/powerpc/CodeGen.zig +++ b/src/arch/powerpc/CodeGen.zig @@ -19,19 +19,15 @@ pub fn generate( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, - code: *std.ArrayListUnmanaged(u8), - debug_output: link.File.DebugInfoOutput, -) codegen.CodeGenError!void { + air: *const Air, + liveness: *const Air.Liveness, +) codegen.CodeGenError!noreturn { _ = bin_file; _ = pt; _ = src_loc; _ = func_index; _ = air; _ = liveness; - _ = code; - _ = debug_output; unreachable; } diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 9fc51bd2d3..9b5e0ed69b 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -68,7 +68,6 @@ gpa: Allocator, mod: *Package.Module, target: *const std.Target, -debug_output: link.File.DebugInfoOutput, args: []MCValue, ret_mcv: InstTracking, fn_type: Type, @@ -746,13 +745,10 @@ pub fn generate( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, - code: *std.ArrayListUnmanaged(u8), - debug_output: link.File.DebugInfoOutput, -) CodeGenError!void { + air: *const Air, + liveness: *const Air.Liveness, +) CodeGenError!Mir { const zcu = pt.zcu; - const comp = zcu.comp; const gpa = zcu.gpa; const ip = &zcu.intern_pool; const func = zcu.funcInfo(func_index); @@ -769,13 +765,12 @@ pub fn generate( var function: Func = .{ .gpa = gpa, - .air = air, + .air = air.*, .pt = pt, .mod = mod, .bin_file = bin_file, - .liveness = liveness, + .liveness = liveness.*, .target = &mod.resolved_target.result, - .debug_output = debug_output, .owner = .{ .nav_index = func.owner_nav }, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` @@ -855,33 +850,8 @@ pub fn generate( .instructions = function.mir_instructions.toOwnedSlice(), .frame_locs = function.frame_locs.toOwnedSlice(), }; - defer mir.deinit(gpa); - - var emit: Emit = .{ - .lower = .{ - .pt = pt, - .allocator = gpa, - .mir = mir, - .cc = fn_info.cc, - .src_loc = src_loc, - .output_mode = comp.config.output_mode, - .link_mode = comp.config.link_mode, - .pic = mod.pic, - }, - .bin_file = bin_file, - .debug_output = debug_output, - .code = code, - .prev_di_pc = 0, - .prev_di_line = func.lbrace_line, - .prev_di_column = func.lbrace_column, - }; - defer emit.deinit(); - - emit.emitMir() catch |err| switch (err) { - error.LowerFail, error.EmitFail => return function.failMsg(emit.lower.err_msg.?), - error.InvalidInstruction => |e| return function.fail("emit MIR failed: {s} (Zig compiler bug)", .{@errorName(e)}), - else => |e| return e, - }; + errdefer mir.deinit(gpa); + return mir; } pub fn generateLazy( @@ -904,7 +874,6 @@ pub fn generateLazy( .bin_file = bin_file, .liveness = undefined, .target = &mod.resolved_target.result, - .debug_output = debug_output, .owner = .{ .lazy_sym = lazy_sym }, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` @@ -4760,6 +4729,9 @@ fn genArgDbgInfo(func: *const Func, inst: Air.Inst.Index, mcv: MCValue) InnerErr const ty = arg.ty.toType(); if (arg.name == .none) return; + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (func.debug_output) { .dwarf => |dw| switch (mcv) { .register => |reg| dw.genLocalDebugInfo( @@ -5273,6 +5245,9 @@ fn genVarDbgInfo( mcv: MCValue, name: []const u8, ) !void { + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (func.debug_output) { .dwarf => |dwarf| { const loc: link.File.Dwarf.Loc = switch (mcv) { diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig index 2ae62fd9b2..eef3fe7511 100644 --- a/src/arch/riscv64/Mir.zig +++ b/src/arch/riscv64/Mir.zig @@ -109,6 +109,50 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } +pub fn emit( + mir: Mir, + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + code: *std.ArrayListUnmanaged(u8), + debug_output: link.File.DebugInfoOutput, + air: *const @import("../../Air.zig"), +) codegen.CodeGenError!void { + _ = air; // using this would be a bug + const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; + const func = zcu.funcInfo(func_index); + const fn_info = zcu.typeToFunc(.fromInterned(func.ty)).?; + const nav = func.owner_nav; + const mod = zcu.navFileScope(nav).mod.?; + var e: Emit = .{ + .lower = .{ + .pt = pt, + .allocator = gpa, + .mir = mir, + .cc = fn_info.cc, + .src_loc = src_loc, + .output_mode = comp.config.output_mode, + .link_mode = comp.config.link_mode, + .pic = mod.pic, + }, + .bin_file = lf, + .debug_output = debug_output, + .code = code, + .prev_di_pc = 0, + .prev_di_line = func.lbrace_line, + .prev_di_column = func.lbrace_column, + }; + defer e.deinit(); + e.emitMir() catch |err| switch (err) { + error.LowerFail, error.EmitFail => return zcu.codegenFailMsg(nav, e.lower.err_msg.?), + error.InvalidInstruction => return zcu.codegenFail(nav, "emit MIR failed: {s} (Zig compiler bug)", .{@errorName(err)}), + else => |err1| return err1, + }; +} + pub const FrameLoc = struct { base: Register, disp: i32, @@ -202,3 +246,9 @@ const FrameIndex = bits.FrameIndex; const FrameAddr = @import("CodeGen.zig").FrameAddr; const IntegerBitSet = std.bit_set.IntegerBitSet; const Mnemonic = @import("mnem.zig").Mnemonic; + +const InternPool = @import("../../InternPool.zig"); +const Emit = @import("Emit.zig"); +const codegen = @import("../../codegen.zig"); +const link = @import("../../link.zig"); +const Zcu = @import("../../Zcu.zig"); diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index ad9884dcdb..180aaedd3c 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -57,8 +57,6 @@ liveness: Air.Liveness, bin_file: *link.File, target: *const std.Target, func_index: InternPool.Index, -code: *std.ArrayListUnmanaged(u8), -debug_output: link.File.DebugInfoOutput, err_msg: ?*ErrorMsg, args: []MCValue, ret_mcv: MCValue, @@ -268,11 +266,9 @@ pub fn generate( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, - code: *std.ArrayListUnmanaged(u8), - debug_output: link.File.DebugInfoOutput, -) CodeGenError!void { + air: *const Air, + liveness: *const Air.Liveness, +) CodeGenError!Mir { const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); @@ -291,13 +287,11 @@ pub fn generate( var function: Self = .{ .gpa = gpa, .pt = pt, - .air = air, - .liveness = liveness, + .air = air.*, + .liveness = liveness.*, .target = target, .bin_file = lf, .func_index = func_index, - .code = code, - .debug_output = debug_output, .err_msg = null, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` @@ -330,29 +324,13 @@ pub fn generate( else => |e| return e, }; - var mir = Mir{ + var mir: Mir = .{ .instructions = function.mir_instructions.toOwnedSlice(), - .extra = try function.mir_extra.toOwnedSlice(gpa), - }; - defer mir.deinit(gpa); - - var emit: Emit = .{ - .mir = mir, - .bin_file = lf, - .debug_output = debug_output, - .target = target, - .src_loc = src_loc, - .code = code, - .prev_di_pc = 0, - .prev_di_line = func.lbrace_line, - .prev_di_column = func.lbrace_column, - }; - defer emit.deinit(); - - emit.emitMir() catch |err| switch (err) { - error.EmitFail => return function.failMsg(emit.err_msg.?), - else => |e| return e, + .extra = &.{}, // fallible, so populated after errdefer }; + errdefer mir.deinit(gpa); + mir.extra = try function.mir_extra.toOwnedSlice(gpa); + return mir; } fn gen(self: *Self) !void { @@ -3566,6 +3544,9 @@ fn genArgDbgInfo(self: Self, inst: Air.Inst.Index, mcv: MCValue) !void { const ty = arg.ty.toType(); if (arg.name == .none) return; + // TODO: Add a pseudo-instruction or something to defer this work until Emit. + // We aren't allowed to interact with linker state here. + if (true) return; switch (self.debug_output) { .dwarf => |dw| switch (mcv) { .register => |reg| try dw.genLocalDebugInfo( diff --git a/src/arch/sparc64/Mir.zig b/src/arch/sparc64/Mir.zig index e9086db7a5..26c5c3267b 100644 --- a/src/arch/sparc64/Mir.zig +++ b/src/arch/sparc64/Mir.zig @@ -12,7 +12,11 @@ const assert = std.debug.assert; const Mir = @This(); const bits = @import("bits.zig"); -const Air = @import("../../Air.zig"); +const InternPool = @import("../../InternPool.zig"); +const Emit = @import("Emit.zig"); +const codegen = @import("../../codegen.zig"); +const link = @import("../../link.zig"); +const Zcu = @import("../../Zcu.zig"); const Instruction = bits.Instruction; const ASI = bits.Instruction.ASI; @@ -370,6 +374,39 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } +pub fn emit( + mir: Mir, + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + code: *std.ArrayListUnmanaged(u8), + debug_output: link.File.DebugInfoOutput, + air: *const @import("../../Air.zig"), +) codegen.CodeGenError!void { + _ = air; // using this would be a bug + const zcu = pt.zcu; + const func = zcu.funcInfo(func_index); + const nav = func.owner_nav; + const mod = zcu.navFileScope(nav).mod.?; + var e: Emit = .{ + .mir = mir, + .bin_file = lf, + .debug_output = debug_output, + .target = &mod.resolved_target.result, + .src_loc = src_loc, + .code = code, + .prev_di_pc = 0, + .prev_di_line = func.lbrace_line, + .prev_di_column = func.lbrace_column, + }; + defer e.deinit(); + e.emitMir() catch |err| switch (err) { + error.EmitFail => return zcu.codegenFailMsg(nav, e.err_msg.?), + else => |err1| return err1, + }; +} + /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index b38492d500..1d95c8db77 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -125,7 +125,6 @@ pt: Zcu.PerThread, air: Air, liveness: Air.Liveness, bin_file: *link.File, -debug_output: link.File.DebugInfoOutput, target: *const std.Target, owner: Owner, inline_func: InternPool.Index, @@ -972,13 +971,10 @@ pub fn generate( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, - code: *std.ArrayListUnmanaged(u8), - debug_output: link.File.DebugInfoOutput, -) codegen.CodeGenError!void { + air: *const Air, + liveness: *const Air.Liveness, +) codegen.CodeGenError!Mir { const zcu = pt.zcu; - const comp = zcu.comp; const gpa = zcu.gpa; const ip = &zcu.intern_pool; const func = zcu.funcInfo(func_index); @@ -988,12 +984,11 @@ pub fn generate( var function: CodeGen = .{ .gpa = gpa, .pt = pt, - .air = air, - .liveness = liveness, + .air = air.*, + .liveness = liveness.*, .target = &mod.resolved_target.result, .mod = mod, .bin_file = bin_file, - .debug_output = debug_output, .owner = .{ .nav_index = func.owner_nav }, .inline_func = func_index, .arg_index = undefined, @@ -1090,7 +1085,7 @@ pub fn generate( }; // Drop them off at the rbrace. - if (debug_output != .none) _ = try function.addInst(.{ + if (!mod.strip) _ = try function.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_line_line_column, .data = .{ .line_column = .{ @@ -1100,49 +1095,17 @@ pub fn generate( }); var mir: Mir = .{ - .instructions = function.mir_instructions.toOwnedSlice(), - .extra = try function.mir_extra.toOwnedSlice(gpa), - .table = try function.mir_table.toOwnedSlice(gpa), - .frame_locs = function.frame_locs.toOwnedSlice(), - }; - defer mir.deinit(gpa); - - var emit: Emit = .{ - .air = function.air, - .lower = .{ - .bin_file = bin_file, - .target = function.target, - .allocator = gpa, - .mir = mir, - .cc = fn_info.cc, - .src_loc = src_loc, - .output_mode = comp.config.output_mode, - .link_mode = comp.config.link_mode, - .pic = mod.pic, - }, - .atom_index = function.owner.getSymbolIndex(&function) catch |err| switch (err) { - error.CodegenFail => return error.CodegenFail, - else => |e| return e, - }, - .debug_output = debug_output, - .code = code, - .prev_di_loc = .{ - .line = func.lbrace_line, - .column = func.lbrace_column, - .is_stmt = switch (debug_output) { - .dwarf => |dwarf| dwarf.dwarf.debug_line.header.default_is_stmt, - .plan9 => undefined, - .none => undefined, - }, - }, - .prev_di_pc = 0, - }; - emit.emitMir() catch |err| switch (err) { - error.LowerFail, error.EmitFail => return function.failMsg(emit.lower.err_msg.?), - - error.InvalidInstruction, error.CannotEncode => |e| return function.fail("emit MIR failed: {s} (Zig compiler bug)", .{@errorName(e)}), - else => |e| return function.fail("emit MIR failed: {s}", .{@errorName(e)}), + .instructions = .empty, + .extra = &.{}, + .table = &.{}, + .frame_locs = .empty, }; + errdefer mir.deinit(gpa); + mir.instructions = function.mir_instructions.toOwnedSlice(); + mir.extra = try function.mir_extra.toOwnedSlice(gpa); + mir.table = try function.mir_table.toOwnedSlice(gpa); + mir.frame_locs = function.frame_locs.toOwnedSlice(); + return mir; } pub fn generateLazy( @@ -1165,7 +1128,6 @@ pub fn generateLazy( .target = &mod.resolved_target.result, .mod = mod, .bin_file = bin_file, - .debug_output = debug_output, .owner = .{ .lazy_sym = lazy_sym }, .inline_func = undefined, .arg_index = undefined, @@ -2339,7 +2301,7 @@ fn gen(self: *CodeGen) InnerError!void { else => |cc| return self.fail("{s} does not support var args", .{@tagName(cc)}), }; - if (self.debug_output != .none) try self.asmPseudo(.pseudo_dbg_prologue_end_none); + if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_prologue_end_none); try self.genBody(self.air.getMainBody()); @@ -2356,7 +2318,7 @@ fn gen(self: *CodeGen) InnerError!void { } for (self.epilogue_relocs.items) |epilogue_reloc| self.performReloc(epilogue_reloc); - if (self.debug_output != .none) try self.asmPseudo(.pseudo_dbg_epilogue_begin_none); + if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_epilogue_begin_none); const backpatch_stack_dealloc = try self.asmPlaceholder(); const backpatch_pop_callee_preserved_regs = try self.asmPlaceholder(); try self.asmRegister(.{ ._, .pop }, .rbp); @@ -2475,9 +2437,9 @@ fn gen(self: *CodeGen) InnerError!void { }); } } else { - if (self.debug_output != .none) try self.asmPseudo(.pseudo_dbg_prologue_end_none); + if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_prologue_end_none); try self.genBody(self.air.getMainBody()); - if (self.debug_output != .none) try self.asmPseudo(.pseudo_dbg_epilogue_begin_none); + if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_epilogue_begin_none); } } @@ -2498,9 +2460,9 @@ fn checkInvariantsAfterAirInst(self: *CodeGen) void { } fn genBodyBlock(self: *CodeGen, body: []const Air.Inst.Index) InnerError!void { - if (self.debug_output != .none) try self.asmPseudo(.pseudo_dbg_enter_block_none); + if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_enter_block_none); try self.genBody(body); - if (self.debug_output != .none) try self.asmPseudo(.pseudo_dbg_leave_block_none); + if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_leave_block_none); } fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { @@ -2544,7 +2506,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .shuffle_one, .shuffle_two => @panic("x86_64 TODO: shuffle_one/shuffle_two"), // zig fmt: on - .arg => if (cg.debug_output != .none) { + .arg => if (!cg.mod.strip) { // skip zero-bit arguments as they don't have a corresponding arg instruction var arg_index = cg.arg_index; while (cg.args[arg_index] == .none) arg_index += 1; @@ -64179,9 +64141,9 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .block => { const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; const block = cg.air.extraData(Air.Block, ty_pl.payload); - if (cg.debug_output != .none) try cg.asmPseudo(.pseudo_dbg_enter_block_none); + if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_enter_block_none); try cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[block.end..][0..block.data.body_len])); - if (cg.debug_output != .none) try cg.asmPseudo(.pseudo_dbg_leave_block_none); + if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_leave_block_none); }, .loop => if (use_old) try cg.airLoop(inst) else { const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; @@ -85191,7 +85153,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .switch_dispatch => try cg.airSwitchDispatch(inst), .@"try", .try_cold => try cg.airTry(inst), .try_ptr, .try_ptr_cold => try cg.airTryPtr(inst), - .dbg_stmt => if (cg.debug_output != .none) { + .dbg_stmt => if (!cg.mod.strip) { const dbg_stmt = air_datas[@intFromEnum(inst)].dbg_stmt; _ = try cg.addInst(.{ .tag = .pseudo, @@ -85202,7 +85164,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { } }, }); }, - .dbg_empty_stmt => if (cg.debug_output != .none) { + .dbg_empty_stmt => if (!cg.mod.strip) { if (cg.mir_instructions.len > 0) { const prev_mir_op = &cg.mir_instructions.items(.ops)[cg.mir_instructions.len - 1]; if (prev_mir_op.* == .pseudo_dbg_line_line_column) @@ -85216,13 +85178,13 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { const old_inline_func = cg.inline_func; defer cg.inline_func = old_inline_func; cg.inline_func = dbg_inline_block.data.func; - if (cg.debug_output != .none) _ = try cg.addInst(.{ + if (!cg.mod.strip) _ = try cg.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_enter_inline_func, .data = .{ .func = dbg_inline_block.data.func }, }); try cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[dbg_inline_block.end..][0..dbg_inline_block.data.body_len])); - if (cg.debug_output != .none) _ = try cg.addInst(.{ + if (!cg.mod.strip) _ = try cg.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_leave_inline_func, .data = .{ .func = old_inline_func }, @@ -85231,7 +85193,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline, - => if (use_old) try cg.airDbgVar(inst) else if (cg.debug_output != .none) { + => if (use_old) try cg.airDbgVar(inst) else if (!cg.mod.strip) { const pl_op = air_datas[@intFromEnum(inst)].pl_op; var ops = try cg.tempsFromOperands(inst, .{pl_op.operand}); var mcv = ops[0].tracking(cg).short; @@ -173366,7 +173328,7 @@ fn airArg(self: *CodeGen, inst: Air.Inst.Index) !void { while (self.args[arg_index] == .none) arg_index += 1; self.arg_index = arg_index + 1; - const result: MCValue = if (self.debug_output == .none and self.liveness.isUnused(inst)) .unreach else result: { + const result: MCValue = if (self.mod.strip and self.liveness.isUnused(inst)) .unreach else result: { const arg_ty = self.typeOfIndex(inst); const src_mcv = self.args[arg_index]; switch (src_mcv) { @@ -173468,7 +173430,7 @@ fn airArg(self: *CodeGen, inst: Air.Inst.Index) !void { } fn airDbgVarArgs(self: *CodeGen) !void { - if (self.debug_output == .none) return; + if (self.mod.strip) return; if (!self.pt.zcu.typeToFunc(self.fn_type).?.is_var_args) return; try self.asmPseudo(.pseudo_dbg_var_args_none); } @@ -173478,7 +173440,7 @@ fn genLocalDebugInfo( inst: Air.Inst.Index, mcv: MCValue, ) !void { - if (self.debug_output == .none) return; + if (self.mod.strip) return; switch (self.air.instructions.items(.tag)[@intFromEnum(inst)]) { else => unreachable, .arg, .dbg_arg_inline, .dbg_var_val => |tag| { diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 8d202e6bae..14468677af 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -1929,6 +1929,67 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } +pub fn emit( + mir: Mir, + lf: *link.File, + pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, + func_index: InternPool.Index, + code: *std.ArrayListUnmanaged(u8), + debug_output: link.File.DebugInfoOutput, + /// TODO: remove dependency on this argument. This blocks enabling `Zcu.Feature.separate_thread`. + air: *const Air, +) codegen.CodeGenError!void { + const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; + const func = zcu.funcInfo(func_index); + const fn_info = zcu.typeToFunc(.fromInterned(func.ty)).?; + const nav = func.owner_nav; + const mod = zcu.navFileScope(nav).mod.?; + var e: Emit = .{ + .air = air.*, + .lower = .{ + .bin_file = lf, + .target = &mod.resolved_target.result, + .allocator = gpa, + .mir = mir, + .cc = fn_info.cc, + .src_loc = src_loc, + .output_mode = comp.config.output_mode, + .link_mode = comp.config.link_mode, + .pic = mod.pic, + }, + .atom_index = sym: { + if (lf.cast(.elf)) |ef| break :sym try ef.zigObjectPtr().?.getOrCreateMetadataForNav(zcu, nav); + if (lf.cast(.macho)) |mf| break :sym try mf.getZigObject().?.getOrCreateMetadataForNav(mf, nav); + if (lf.cast(.coff)) |cf| { + const atom = try cf.getOrCreateAtomForNav(nav); + break :sym cf.getAtom(atom).getSymbolIndex().?; + } + if (lf.cast(.plan9)) |p9f| break :sym try p9f.seeNav(pt, nav); + unreachable; + }, + .debug_output = debug_output, + .code = code, + .prev_di_loc = .{ + .line = func.lbrace_line, + .column = func.lbrace_column, + .is_stmt = switch (debug_output) { + .dwarf => |dwarf| dwarf.dwarf.debug_line.header.default_is_stmt, + .plan9 => undefined, + .none => undefined, + }, + }, + .prev_di_pc = 0, + }; + e.emitMir() catch |err| switch (err) { + error.LowerFail, error.EmitFail => return zcu.codegenFailMsg(nav, e.lower.err_msg.?), + error.InvalidInstruction, error.CannotEncode => return zcu.codegenFail(nav, "emit MIR failed: {s} (Zig compiler bug)", .{@errorName(err)}), + else => return zcu.codegenFail(nav, "emit MIR failed: {s}", .{@errorName(err)}), + }; +} + pub fn extraData(mir: Mir, comptime T: type, index: u32) struct { data: T, end: u32 } { const fields = std.meta.fields(T); var i: u32 = index; @@ -1987,3 +2048,7 @@ const IntegerBitSet = std.bit_set.IntegerBitSet; const InternPool = @import("../../InternPool.zig"); const Mir = @This(); const Register = bits.Register; +const Emit = @import("Emit.zig"); +const codegen = @import("../../codegen.zig"); +const link = @import("../../link.zig"); +const Zcu = @import("../../Zcu.zig"); diff --git a/src/codegen.zig b/src/codegen.zig index 2c2524257c..ea57aaf89c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -182,7 +182,7 @@ pub fn emitFunction( /// in the pipeline. Any information needed to call emit must be stored in MIR. /// This is `undefined` if the backend supports the `separate_thread` feature. air: *const Air, -) Allocator.Error!void { +) CodeGenError!void { const zcu = pt.zcu; const func = zcu.funcInfo(func_index); const target = zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result; diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index 98d4a42f91..d90ba974fc 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -985,7 +985,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { assert(comp.freebsd_so_files == null); comp.freebsd_so_files = so_files; - var task_buffer: [libs.len]link.Task = undefined; + var task_buffer: [libs.len]link.PrelinkTask = undefined; var task_buffer_i: usize = 0; { diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index c1146d933d..ed5eae377f 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -1148,7 +1148,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { assert(comp.glibc_so_files == null); comp.glibc_so_files = so_files; - var task_buffer: [libs.len]link.Task = undefined; + var task_buffer: [libs.len]link.PrelinkTask = undefined; var task_buffer_i: usize = 0; { diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index aab75cce49..7121c308f5 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -650,7 +650,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { assert(comp.netbsd_so_files == null); comp.netbsd_so_files = so_files; - var task_buffer: [libs.len]link.Task = undefined; + var task_buffer: [libs.len]link.PrelinkTask = undefined; var task_buffer_i: usize = 0; { diff --git a/src/link.zig b/src/link.zig index 31fd0a4a4e..838654775d 100644 --- a/src/link.zig +++ b/src/link.zig @@ -759,6 +759,8 @@ pub const File = struct { switch (base.tag) { .lld => unreachable, inline else => |tag| { + if (tag == .wasm) @panic("MLUGG TODO"); + if (tag == .spirv) @panic("MLUGG TODO"); dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, mir, maybe_undef_air); }, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 9a040754ef..bb8faf583d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1057,8 +1057,10 @@ pub fn updateFunc( coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + /// This may be `undefined`; only pass it to `emitFunction`. + /// This parameter will eventually be removed. + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -1079,15 +1081,15 @@ pub fn updateFunc( var code_buffer: std.ArrayListUnmanaged(u8) = .empty; defer code_buffer.deinit(gpa); - try codegen.generateFunction( + try codegen.emitFunction( &coff.base, pt, zcu.navSrcLoc(nav_index), func_index, - air, - liveness, + mir, &code_buffer, .none, + maybe_undef_air, ); try coff.updateNavCode(pt, nav_index, code_buffer.items, .FUNCTION); diff --git a/src/link/Goff.zig b/src/link/Goff.zig index 28da184495..d0c2b8e80b 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -13,6 +13,7 @@ const Path = std.Build.Cache.Path; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const Compilation = @import("../Compilation.zig"); +const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); @@ -72,14 +73,14 @@ pub fn updateFunc( self: *Goff, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { _ = self; _ = pt; _ = func_index; - _ = air; - _ = liveness; + _ = mir; + _ = maybe_undef_air; unreachable; // we always use llvm } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 2c30b34215..8fd85df0a3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3051,13 +3051,13 @@ pub fn updateFunc( self: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - return self.getZigObject().?.updateFunc(self, pt, func_index, air, liveness); + return self.getZigObject().?.updateFunc(self, pt, func_index, mir, maybe_undef_air); } pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 13ebb40cf9..f378a9c410 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -777,8 +777,10 @@ pub fn updateFunc( macho_file: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + /// This may be `undefined`; only pass it to `emitFunction`. + /// This parameter will eventually be removed. + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { const tracy = trace(@src()); defer tracy.end(); @@ -796,15 +798,15 @@ pub fn updateFunc( var debug_wip_nav = if (self.dwarf) |*dwarf| try dwarf.initWipNav(pt, func.owner_nav, sym_index) else null; defer if (debug_wip_nav) |*wip_nav| wip_nav.deinit(); - try codegen.generateFunction( + try codegen.emitFunction( &macho_file.base, pt, zcu.navSrcLoc(func.owner_nav), func_index, - air, - liveness, + mir, &code_buffer, if (debug_wip_nav) |*wip_nav| .{ .dwarf = wip_nav } else .none, + maybe_undef_air, ); const code = code_buffer.items; diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index c487169b3f..0d0699f0f0 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -386,8 +386,10 @@ pub fn updateFunc( self: *Plan9, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + /// This may be `undefined`; only pass it to `emitFunction`. + /// This parameter will eventually be removed. + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .plan9) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -412,15 +414,15 @@ pub fn updateFunc( }; defer dbg_info_output.dbg_line.deinit(); - try codegen.generateFunction( + try codegen.emitFunction( &self.base, pt, zcu.navSrcLoc(func.owner_nav), func_index, - air, - liveness, + mir, &code_buffer, .{ .plan9 = &dbg_info_output }, + maybe_undef_air, ); const code = try code_buffer.toOwnedSlice(gpa); self.getAtomPtr(atom_idx).code = .{ diff --git a/src/link/Queue.zig b/src/link/Queue.zig index c73a0e9684..3436be5921 100644 --- a/src/link/Queue.zig +++ b/src/link/Queue.zig @@ -97,8 +97,7 @@ pub fn mirReady(q: *Queue, comp: *Compilation, mir: *ZcuTask.LinkFunc.SharedMir) q.mutex.lock(); defer q.mutex.unlock(); switch (q.state) { - .finished => unreachable, // there's definitely a task queued - .running => return, + .finished, .running => return, .wait_for_mir => |wait_for| if (wait_for != mir) return, } // We were waiting for `mir`, so we will restart the linker thread. diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index 7fe714ce6e..97ea300ed2 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -13,6 +13,7 @@ const Path = std.Build.Cache.Path; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const Compilation = @import("../Compilation.zig"); +const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); @@ -72,14 +73,14 @@ pub fn updateFunc( self: *Xcoff, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, + mir: *const codegen.AnyMir, + maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { _ = self; _ = pt; _ = func_index; - _ = air; - _ = liveness; + _ = mir; + _ = maybe_undef_air; unreachable; // we always use llvm } -- cgit v1.2.3 From c0df70706695a67089d4e691d3d3a0f77b90298f Mon Sep 17 00:00:00 2001 From: mlugg Date: Tue, 3 Jun 2025 16:25:16 +0100 Subject: wasm: get self-hosted compiling, and supporting `separate_thread` My original goal here was just to get the self-hosted Wasm backend compiling again after the pipeline change, but it turned out that from there it was pretty simple to entirely eliminate the shared state between `codegen.wasm` and `link.Wasm`. As such, this commit not only fixes the backend, but makes it the second backend (after CBE) to support the new 1:N:1 threading model. --- lib/std/multi_array_list.zig | 16 +++ src/Compilation.zig | 2 +- src/arch/wasm/CodeGen.zig | 301 +++++++++++++++---------------------------- src/arch/wasm/Emit.zig | 39 ++++-- src/arch/wasm/Mir.zig | 123 +++++++++++++++--- src/codegen.zig | 19 +-- src/link.zig | 3 +- src/link/Wasm.zig | 193 ++++++++++++++++++--------- src/link/Wasm/Flush.zig | 17 ++- src/target.zig | 2 +- 10 files changed, 404 insertions(+), 311 deletions(-) (limited to 'src/Compilation.zig') diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig index 341ca6931e..279a150799 100644 --- a/lib/std/multi_array_list.zig +++ b/lib/std/multi_array_list.zig @@ -135,6 +135,22 @@ pub fn MultiArrayList(comptime T: type) type { self.* = undefined; } + /// Returns a `Slice` representing a range of elements in `s`, analagous to `arr[off..len]`. + /// It is illegal to call `deinit` or `toMultiArrayList` on the returned `Slice`. + /// Asserts that `off + len <= s.len`. + pub fn subslice(s: Slice, off: usize, len: usize) Slice { + assert(off + len <= s.len); + var ptrs: [fields.len][*]u8 = undefined; + inline for (s.ptrs, &ptrs, fields) |in, *out, field| { + out.* = in + (off * @sizeOf(field.type)); + } + return .{ + .ptrs = ptrs, + .len = len, + .capacity = len, + }; + } + /// This function is used in the debugger pretty formatters in tools/ to fetch the /// child field order and entry type to facilitate fancy debug printing for this type. fn dbHelper(self: *Slice, child: *Elem, field: *Field, entry: *Entry) void { diff --git a/src/Compilation.zig b/src/Compilation.zig index e967935539..0342566e27 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3500,7 +3500,7 @@ pub fn saveState(comp: *Compilation) !void { // TODO handle the union safety field //addBuf(&bufs, mem.sliceAsBytes(wasm.mir_instructions.items(.data))); addBuf(&bufs, mem.sliceAsBytes(wasm.mir_extra.items)); - addBuf(&bufs, mem.sliceAsBytes(wasm.all_zcu_locals.items)); + addBuf(&bufs, mem.sliceAsBytes(wasm.mir_locals.items)); addBuf(&bufs, mem.sliceAsBytes(wasm.tag_name_bytes.items)); addBuf(&bufs, mem.sliceAsBytes(wasm.tag_name_offs.items)); diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 264b1e732d..2993923589 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const testing = std.testing; -const leb = std.leb; const mem = std.mem; const log = std.log.scoped(.codegen); @@ -18,12 +17,10 @@ const Compilation = @import("../../Compilation.zig"); const link = @import("../../link.zig"); const Air = @import("../../Air.zig"); const Mir = @import("Mir.zig"); -const Emit = @import("Emit.zig"); const abi = @import("abi.zig"); const Alignment = InternPool.Alignment; const errUnionPayloadOffset = codegen.errUnionPayloadOffset; const errUnionErrorOffset = codegen.errUnionErrorOffset; -const Wasm = link.File.Wasm; const target_util = @import("../../target.zig"); const libcFloatPrefix = target_util.libcFloatPrefix; @@ -78,17 +75,24 @@ simd_immediates: std.ArrayListUnmanaged([16]u8) = .empty, /// The Target we're emitting (used to call intInfo) target: *const std.Target, ptr_size: enum { wasm32, wasm64 }, -wasm: *link.File.Wasm, pt: Zcu.PerThread, /// List of MIR Instructions -mir_instructions: *std.MultiArrayList(Mir.Inst), +mir_instructions: std.MultiArrayList(Mir.Inst), /// Contains extra data for MIR -mir_extra: *std.ArrayListUnmanaged(u32), -start_mir_extra_off: u32, -start_locals_off: u32, +mir_extra: std.ArrayListUnmanaged(u32), /// List of all locals' types generated throughout this declaration /// used to emit locals count at start of 'code' section. -locals: *std.ArrayListUnmanaged(std.wasm.Valtype), +mir_locals: std.ArrayListUnmanaged(std.wasm.Valtype), +/// Set of all UAVs referenced by this function. Key is the UAV value, value is the alignment. +/// `.none` means naturally aligned. An explicit alignment is never less than the natural alignment. +mir_uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment), +/// Set of all functions whose address this function has taken and which therefore might be called +/// via a `call_indirect` function. +mir_indirect_function_set: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void), +/// Set of all function types used by this function. These must be interned by the linker. +mir_func_tys: std.AutoArrayHashMapUnmanaged(InternPool.Index, void), +/// The number of `error_name_table_ref` instructions emitted. +error_name_table_ref_count: u32, /// When a function is executing, we store the the current stack pointer's value within this local. /// This value is then used to restore the stack pointer to the original value at the return of the function. initial_stack_value: WValue = .none, @@ -219,7 +223,7 @@ const WValue = union(enum) { if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals. const index = local_value - reserved; - const valtype = gen.locals.items[gen.start_locals_off + index]; + const valtype = gen.mir_locals.items[index]; switch (valtype) { .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return, @@ -716,6 +720,12 @@ pub fn deinit(cg: *CodeGen) void { cg.free_locals_f32.deinit(gpa); cg.free_locals_f64.deinit(gpa); cg.free_locals_v128.deinit(gpa); + cg.mir_instructions.deinit(gpa); + cg.mir_extra.deinit(gpa); + cg.mir_locals.deinit(gpa); + cg.mir_uavs.deinit(gpa); + cg.mir_indirect_function_set.deinit(gpa); + cg.mir_func_tys.deinit(gpa); cg.* = undefined; } @@ -876,7 +886,7 @@ fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { } fn addExtended(cg: *CodeGen, opcode: std.wasm.MiscOpcode) error{OutOfMemory}!void { - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); try cg.mir_extra.append(cg.gpa, @intFromEnum(opcode)); try cg.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } }); } @@ -889,10 +899,6 @@ fn addLocal(cg: *CodeGen, tag: Mir.Inst.Tag, local: u32) error{OutOfMemory}!void try cg.addInst(.{ .tag = tag, .data = .{ .local = local } }); } -fn addFuncTy(cg: *CodeGen, tag: Mir.Inst.Tag, i: Wasm.FunctionType.Index) error{OutOfMemory}!void { - try cg.addInst(.{ .tag = tag, .data = .{ .func_ty = i } }); -} - /// Accepts an unsigned 32bit integer rather than a signed integer to /// prevent us from having to bitcast multiple times as most values /// within codegen are represented as unsigned rather than signed. @@ -911,7 +917,7 @@ fn addImm64(cg: *CodeGen, imm: u64) error{OutOfMemory}!void { /// Accepts the index into the list of 128bit-immediates fn addImm128(cg: *CodeGen, index: u32) error{OutOfMemory}!void { const simd_values = cg.simd_immediates.items[index]; - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); // tag + 128bit value try cg.mir_extra.ensureUnusedCapacity(cg.gpa, 5); cg.mir_extra.appendAssumeCapacity(@intFromEnum(std.wasm.SimdOpcode.v128_const)); @@ -956,15 +962,13 @@ fn addExtra(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { /// Returns the index into `mir_extra` fn addExtraAssumeCapacity(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { const fields = std.meta.fields(@TypeOf(extra)); - const result = cg.extraLen(); + const result: u32 = @intCast(cg.mir_extra.items.len); inline for (fields) |field| { cg.mir_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), i32 => @bitCast(@field(extra, field.name)), InternPool.Index, InternPool.Nav.Index, - Wasm.UavsObjIndex, - Wasm.UavsExeIndex, => @intFromEnum(@field(extra, field.name)), else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)), }); @@ -1034,18 +1038,12 @@ fn emitWValue(cg: *CodeGen, value: WValue) InnerError!void { .float32 => |val| try cg.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }), .float64 => |val| try cg.addFloat64(val), .nav_ref => |nav_ref| { - const wasm = cg.wasm; - const comp = wasm.base.comp; - const zcu = comp.zcu.?; + const zcu = cg.pt.zcu; const ip = &zcu.intern_pool; if (ip.getNav(nav_ref.nav_index).isFn(ip)) { assert(nav_ref.offset == 0); - const gop = try wasm.zcu_indirect_function_set.getOrPut(comp.gpa, nav_ref.nav_index); - if (!gop.found_existing) gop.value_ptr.* = {}; - try cg.addInst(.{ - .tag = .func_ref, - .data = .{ .indirect_function_table_index = @enumFromInt(gop.index) }, - }); + try cg.mir_indirect_function_set.put(cg.gpa, nav_ref.nav_index, {}); + try cg.addInst(.{ .tag = .func_ref, .data = .{ .nav_index = nav_ref.nav_index } }); } else if (nav_ref.offset == 0) { try cg.addInst(.{ .tag = .nav_ref, .data = .{ .nav_index = nav_ref.nav_index } }); } else { @@ -1061,41 +1059,37 @@ fn emitWValue(cg: *CodeGen, value: WValue) InnerError!void { } }, .uav_ref => |uav| { - const wasm = cg.wasm; - const comp = wasm.base.comp; - const is_obj = comp.config.output_mode == .Obj; - const zcu = comp.zcu.?; + const zcu = cg.pt.zcu; const ip = &zcu.intern_pool; - if (ip.isFunctionType(ip.typeOf(uav.ip_index))) { - assert(uav.offset == 0); - const owner_nav = ip.toFunc(uav.ip_index).owner_nav; - const gop = try wasm.zcu_indirect_function_set.getOrPut(comp.gpa, owner_nav); - if (!gop.found_existing) gop.value_ptr.* = {}; - try cg.addInst(.{ - .tag = .func_ref, - .data = .{ .indirect_function_table_index = @enumFromInt(gop.index) }, - }); - } else if (uav.offset == 0) { + assert(!ip.isFunctionType(ip.typeOf(uav.ip_index))); + const gop = try cg.mir_uavs.getOrPut(cg.gpa, uav.ip_index); + const this_align: Alignment = a: { + if (uav.orig_ptr_ty == .none) break :a .none; + const ptr_type = ip.indexToKey(uav.orig_ptr_ty).ptr_type; + const this_align = ptr_type.flags.alignment; + if (this_align == .none) break :a .none; + const abi_align = Type.fromInterned(ptr_type.child).abiAlignment(zcu); + if (this_align.compare(.lte, abi_align)) break :a .none; + break :a this_align; + }; + if (!gop.found_existing or + gop.value_ptr.* == .none or + (this_align != .none and this_align.compare(.gt, gop.value_ptr.*))) + { + gop.value_ptr.* = this_align; + } + if (uav.offset == 0) { try cg.addInst(.{ .tag = .uav_ref, - .data = if (is_obj) .{ - .uav_obj = try wasm.refUavObj(uav.ip_index, uav.orig_ptr_ty), - } else .{ - .uav_exe = try wasm.refUavExe(uav.ip_index, uav.orig_ptr_ty), - }, + .data = .{ .ip_index = uav.ip_index }, }); } else { try cg.addInst(.{ .tag = .uav_ref_off, - .data = .{ - .payload = if (is_obj) try cg.addExtra(Mir.UavRefOffObj{ - .uav_obj = try wasm.refUavObj(uav.ip_index, uav.orig_ptr_ty), - .offset = uav.offset, - }) else try cg.addExtra(Mir.UavRefOffExe{ - .uav_exe = try wasm.refUavExe(uav.ip_index, uav.orig_ptr_ty), - .offset = uav.offset, - }), - }, + .data = .{ .payload = try cg.addExtra(@as(Mir.UavRefOff, .{ + .value = uav.ip_index, + .offset = uav.offset, + })) }, }); } }, @@ -1157,106 +1151,12 @@ fn allocLocal(cg: *CodeGen, ty: Type) InnerError!WValue { /// to use a zero-initialized local. fn ensureAllocLocal(cg: *CodeGen, ty: Type) InnerError!WValue { const zcu = cg.pt.zcu; - try cg.locals.append(cg.gpa, typeToValtype(ty, zcu, cg.target)); + try cg.mir_locals.append(cg.gpa, typeToValtype(ty, zcu, cg.target)); const initial_index = cg.local_index; cg.local_index += 1; return .{ .local = .{ .value = initial_index, .references = 1 } }; } -pub const Function = extern struct { - /// Index into `Wasm.mir_instructions`. - mir_off: u32, - /// This is unused except for as a safety slice bound and could be removed. - mir_len: u32, - /// Index into `Wasm.mir_extra`. - mir_extra_off: u32, - /// This is unused except for as a safety slice bound and could be removed. - mir_extra_len: u32, - locals_off: u32, - locals_len: u32, - prologue: Prologue, - - pub const Prologue = extern struct { - flags: Flags, - sp_local: u32, - stack_size: u32, - bottom_stack_local: u32, - - pub const Flags = packed struct(u32) { - stack_alignment: Alignment, - padding: u26 = 0, - }; - - pub const none: Prologue = .{ - .sp_local = 0, - .flags = .{ .stack_alignment = .none }, - .stack_size = 0, - .bottom_stack_local = 0, - }; - - pub fn isNone(p: *const Prologue) bool { - return p.flags.stack_alignment != .none; - } - }; - - pub fn lower(f: *Function, wasm: *Wasm, code: *std.ArrayListUnmanaged(u8)) Allocator.Error!void { - const gpa = wasm.base.comp.gpa; - - // Write the locals in the prologue of the function body. - const locals = wasm.all_zcu_locals.items[f.locals_off..][0..f.locals_len]; - try code.ensureUnusedCapacity(gpa, 5 + locals.len * 6 + 38); - - std.leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(locals.len))) catch unreachable; - for (locals) |local| { - std.leb.writeUleb128(code.fixedWriter(), @as(u32, 1)) catch unreachable; - code.appendAssumeCapacity(@intFromEnum(local)); - } - - // Stack management section of function prologue. - const stack_alignment = f.prologue.flags.stack_alignment; - if (stack_alignment.toByteUnits()) |align_bytes| { - const sp_global: Wasm.GlobalIndex = .stack_pointer; - // load stack pointer - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_get)); - std.leb.writeULEB128(code.fixedWriter(), @intFromEnum(sp_global)) catch unreachable; - // store stack pointer so we can restore it when we return from the function - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_tee)); - leb.writeUleb128(code.fixedWriter(), f.prologue.sp_local) catch unreachable; - // get the total stack size - const aligned_stack: i32 = @intCast(stack_alignment.forward(f.prologue.stack_size)); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); - leb.writeIleb128(code.fixedWriter(), aligned_stack) catch unreachable; - // subtract it from the current stack pointer - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_sub)); - // Get negative stack alignment - const neg_stack_align = @as(i32, @intCast(align_bytes)) * -1; - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); - leb.writeIleb128(code.fixedWriter(), neg_stack_align) catch unreachable; - // Bitwise-and the value to get the new stack pointer to ensure the - // pointers are aligned with the abi alignment. - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_and)); - // The bottom will be used to calculate all stack pointer offsets. - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_tee)); - leb.writeUleb128(code.fixedWriter(), f.prologue.bottom_stack_local) catch unreachable; - // Store the current stack pointer value into the global stack pointer so other function calls will - // start from this value instead and not overwrite the current stack. - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_set)); - std.leb.writeULEB128(code.fixedWriter(), @intFromEnum(sp_global)) catch unreachable; - } - - var emit: Emit = .{ - .mir = .{ - .instruction_tags = wasm.mir_instructions.items(.tag)[f.mir_off..][0..f.mir_len], - .instruction_datas = wasm.mir_instructions.items(.data)[f.mir_off..][0..f.mir_len], - .extra = wasm.mir_extra.items[f.mir_extra_off..][0..f.mir_extra_len], - }, - .wasm = wasm, - .code = code, - }; - try emit.lowerToCode(); - } -}; - pub const Error = error{ OutOfMemory, /// Compiler was asked to operate on a number larger than supported. @@ -1265,13 +1165,16 @@ pub const Error = error{ CodegenFail, }; -pub fn function( - wasm: *Wasm, +pub fn generate( + bin_file: *link.File, pt: Zcu.PerThread, + src_loc: Zcu.LazySrcLoc, func_index: InternPool.Index, - air: Air, - liveness: Air.Liveness, -) Error!Function { + air: *const Air, + liveness: *const Air.Liveness, +) Error!Mir { + _ = src_loc; + _ = bin_file; const zcu = pt.zcu; const gpa = zcu.gpa; const cg = zcu.funcInfo(func_index); @@ -1279,10 +1182,8 @@ pub fn function( const target = &file_scope.mod.?.resolved_target.result; const fn_ty = zcu.navValue(cg.owner_nav).typeOf(zcu); const fn_info = zcu.typeToFunc(fn_ty).?; - const ip = &zcu.intern_pool; - const fn_ty_index = try wasm.internFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target); - const returns = fn_ty_index.ptr(wasm).returns.slice(wasm); - const any_returns = returns.len != 0; + const ret_ty: Type = .fromInterned(fn_info.return_type); + const any_returns = !firstParamSRet(fn_info.cc, ret_ty, zcu, target) and ret_ty.hasRuntimeBitsIgnoreComptime(zcu); var cc_result = try resolveCallingConventionValues(zcu, fn_ty, target); defer cc_result.deinit(gpa); @@ -1290,8 +1191,8 @@ pub fn function( var code_gen: CodeGen = .{ .gpa = gpa, .pt = pt, - .air = air, - .liveness = liveness, + .air = air.*, + .liveness = liveness.*, .owner_nav = cg.owner_nav, .target = target, .ptr_size = switch (target.cpu.arch) { @@ -1299,31 +1200,33 @@ pub fn function( .wasm64 => .wasm64, else => unreachable, }, - .wasm = wasm, .func_index = func_index, .args = cc_result.args, .return_value = cc_result.return_value, .local_index = cc_result.local_index, - .mir_instructions = &wasm.mir_instructions, - .mir_extra = &wasm.mir_extra, - .locals = &wasm.all_zcu_locals, - .start_mir_extra_off = @intCast(wasm.mir_extra.items.len), - .start_locals_off = @intCast(wasm.all_zcu_locals.items.len), + .mir_instructions = .empty, + .mir_extra = .empty, + .mir_locals = .empty, + .mir_uavs = .empty, + .mir_indirect_function_set = .empty, + .mir_func_tys = .empty, + .error_name_table_ref_count = 0, }; defer code_gen.deinit(); - return functionInner(&code_gen, any_returns) catch |err| switch (err) { - error.CodegenFail => return error.CodegenFail, + try code_gen.mir_func_tys.putNoClobber(gpa, fn_ty.toIntern(), {}); + + return generateInner(&code_gen, any_returns) catch |err| switch (err) { + error.CodegenFail, + error.OutOfMemory, + error.Overflow, + => |e| return e, else => |e| return code_gen.fail("failed to generate function: {s}", .{@errorName(e)}), }; } -fn functionInner(cg: *CodeGen, any_returns: bool) InnerError!Function { - const wasm = cg.wasm; +fn generateInner(cg: *CodeGen, any_returns: bool) InnerError!Mir { const zcu = cg.pt.zcu; - - const start_mir_off: u32 = @intCast(wasm.mir_instructions.len); - try cg.branches.append(cg.gpa, .{}); // clean up outer branch defer { @@ -1347,20 +1250,25 @@ fn functionInner(cg: *CodeGen, any_returns: bool) InnerError!Function { try cg.addTag(.end); try cg.addTag(.dbg_epilogue_begin); - return .{ - .mir_off = start_mir_off, - .mir_len = @intCast(wasm.mir_instructions.len - start_mir_off), - .mir_extra_off = cg.start_mir_extra_off, - .mir_extra_len = cg.extraLen(), - .locals_off = cg.start_locals_off, - .locals_len = @intCast(wasm.all_zcu_locals.items.len - cg.start_locals_off), + var mir: Mir = .{ + .instructions = cg.mir_instructions.toOwnedSlice(), + .extra = &.{}, // fallible so assigned after errdefer + .locals = &.{}, // fallible so assigned after errdefer .prologue = if (cg.initial_stack_value == .none) .none else .{ .sp_local = cg.initial_stack_value.local.value, .flags = .{ .stack_alignment = cg.stack_alignment }, .stack_size = cg.stack_size, .bottom_stack_local = cg.bottom_stack_value.local.value, }, + .uavs = cg.mir_uavs.move(), + .indirect_function_set = cg.mir_indirect_function_set.move(), + .func_tys = cg.mir_func_tys.move(), + .error_name_table_ref_count = cg.error_name_table_ref_count, }; + errdefer mir.deinit(cg.gpa); + mir.extra = try cg.mir_extra.toOwnedSlice(cg.gpa); + mir.locals = try cg.mir_locals.toOwnedSlice(cg.gpa); + return mir; } const CallWValues = struct { @@ -2220,7 +2128,6 @@ fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { } fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void { - const wasm = cg.wasm; if (modifier == .always_tail) return cg.fail("TODO implement tail calls for wasm", .{}); const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; const extra = cg.air.extraData(Air.Call, pl_op.payload); @@ -2277,8 +2184,11 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie const operand = try cg.resolveInst(pl_op.operand); try cg.emitWValue(operand); - const fn_type_index = try wasm.internFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), cg.target); - try cg.addFuncTy(.call_indirect, fn_type_index); + try cg.mir_func_tys.put(cg.gpa, fn_ty.toIntern(), {}); + try cg.addInst(.{ + .tag = .call_indirect, + .data = .{ .ip_index = fn_ty.toIntern() }, + }); } const result_value = result_value: { @@ -2449,7 +2359,7 @@ fn store(cg: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErr try cg.emitWValue(lhs); try cg.lowerToStack(rhs); // TODO: Add helper functions for simd opcodes - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); // stores as := opcode, offset, alignment (opcode::memarg) try cg.mir_extra.appendSlice(cg.gpa, &[_]u32{ @intFromEnum(std.wasm.SimdOpcode.v128_store), @@ -2574,7 +2484,7 @@ fn load(cg: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValue if (ty.zigTypeTag(zcu) == .vector) { // TODO: Add helper functions for simd opcodes - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); // stores as := opcode, offset, alignment (opcode::memarg) try cg.mir_extra.appendSlice(cg.gpa, &[_]u32{ @intFromEnum(std.wasm.SimdOpcode.v128_load), @@ -4971,7 +4881,7 @@ fn airArrayElemVal(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { try cg.emitWValue(array); - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); try cg.mir_extra.appendSlice(cg.gpa, &operands); try cg.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); @@ -5123,7 +5033,7 @@ fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { else => break :blk, // Cannot make use of simd-instructions }; try cg.emitWValue(operand); - const extra_index: u32 = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); // stores as := opcode, offset, alignment (opcode::memarg) try cg.mir_extra.appendSlice(cg.gpa, &[_]u32{ opcode, @@ -5142,7 +5052,7 @@ fn airSplat(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { else => break :blk, // Cannot make use of simd-instructions }; try cg.emitWValue(operand); - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); try cg.mir_extra.append(cg.gpa, opcode); try cg.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } }); return cg.finishAir(inst, .stack, &.{ty_op.operand}); @@ -5246,7 +5156,7 @@ fn airShuffleTwo(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { } try cg.emitWValue(operand_a); try cg.emitWValue(operand_b); - const extra_index = cg.extraLen(); + const extra_index: u32 = @intCast(cg.mir_extra.items.len); try cg.mir_extra.appendSlice(cg.gpa, &.{ @intFromEnum(std.wasm.SimdOpcode.i8x16_shuffle), @bitCast(lane_map[0..4].*), @@ -6016,9 +5926,8 @@ fn airErrorName(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const name_ty = Type.slice_const_u8_sentinel_0; const abi_size = name_ty.abiSize(pt.zcu); - cg.wasm.error_name_table_ref_count += 1; - // Lowers to a i32.const or i64.const with the error table memory address. + cg.error_name_table_ref_count += 1; try cg.addTag(.error_name_table_ref); try cg.emitWValue(operand); switch (cg.ptr_size) { @@ -6046,7 +5955,7 @@ fn airPtrSliceFieldPtr(cg: *CodeGen, inst: Air.Inst.Index, offset: u32) InnerErr /// NOTE: Allocates place for result on virtual stack, when integer size > 64 bits fn intZeroValue(cg: *CodeGen, ty: Type) InnerError!WValue { - const zcu = cg.wasm.base.comp.zcu.?; + const zcu = cg.pt.zcu; const int_info = ty.intInfo(zcu); const wasm_bits = toWasmBits(int_info.bits) orelse { return cg.fail("TODO: Implement intZeroValue for integer bitsize: {d}", .{int_info.bits}); @@ -7673,7 +7582,3 @@ fn floatCmpIntrinsic(op: std.math.CompareOperator, bits: u16) Mir.Intrinsic { }, }; } - -fn extraLen(cg: *const CodeGen) u32 { - return @intCast(cg.mir_extra.items.len - cg.start_mir_extra_off); -} diff --git a/src/arch/wasm/Emit.zig b/src/arch/wasm/Emit.zig index 28159f3336..8024f2db9e 100644 --- a/src/arch/wasm/Emit.zig +++ b/src/arch/wasm/Emit.zig @@ -31,8 +31,8 @@ pub fn lowerToCode(emit: *Emit) Error!void { const target = &comp.root_mod.resolved_target.result; const is_wasm32 = target.cpu.arch == .wasm32; - const tags = mir.instruction_tags; - const datas = mir.instruction_datas; + const tags = mir.instructions.items(.tag); + const datas = mir.instructions.items(.data); var inst: u32 = 0; loop: switch (tags[inst]) { @@ -50,18 +50,19 @@ pub fn lowerToCode(emit: *Emit) Error!void { }, .uav_ref => { if (is_obj) { - try uavRefOffObj(wasm, code, .{ .uav_obj = datas[inst].uav_obj, .offset = 0 }, is_wasm32); + try uavRefObj(wasm, code, datas[inst].ip_index, 0, is_wasm32); } else { - try uavRefOffExe(wasm, code, .{ .uav_exe = datas[inst].uav_exe, .offset = 0 }, is_wasm32); + try uavRefExe(wasm, code, datas[inst].ip_index, 0, is_wasm32); } inst += 1; continue :loop tags[inst]; }, .uav_ref_off => { + const extra = mir.extraData(Mir.UavRefOff, datas[inst].payload).data; if (is_obj) { - try uavRefOffObj(wasm, code, mir.extraData(Mir.UavRefOffObj, datas[inst].payload).data, is_wasm32); + try uavRefObj(wasm, code, extra.value, extra.offset, is_wasm32); } else { - try uavRefOffExe(wasm, code, mir.extraData(Mir.UavRefOffExe, datas[inst].payload).data, is_wasm32); + try uavRefExe(wasm, code, extra.value, extra.offset, is_wasm32); } inst += 1; continue :loop tags[inst]; @@ -77,11 +78,14 @@ pub fn lowerToCode(emit: *Emit) Error!void { continue :loop tags[inst]; }, .func_ref => { + const indirect_func_idx: Wasm.ZcuIndirectFunctionSetIndex = @enumFromInt( + wasm.zcu_indirect_function_set.getIndex(datas[inst].nav_index).?, + ); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); if (is_obj) { @panic("TODO"); } else { - leb.writeUleb128(code.fixedWriter(), 1 + @intFromEnum(datas[inst].indirect_function_table_index)) catch unreachable; + leb.writeUleb128(code.fixedWriter(), 1 + @intFromEnum(indirect_func_idx)) catch unreachable; } inst += 1; continue :loop tags[inst]; @@ -101,6 +105,7 @@ pub fn lowerToCode(emit: *Emit) Error!void { continue :loop tags[inst]; }, .error_name_table_ref => { + wasm.error_name_table_ref_count += 1; try code.ensureUnusedCapacity(gpa, 11); const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const; code.appendAssumeCapacity(@intFromEnum(opcode)); @@ -176,7 +181,13 @@ pub fn lowerToCode(emit: *Emit) Error!void { .call_indirect => { try code.ensureUnusedCapacity(gpa, 11); - const func_ty_index = datas[inst].func_ty; + const fn_info = comp.zcu.?.typeToFunc(.fromInterned(datas[inst].ip_index)).?; + const func_ty_index = wasm.getExistingFunctionType( + fn_info.cc, + fn_info.param_types.get(&comp.zcu.?.intern_pool), + .fromInterned(fn_info.return_type), + target, + ).?; code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call_indirect)); if (is_obj) { try wasm.out_relocs.append(gpa, .{ @@ -912,7 +923,7 @@ fn encodeMemArg(code: *std.ArrayListUnmanaged(u8), mem_arg: Mir.MemArg) void { leb.writeUleb128(code.fixedWriter(), mem_arg.offset) catch unreachable; } -fn uavRefOffObj(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRefOffObj, is_wasm32: bool) !void { +fn uavRefObj(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), value: InternPool.Index, offset: i32, is_wasm32: bool) !void { const comp = wasm.base.comp; const gpa = comp.gpa; const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const; @@ -922,14 +933,14 @@ fn uavRefOffObj(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRef try wasm.out_relocs.append(gpa, .{ .offset = @intCast(code.items.len), - .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(data.uav_obj.key(wasm).*) }, + .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(value) }, .tag = if (is_wasm32) .memory_addr_leb else .memory_addr_leb64, - .addend = data.offset, + .addend = offset, }); code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10); } -fn uavRefOffExe(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRefOffExe, is_wasm32: bool) !void { +fn uavRefExe(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), value: InternPool.Index, offset: i32, is_wasm32: bool) !void { const comp = wasm.base.comp; const gpa = comp.gpa; const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const; @@ -937,8 +948,8 @@ fn uavRefOffExe(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRef try code.ensureUnusedCapacity(gpa, 11); code.appendAssumeCapacity(@intFromEnum(opcode)); - const addr = wasm.uavAddr(data.uav_exe); - leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(@as(i64, addr) + data.offset))) catch unreachable; + const addr = wasm.uavAddr(value); + leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(@as(i64, addr) + offset))) catch unreachable; } fn navRefOff(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.NavRefOff, is_wasm32: bool) !void { diff --git a/src/arch/wasm/Mir.zig b/src/arch/wasm/Mir.zig index 5c8c558926..3aee13acd7 100644 --- a/src/arch/wasm/Mir.zig +++ b/src/arch/wasm/Mir.zig @@ -9,16 +9,53 @@ const Mir = @This(); const InternPool = @import("../../InternPool.zig"); const Wasm = @import("../../link/Wasm.zig"); +const Emit = @import("Emit.zig"); +const Alignment = InternPool.Alignment; const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; +const leb = std.leb; -instruction_tags: []const Inst.Tag, -instruction_datas: []const Inst.Data, +instructions: std.MultiArrayList(Inst).Slice, /// A slice of indexes where the meaning of the data is determined by the /// `Inst.Tag` value. extra: []const u32, +locals: []const std.wasm.Valtype, +prologue: Prologue, + +/// Not directly used by `Emit`, but the linker needs this to merge it with a global set. +/// Value is the explicit alignment if greater than natural alignment, `.none` otherwise. +uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment), +/// Not directly used by `Emit`, but the linker needs this to merge it with a global set. +indirect_function_set: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void), +/// Not directly used by `Emit`, but the linker needs this to ensure these types are interned. +func_tys: std.AutoArrayHashMapUnmanaged(InternPool.Index, void), +/// Not directly used by `Emit`, but the linker needs this to add it to its own refcount. +error_name_table_ref_count: u32, + +pub const Prologue = extern struct { + flags: Flags, + sp_local: u32, + stack_size: u32, + bottom_stack_local: u32, + + pub const Flags = packed struct(u32) { + stack_alignment: Alignment, + padding: u26 = 0, + }; + + pub const none: Prologue = .{ + .sp_local = 0, + .flags = .{ .stack_alignment = .none }, + .stack_size = 0, + .bottom_stack_local = 0, + }; + + pub fn isNone(p: *const Prologue) bool { + return p.flags.stack_alignment != .none; + } +}; pub const Inst = struct { /// The opcode that represents this instruction @@ -80,7 +117,7 @@ pub const Inst = struct { /// Lowers to an i32_const which is the index of the function in the /// table section. /// - /// Uses `indirect_function_table_index`. + /// Uses `nav_index`. func_ref, /// Inserts debug information about the current line and column /// of the source code @@ -123,7 +160,7 @@ pub const Inst = struct { /// Calls a function pointer by its function signature /// and index into the function table. /// - /// Uses `func_ty` + /// Uses `ip_index`; the `InternPool.Index` is the function type. call_indirect, /// Calls a function by its index. /// @@ -611,11 +648,7 @@ pub const Inst = struct { ip_index: InternPool.Index, nav_index: InternPool.Nav.Index, - func_ty: Wasm.FunctionType.Index, intrinsic: Intrinsic, - uav_obj: Wasm.UavsObjIndex, - uav_exe: Wasm.UavsExeIndex, - indirect_function_table_index: Wasm.ZcuIndirectFunctionSetIndex, comptime { switch (builtin.mode) { @@ -626,10 +659,66 @@ pub const Inst = struct { }; }; -pub fn deinit(self: *Mir, gpa: std.mem.Allocator) void { - self.instructions.deinit(gpa); - gpa.free(self.extra); - self.* = undefined; +pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { + mir.instructions.deinit(gpa); + gpa.free(mir.extra); + gpa.free(mir.locals); + mir.uavs.deinit(gpa); + mir.indirect_function_set.deinit(gpa); + mir.func_tys.deinit(gpa); + mir.* = undefined; +} + +pub fn lower(mir: *const Mir, wasm: *Wasm, code: *std.ArrayListUnmanaged(u8)) std.mem.Allocator.Error!void { + const gpa = wasm.base.comp.gpa; + + // Write the locals in the prologue of the function body. + try code.ensureUnusedCapacity(gpa, 5 + mir.locals.len * 6 + 38); + + std.leb.writeUleb128(code.fixedWriter(), @as(u32, @intCast(mir.locals.len))) catch unreachable; + for (mir.locals) |local| { + std.leb.writeUleb128(code.fixedWriter(), @as(u32, 1)) catch unreachable; + code.appendAssumeCapacity(@intFromEnum(local)); + } + + // Stack management section of function prologue. + const stack_alignment = mir.prologue.flags.stack_alignment; + if (stack_alignment.toByteUnits()) |align_bytes| { + const sp_global: Wasm.GlobalIndex = .stack_pointer; + // load stack pointer + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_get)); + std.leb.writeULEB128(code.fixedWriter(), @intFromEnum(sp_global)) catch unreachable; + // store stack pointer so we can restore it when we return from the function + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_tee)); + leb.writeUleb128(code.fixedWriter(), mir.prologue.sp_local) catch unreachable; + // get the total stack size + const aligned_stack: i32 = @intCast(stack_alignment.forward(mir.prologue.stack_size)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); + leb.writeIleb128(code.fixedWriter(), aligned_stack) catch unreachable; + // subtract it from the current stack pointer + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_sub)); + // Get negative stack alignment + const neg_stack_align = @as(i32, @intCast(align_bytes)) * -1; + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const)); + leb.writeIleb128(code.fixedWriter(), neg_stack_align) catch unreachable; + // Bitwise-and the value to get the new stack pointer to ensure the + // pointers are aligned with the abi alignment. + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_and)); + // The bottom will be used to calculate all stack pointer offsets. + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_tee)); + leb.writeUleb128(code.fixedWriter(), mir.prologue.bottom_stack_local) catch unreachable; + // Store the current stack pointer value into the global stack pointer so other function calls will + // start from this value instead and not overwrite the current stack. + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.global_set)); + std.leb.writeULEB128(code.fixedWriter(), @intFromEnum(sp_global)) catch unreachable; + } + + var emit: Emit = .{ + .mir = mir.*, + .wasm = wasm, + .code = code, + }; + try emit.lowerToCode(); } pub fn extraData(self: *const Mir, comptime T: type, index: usize) struct { data: T, end: usize } { @@ -643,6 +732,7 @@ pub fn extraData(self: *const Mir, comptime T: type, index: usize) struct { data Wasm.UavsObjIndex, Wasm.UavsExeIndex, InternPool.Nav.Index, + InternPool.Index, => @enumFromInt(self.extra[i]), else => |field_type| @compileError("Unsupported field type " ++ @typeName(field_type)), }; @@ -695,13 +785,8 @@ pub const MemArg = struct { alignment: u32, }; -pub const UavRefOffObj = struct { - uav_obj: Wasm.UavsObjIndex, - offset: i32, -}; - -pub const UavRefOffExe = struct { - uav_exe: Wasm.UavsExeIndex, +pub const UavRefOff = struct { + value: InternPool.Index, offset: i32, }; diff --git a/src/codegen.zig b/src/codegen.zig index ea57aaf89c..5a8f17735a 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -123,6 +123,7 @@ pub const AnyMir = union { .stage2_riscv64, .stage2_sparc64, .stage2_x86_64, + .stage2_wasm, .stage2_c, => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa), } @@ -153,6 +154,7 @@ pub fn generateFunction( .stage2_riscv64, .stage2_sparc64, .stage2_x86_64, + .stage2_wasm, .stage2_c, => |backend| { dev.check(devFeatureForBackend(backend)); @@ -784,7 +786,6 @@ fn lowerUavRef( const comp = lf.comp; const target = &comp.root_mod.resolved_target.result; const ptr_width_bytes = @divExact(target.ptrBitWidth(), 8); - const is_obj = comp.config.output_mode == .Obj; const uav_val = uav.val; const uav_ty = Type.fromInterned(ip.typeOf(uav_val)); const is_fn_body = uav_ty.zigTypeTag(zcu) == .@"fn"; @@ -804,21 +805,7 @@ fn lowerUavRef( dev.check(link.File.Tag.wasm.devFeature()); const wasm = lf.cast(.wasm).?; assert(reloc_parent == .none); - if (is_obj) { - try wasm.out_relocs.append(gpa, .{ - .offset = @intCast(code.items.len), - .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(uav.val) }, - .tag = if (ptr_width_bytes == 4) .memory_addr_i32 else .memory_addr_i64, - .addend = @intCast(offset), - }); - } else { - try wasm.uav_fixups.ensureUnusedCapacity(gpa, 1); - wasm.uav_fixups.appendAssumeCapacity(.{ - .uavs_exe_index = try wasm.refUavExe(uav.val, uav.orig_ty), - .offset = @intCast(code.items.len), - .addend = @intCast(offset), - }); - } + try wasm.addUavReloc(code.items.len, uav.val, uav.orig_ty, @intCast(offset)); code.appendNTimesAssumeCapacity(0, ptr_width_bytes); return; }, diff --git a/src/link.zig b/src/link.zig index 838654775d..f49acbf3d6 100644 --- a/src/link.zig +++ b/src/link.zig @@ -759,7 +759,6 @@ pub const File = struct { switch (base.tag) { .lld => unreachable, inline else => |tag| { - if (tag == .wasm) @panic("MLUGG TODO"); if (tag == .spirv) @panic("MLUGG TODO"); dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, mir, maybe_undef_air); @@ -1450,12 +1449,12 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { const nav = zcu.funcInfo(func.func).owner_nav; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); - assert(zcu.llvm_object == null); // LLVM codegen doesn't produce MIR switch (func.mir.status.load(.monotonic)) { .pending => unreachable, .ready => {}, .failed => return, } + assert(zcu.llvm_object == null); // LLVM codegen doesn't produce MIR const mir = &func.mir.value; if (comp.bin_file) |lf| { lf.updateFunc(pt, func.func, mir, func.air) catch |err| switch (err) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 5c804ed21f..67e530b5cc 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -282,7 +282,7 @@ mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, /// Corresponds to `mir_instructions`. mir_extra: std.ArrayListUnmanaged(u32) = .empty, /// All local types for all Zcu functions. -all_zcu_locals: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty, +mir_locals: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty, params_scratch: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty, returns_scratch: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty, @@ -866,9 +866,24 @@ const ZcuDataStarts = struct { }; pub const ZcuFunc = union { - function: CodeGen.Function, + function: Function, tag_name: TagName, + pub const Function = extern struct { + /// Index into `Wasm.mir_instructions`. + instructions_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + instructions_len: u32, + /// Index into `Wasm.mir_extra`. + extra_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + extra_len: u32, + /// Index into `Wasm.mir_locals`. + locals_off: u32, + locals_len: u32, + prologue: Mir.Prologue, + }; + pub const TagName = extern struct { symbol_name: String, type_index: FunctionType.Index, @@ -3107,7 +3122,7 @@ pub fn deinit(wasm: *Wasm) void { wasm.mir_instructions.deinit(gpa); wasm.mir_extra.deinit(gpa); - wasm.all_zcu_locals.deinit(gpa); + wasm.mir_locals.deinit(gpa); if (wasm.dwarf) |*dwarf| dwarf.deinit(); @@ -3167,33 +3182,96 @@ pub fn deinit(wasm: *Wasm) void { wasm.missing_exports.deinit(gpa); } -pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Air.Liveness) !void { +pub fn updateFunc( + wasm: *Wasm, + pt: Zcu.PerThread, + func_index: InternPool.Index, + any_mir: *const codegen.AnyMir, + maybe_undef_air: *const Air, +) !void { if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } dev.check(.wasm_backend); + _ = maybe_undef_air; // we (correctly) do not need this + // This linker implementation only works with codegen backend `.stage2_wasm`. + const mir = &any_mir.wasm; const zcu = pt.zcu; const gpa = zcu.gpa; - try wasm.functions.ensureUnusedCapacity(gpa, 1); - try wasm.zcu_funcs.ensureUnusedCapacity(gpa, 1); - const ip = &zcu.intern_pool; + const is_obj = zcu.comp.config.output_mode == .Obj; + const target = &zcu.comp.root_mod.resolved_target.result; const owner_nav = zcu.funcInfo(func_index).owner_nav; log.debug("updateFunc {}", .{ip.getNav(owner_nav).fqn.fmt(ip)}); + // For Wasm, we do not lower the MIR to code just yet. That lowering happens during `flush`, + // after garbage collection, which can affect function and global indexes, which affects the + // LEB integer encoding, which affects the output binary size. + + // However, we do move the MIR into a more efficient in-memory representation, where the arrays + // for all functions are packed together rather than keeping them each in their own `Mir`. + const mir_instructions_off: u32 = @intCast(wasm.mir_instructions.len); + const mir_extra_off: u32 = @intCast(wasm.mir_extra.items.len); + const mir_locals_off: u32 = @intCast(wasm.mir_locals.items.len); + { + // Copying MultiArrayList data is a little non-trivial. Resize, then memcpy both slices. + const old_len = wasm.mir_instructions.len; + try wasm.mir_instructions.resize(gpa, old_len + mir.instructions.len); + const dest_slice = wasm.mir_instructions.slice().subslice(old_len, mir.instructions.len); + const src_slice = mir.instructions; + @memcpy(dest_slice.items(.tag), src_slice.items(.tag)); + @memcpy(dest_slice.items(.data), src_slice.items(.data)); + } + try wasm.mir_extra.appendSlice(gpa, mir.extra); + try wasm.mir_locals.appendSlice(gpa, mir.locals); + + // We also need to populate some global state from `mir`. + try wasm.zcu_indirect_function_set.ensureUnusedCapacity(gpa, mir.indirect_function_set.count()); + for (mir.indirect_function_set.keys()) |nav| wasm.zcu_indirect_function_set.putAssumeCapacity(nav, {}); + for (mir.func_tys.keys()) |func_ty| { + const fn_info = zcu.typeToFunc(.fromInterned(func_ty)).?; + _ = try wasm.internFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target); + } + wasm.error_name_table_ref_count += mir.error_name_table_ref_count; + // We need to populate UAV data. In theory, we can lower the UAV values while we fill `mir.uavs`. + // However, lowering the data might cause *more* UAVs to be created, and mixing them up would be + // a headache. So instead, just write `undefined` placeholder code and use the `ZcuDataStarts`. const zds: ZcuDataStarts = .init(wasm); + for (mir.uavs.keys(), mir.uavs.values()) |uav_val, uav_align| { + if (uav_align != .none) { + const gop = try wasm.overaligned_uavs.getOrPut(gpa, uav_val); + gop.value_ptr.* = if (gop.found_existing) gop.value_ptr.maxStrict(uav_align) else uav_align; + } + if (is_obj) { + const gop = try wasm.uavs_obj.getOrPut(gpa, uav_val); + if (!gop.found_existing) gop.value_ptr.* = undefined; // `zds` handles lowering + } else { + const gop = try wasm.uavs_exe.getOrPut(gpa, uav_val); + if (!gop.found_existing) gop.value_ptr.* = .{ + .code = undefined, // `zds` handles lowering + .count = 0, + }; + gop.value_ptr.count += 1; + } + } + try zds.finish(wasm, pt); // actually generates the UAVs + + try wasm.functions.ensureUnusedCapacity(gpa, 1); + try wasm.zcu_funcs.ensureUnusedCapacity(gpa, 1); // This converts AIR to MIR but does not yet lower to wasm code. - // That lowering happens during `flush`, after garbage collection, which - // can affect function and global indexes, which affects the LEB integer - // encoding, which affects the output binary size. - const function = try CodeGen.function(wasm, pt, func_index, air, liveness); - wasm.zcu_funcs.putAssumeCapacity(func_index, .{ .function = function }); + wasm.zcu_funcs.putAssumeCapacity(func_index, .{ .function = .{ + .instructions_off = mir_instructions_off, + .instructions_len = @intCast(mir.instructions.len), + .extra_off = mir_extra_off, + .extra_len = @intCast(mir.extra.len), + .locals_off = mir_locals_off, + .locals_len = @intCast(mir.locals.len), + .prologue = mir.prologue, + } }); wasm.functions.putAssumeCapacity(.pack(wasm, .{ .zcu_func = @enumFromInt(wasm.zcu_funcs.entries.len - 1) }), {}); - - try zds.finish(wasm, pt); } // Generate code for the "Nav", storing it in memory to be later written to @@ -3988,58 +4066,54 @@ pub fn symbolNameIndex(wasm: *Wasm, name: String) Allocator.Error!SymbolTableInd return @enumFromInt(gop.index); } -pub fn refUavObj(wasm: *Wasm, ip_index: InternPool.Index, orig_ptr_ty: InternPool.Index) !UavsObjIndex { - const comp = wasm.base.comp; - const zcu = comp.zcu.?; - const ip = &zcu.intern_pool; - const gpa = comp.gpa; - assert(comp.config.output_mode == .Obj); - - if (orig_ptr_ty != .none) { - const abi_alignment = Zcu.Type.fromInterned(ip.typeOf(ip_index)).abiAlignment(zcu); - const explicit_alignment = ip.indexToKey(orig_ptr_ty).ptr_type.flags.alignment; - if (explicit_alignment.compare(.gt, abi_alignment)) { - const gop = try wasm.overaligned_uavs.getOrPut(gpa, ip_index); - gop.value_ptr.* = if (gop.found_existing) gop.value_ptr.maxStrict(explicit_alignment) else explicit_alignment; - } - } - - const gop = try wasm.uavs_obj.getOrPut(gpa, ip_index); - if (!gop.found_existing) gop.value_ptr.* = .{ - // Lowering the value is delayed to avoid recursion. - .code = undefined, - .relocs = undefined, - }; - return @enumFromInt(gop.index); -} - -pub fn refUavExe(wasm: *Wasm, ip_index: InternPool.Index, orig_ptr_ty: InternPool.Index) !UavsExeIndex { +pub fn addUavReloc( + wasm: *Wasm, + reloc_offset: usize, + uav_val: InternPool.Index, + orig_ptr_ty: InternPool.Index, + addend: u32, +) !void { const comp = wasm.base.comp; const zcu = comp.zcu.?; const ip = &zcu.intern_pool; const gpa = comp.gpa; - assert(comp.config.output_mode != .Obj); - if (orig_ptr_ty != .none) { - const abi_alignment = Zcu.Type.fromInterned(ip.typeOf(ip_index)).abiAlignment(zcu); - const explicit_alignment = ip.indexToKey(orig_ptr_ty).ptr_type.flags.alignment; - if (explicit_alignment.compare(.gt, abi_alignment)) { - const gop = try wasm.overaligned_uavs.getOrPut(gpa, ip_index); - gop.value_ptr.* = if (gop.found_existing) gop.value_ptr.maxStrict(explicit_alignment) else explicit_alignment; - } - } - - const gop = try wasm.uavs_exe.getOrPut(gpa, ip_index); - if (gop.found_existing) { - gop.value_ptr.count += 1; + @"align": { + const ptr_type = ip.indexToKey(orig_ptr_ty).ptr_type; + const this_align = ptr_type.flags.alignment; + if (this_align == .none) break :@"align"; + const abi_align = Zcu.Type.fromInterned(ptr_type.child).abiAlignment(zcu); + if (this_align.compare(.lte, abi_align)) break :@"align"; + const gop = try wasm.overaligned_uavs.getOrPut(gpa, uav_val); + gop.value_ptr.* = if (gop.found_existing) gop.value_ptr.maxStrict(this_align) else this_align; + } + + if (comp.config.output_mode == .Obj) { + const gop = try wasm.uavs_obj.getOrPut(gpa, uav_val); + if (!gop.found_existing) gop.value_ptr.* = undefined; // to avoid recursion, `ZcuDataStarts` will lower the value later + try wasm.out_relocs.append(gpa, .{ + .offset = @intCast(reloc_offset), + .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(uav_val) }, + .tag = switch (wasm.pointerSize()) { + 32 => .memory_addr_i32, + 64 => .memory_addr_i64, + else => unreachable, + }, + .addend = @intCast(addend), + }); } else { - gop.value_ptr.* = .{ - // Lowering the value is delayed to avoid recursion. - .code = undefined, - .count = 1, + const gop = try wasm.uavs_exe.getOrPut(gpa, uav_val); + if (!gop.found_existing) gop.value_ptr.* = .{ + .code = undefined, // to avoid recursion, `ZcuDataStarts` will lower the value later + .count = 0, }; + gop.value_ptr.count += 1; + try wasm.uav_fixups.append(gpa, .{ + .uavs_exe_index = @enumFromInt(gop.index), + .offset = @intCast(reloc_offset), + .addend = addend, + }); } - return @enumFromInt(gop.index); } pub fn refNavObj(wasm: *Wasm, nav_index: InternPool.Nav.Index) !NavsObjIndex { @@ -4073,10 +4147,11 @@ pub fn refNavExe(wasm: *Wasm, nav_index: InternPool.Nav.Index) !NavsExeIndex { } /// Asserts it is called after `Flush.data_segments` is fully populated and sorted. -pub fn uavAddr(wasm: *Wasm, uav_index: UavsExeIndex) u32 { +pub fn uavAddr(wasm: *Wasm, ip_index: InternPool.Index) u32 { assert(wasm.flush_buffer.memory_layout_finished); const comp = wasm.base.comp; assert(comp.config.output_mode != .Obj); + const uav_index: UavsExeIndex = @enumFromInt(wasm.uavs_exe.getIndex(ip_index).?); const ds_id: DataSegmentId = .pack(wasm, .{ .uav_exe = uav_index }); return wasm.flush_buffer.data_segments.get(ds_id).?; } diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig index 7ed72e8518..60f5971e40 100644 --- a/src/link/Wasm/Flush.zig +++ b/src/link/Wasm/Flush.zig @@ -9,6 +9,7 @@ const Alignment = Wasm.Alignment; const String = Wasm.String; const Relocation = Wasm.Relocation; const InternPool = @import("../../InternPool.zig"); +const Mir = @import("../../arch/wasm/Mir.zig"); const build_options = @import("build_options"); @@ -868,7 +869,21 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { .enum_type => { try emitTagNameFunction(wasm, binary_bytes, f.data_segments.get(.__zig_tag_name_table).?, i.value(wasm).tag_name.table_index, ip_index); }, - else => try i.value(wasm).function.lower(wasm, binary_bytes), + else => { + const func = i.value(wasm).function; + const mir: Mir = .{ + .instructions = wasm.mir_instructions.slice().subslice(func.instructions_off, func.instructions_len), + .extra = wasm.mir_extra.items[func.extra_off..][0..func.extra_len], + .locals = wasm.mir_locals.items[func.locals_off..][0..func.locals_len], + .prologue = func.prologue, + // These fields are unused by `lower`. + .uavs = undefined, + .indirect_function_set = undefined, + .func_tys = undefined, + .error_name_table_ref_count = undefined, + }; + try mir.lower(wasm, binary_bytes); + }, } }, }; diff --git a/src/target.zig b/src/target.zig index 01c6a6cbf0..a408c82c14 100644 --- a/src/target.zig +++ b/src/target.zig @@ -851,7 +851,7 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt .separate_thread => switch (backend) { .stage2_llvm => false, // MLUGG TODO - .stage2_c => true, + .stage2_c, .stage2_wasm => true, else => false, }, }; -- cgit v1.2.3 From b5f73f8a7b90c5144b79692f142b5d91025dbe01 Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 6 Jun 2025 20:16:26 +0100 Subject: compiler: rework emit paths and cache modes Previously, various doc comments heavily disagreed with the implementation on both what lives where on the filesystem at what time, and how that was represented in code. Notably, the combination of emit paths outside the cache and `disable_lld_caching` created a kind of ad-hoc "cache disable" mechanism -- which didn't actually *work* very well, 'most everything still ended up in this cache. There was also a long-standing issue where building using the LLVM backend would put a random object file in your cwd. This commit reworks how emit paths are specified in `Compilation.CreateOptions`, how they are represented internally, and how the cache usage is specified. There are now 3 options for `Compilation.CacheMode`: * `.none`: do not use the cache. The paths we have to emit to are relative to the compiler cwd (they're either user-specified, or defaults inferred from the root name). If we create any temporary files (e.g. the ZCU object when using the LLVM backend) they are emitted to a directory in `local_cache/tmp/`, which is deleted once the update finishes. * `.whole`: cache the compilation based on all inputs, including file contents. All emit paths are computed by the compiler (and will be stored as relative to the local cache directory); it is a CLI error to specify an explicit emit path. Artifacts (including temporary files) are written to a directory under `local_cache/tmp/`, which is later renamed to an appropriate `local_cache/o/`. The caller (who is using `--listen`; e.g. the build system) learns the name of this directory, and can get the artifacts from it. * `.incremental`: similar to `.whole`, but Zig source file contents, and anything else which incremental compilation can handle changes for, is not included in the cache manifest. We don't need to do the dance where the output directory is initially in `tmp/`, because our digest is computed entirely from CLI inputs. To be clear, the difference between `CacheMode.whole` and `CacheMode.incremental` is unchanged. `CacheMode.none` is new (previously it was sort of poorly imitated with `CacheMode.whole`). The defined behavior for temporary/intermediate files is new. `.none` is used for direct CLI invocations like `zig build-exe foo.zig`. The other cache modes are reserved for `--listen`, and the cache mode in use is currently just based on the presence of the `-fincremental` flag. There are two cases in which `CacheMode.whole` is used despite there being no `--listen` flag: `zig test` and `zig run`. Unless an explicit `-femit-bin=xxx` argument is passed on the CLI, these subcommands will use `CacheMode.whole`, so that they can put the output somewhere without polluting the cwd (plus, caching is potentially more useful for direct usage of these subcommands). Users of `--listen` (such as the build system) can now use `std.zig.EmitArtifact.cacheName` to find out what an output will be named. This avoids having to synchronize logic between the compiler and all users of `--listen`. --- lib/std/Build/Step/Compile.zig | 66 ++-- lib/std/zig.zig | 29 ++ src/Compilation.zig | 718 +++++++++++++++++++++-------------------- src/Zcu/PerThread.zig | 4 +- src/libs/freebsd.zig | 7 +- src/libs/glibc.zig | 7 +- src/libs/libcxx.zig | 28 +- src/libs/libtsan.zig | 8 +- src/libs/libunwind.zig | 15 +- src/libs/musl.zig | 3 +- src/libs/netbsd.zig | 7 +- src/link.zig | 36 ++- src/link/Coff.zig | 13 +- src/link/Elf.zig | 23 +- src/link/Goff.zig | 2 +- src/link/Lld.zig | 75 ++--- src/link/MachO.zig | 40 +-- src/link/Wasm.zig | 25 +- src/link/Xcoff.zig | 2 +- src/main.zig | 355 ++++++-------------- tools/incr-check.zig | 2 +- 21 files changed, 624 insertions(+), 841 deletions(-) (limited to 'src/Compilation.zig') diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index dd8c4158ce..924dc18f91 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1834,47 +1834,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void { lp.path = b.fmt("{}", .{output_dir}); } - // -femit-bin[=path] (default) Output machine code - if (compile.generated_bin) |bin| { - bin.path = output_dir.joinString(b.allocator, compile.out_filename) catch @panic("OOM"); - } - - const sep = std.fs.path.sep_str; - - // output PDB if someone requested it - if (compile.generated_pdb) |pdb| { - pdb.path = b.fmt("{}" ++ sep ++ "{s}.pdb", .{ output_dir, compile.name }); - } - - // -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL - if (compile.generated_implib) |implib| { - implib.path = b.fmt("{}" ++ sep ++ "{s}.lib", .{ output_dir, compile.name }); - } - - // -femit-h[=path] Generate a C header file (.h) - if (compile.generated_h) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.h", .{ output_dir, compile.name }); - } - - // -femit-docs[=path] Create a docs/ dir with html documentation - if (compile.generated_docs) |generated_docs| { - generated_docs.path = output_dir.joinString(b.allocator, "docs") catch @panic("OOM"); - } - - // -femit-asm[=path] Output .s (assembly code) - if (compile.generated_asm) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.s", .{ output_dir, compile.name }); - } - - // -femit-llvm-ir[=path] Produce a .ll file with optimized LLVM IR (requires LLVM extensions) - if (compile.generated_llvm_ir) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.ll", .{ output_dir, compile.name }); - } - - // -femit-llvm-bc[=path] Produce an optimized LLVM module as a .bc file (requires LLVM extensions) - if (compile.generated_llvm_bc) |lp| { - lp.path = b.fmt("{}" ++ sep ++ "{s}.bc", .{ output_dir, compile.name }); - } + // zig fmt: off + if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin); + if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb); + if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib); + if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h); + if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs); + if (compile.generated_asm) |lp| lp.path = compile.outputPath(output_dir, .@"asm"); + if (compile.generated_llvm_ir) |lp| lp.path = compile.outputPath(output_dir, .llvm_ir); + if (compile.generated_llvm_bc) |lp| lp.path = compile.outputPath(output_dir, .llvm_bc); + // zig fmt: on } if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and @@ -1888,6 +1857,21 @@ fn make(step: *Step, options: Step.MakeOptions) !void { ); } } +fn outputPath(c: *Compile, out_dir: std.Build.Cache.Path, ea: std.zig.EmitArtifact) []const u8 { + const arena = c.step.owner.graph.arena; + const name = ea.cacheName(arena, .{ + .root_name = c.name, + .target = c.root_module.resolved_target.?.result, + .output_mode = switch (c.kind) { + .lib => .Lib, + .obj, .test_obj => .Obj, + .exe, .@"test" => .Exe, + }, + .link_mode = c.linkage, + .version = c.version, + }) catch @panic("OOM"); + return out_dir.joinString(arena, name) catch @panic("OOM"); +} pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) !Path { const gpa = c.step.owner.allocator; diff --git a/lib/std/zig.zig b/lib/std/zig.zig index a946fe5e8b..21f1296766 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -884,6 +884,35 @@ pub const SimpleComptimeReason = enum(u32) { } }; +/// Every kind of artifact which the compiler can emit. +pub const EmitArtifact = enum { + bin, + @"asm", + implib, + llvm_ir, + llvm_bc, + docs, + pdb, + h, + + /// If using `Server` to communicate with the compiler, it will place requested artifacts in + /// paths under the output directory, where those paths are named according to this function. + /// Returned string is allocated with `gpa` and owned by the caller. + pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 { + const suffix: []const u8 = switch (ea) { + .bin => return binNameAlloc(gpa, opts), + .@"asm" => ".s", + .implib => ".lib", + .llvm_ir => ".ll", + .llvm_bc => ".bc", + .docs => "-docs", + .pdb => ".pdb", + .h => ".h", + }; + return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix }); + } +}; + test { _ = Ast; _ = AstRlAnnotate; diff --git a/src/Compilation.zig b/src/Compilation.zig index 0342566e27..b9b51222eb 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -55,8 +55,7 @@ gpa: Allocator, arena: Allocator, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. zcu: ?*Zcu, -/// Contains different state depending on whether the Compilation uses -/// incremental or whole cache mode. +/// Contains different state depending on the `CacheMode` used by this `Compilation`. cache_use: CacheUse, /// All compilations have a root module because this is where some important /// settings are stored, such as target and optimization mode. This module @@ -67,17 +66,13 @@ root_mod: *Package.Module, config: Config, /// The main output file. -/// In whole cache mode, this is null except for during the body of the update -/// function. In incremental cache mode, this is a long-lived object. -/// In both cases, this is `null` when `-fno-emit-bin` is used. +/// In `CacheMode.whole`, this is null except for during the body of `update`. +/// In `CacheMode.none` and `CacheMode.incremental`, this is long-lived. +/// Regardless of cache mode, this is `null` when `-fno-emit-bin` is used. bin_file: ?*link.File, /// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin) sysroot: ?[]const u8, -/// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used. -implib_emit: ?Cache.Path, -/// This is non-null when `-femit-docs` is provided. -docs_emit: ?Cache.Path, root_name: [:0]const u8, compiler_rt_strat: RtStrat, ubsan_rt_strat: RtStrat, @@ -259,10 +254,6 @@ mutex: if (builtin.single_threaded) struct { test_filters: []const []const u8, test_name_prefix: ?[]const u8, -emit_asm: ?EmitLoc, -emit_llvm_ir: ?EmitLoc, -emit_llvm_bc: ?EmitLoc, - link_task_wait_group: WaitGroup = .{}, work_queue_progress_node: std.Progress.Node = .none, @@ -274,6 +265,31 @@ file_system_inputs: ?*std.ArrayListUnmanaged(u8), /// This digest will be known after update() is called. digest: ?[Cache.bin_digest_len]u8 = null, +/// Non-`null` iff we are emitting a binary. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_bin: ?[]const u8, +/// Non-`null` iff we are emitting assembly. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_asm: ?[]const u8, +/// Non-`null` iff we are emitting an implib. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_implib: ?[]const u8, +/// Non-`null` iff we are emitting LLVM IR. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_llvm_ir: ?[]const u8, +/// Non-`null` iff we are emitting LLVM bitcode. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_llvm_bc: ?[]const u8, +/// Non-`null` iff we are emitting documentation. +/// Does not change for the lifetime of this `Compilation`. +/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache. +emit_docs: ?[]const u8, + const QueuedJobs = struct { compiler_rt_lib: bool = false, compiler_rt_obj: bool = false, @@ -774,13 +790,6 @@ pub const CrtFile = struct { lock: Cache.Lock, full_object_path: Cache.Path, - pub fn isObject(cf: CrtFile) bool { - return switch (classifyFileExt(cf.full_object_path.sub_path)) { - .object => true, - else => false, - }; - } - pub fn deinit(self: *CrtFile, gpa: Allocator) void { self.lock.release(); gpa.free(self.full_object_path.sub_path); @@ -1321,14 +1330,6 @@ pub const MiscError = struct { } }; -pub const EmitLoc = struct { - /// If this is `null` it means the file will be output to the cache directory. - /// When provided, both the open file handle and the path name must outlive the `Compilation`. - directory: ?Cache.Directory, - /// This may not have sub-directories in it. - basename: []const u8, -}; - pub const cache_helpers = struct { pub fn addModule(hh: *Cache.HashHelper, mod: *const Package.Module) void { addResolvedTarget(hh, mod.resolved_target); @@ -1368,15 +1369,6 @@ pub const cache_helpers = struct { hh.add(resolved_target.is_explicit_dynamic_linker); } - pub fn addEmitLoc(hh: *Cache.HashHelper, emit_loc: EmitLoc) void { - hh.addBytes(emit_loc.basename); - } - - pub fn addOptionalEmitLoc(hh: *Cache.HashHelper, optional_emit_loc: ?EmitLoc) void { - hh.add(optional_emit_loc != null); - addEmitLoc(hh, optional_emit_loc orelse return); - } - pub fn addOptionalDebugFormat(hh: *Cache.HashHelper, x: ?Config.DebugFormat) void { hh.add(x != null); addDebugFormat(hh, x orelse return); @@ -1423,7 +1415,38 @@ pub const ClangPreprocessorMode = enum { pub const Framework = link.File.MachO.Framework; pub const SystemLib = link.SystemLib; -pub const CacheMode = enum { incremental, whole }; +pub const CacheMode = enum { + /// The results of this compilation are not cached. The compilation is always performed, and the + /// results are emitted directly to their output locations. Temporary files will be placed in a + /// temporary directory in the cache, but deleted after the compilation is done. + /// + /// This mode is typically used for direct CLI invocations like `zig build-exe`, because such + /// processes are typically low-level usages which would not make efficient use of the cache. + none, + /// The compilation is cached based only on the options given when creating the `Compilation`. + /// In particular, Zig source file contents are not included in the cache manifest. This mode + /// allows incremental compilation, because the old cached compilation state can be restored + /// and the old binary patched up with the changes. All files, including temporary files, are + /// stored in the cache directory like '/o//'. Temporary files are not deleted. + /// + /// At the time of writing, incremental compilation is only supported with the `-fincremental` + /// command line flag, so this mode is rarely used. However, it is required in order to use + /// incremental compilation. + incremental, + /// The compilation is cached based on the `Compilation` options and every input, including Zig + /// source files, linker inputs, and `@embedFile` targets. If any of them change, we will see a + /// cache miss, and the entire compilation will be re-run. On a cache miss, we initially write + /// all output files to a directory under '/tmp/', because we don't know the final + /// manifest digest until the update is almost done. Once we can compute the final digest, this + /// directory is moved to '/o//'. Temporary files are not deleted. + /// + /// At the time of writing, this is the most commonly used cache mode: it is used by the build + /// system (and any other parent using `--listen`) unless incremental compilation is enabled. + /// Once incremental compilation is more mature, it will be replaced by `incremental` in many + /// cases, but still has use cases, such as for release binaries, particularly globally cached + /// artifacts like compiler_rt. + whole, +}; pub const ParentWholeCache = struct { manifest: *Cache.Manifest, @@ -1432,22 +1455,33 @@ pub const ParentWholeCache = struct { }; const CacheUse = union(CacheMode) { + none: *None, incremental: *Incremental, whole: *Whole, + const None = struct { + /// User-requested artifacts are written directly to their output path in this cache mode. + /// However, if we need to emit any temporary files, they are placed in this directory. + /// We will recursively delete this directory at the end of this update. This field is + /// non-`null` only inside `update`. + tmp_artifact_directory: ?Cache.Directory, + }; + + const Incremental = struct { + /// All output files, including artifacts and incremental compilation metadata, are placed + /// in this directory, which is some 'o/' in a cache directory. + artifact_directory: Cache.Directory, + }; + const Whole = struct { - /// This is a pointer to a local variable inside `update()`. - cache_manifest: ?*Cache.Manifest = null, - cache_manifest_mutex: std.Thread.Mutex = .{}, - /// null means -fno-emit-bin. - /// This is mutable memory allocated into the Compilation-lifetime arena (`arena`) - /// of exactly the correct size for "o/[digest]/[basename]". - /// The basename is of the outputted binary file in case we don't know the directory yet. - bin_sub_path: ?[]u8, - /// Same as `bin_sub_path` but for implibs. - implib_sub_path: ?[]u8, - docs_sub_path: ?[]u8, + /// Since we don't open the output file until `update`, we must save these options for then. lf_open_opts: link.File.OpenOptions, + /// This is a pointer to a local variable inside `update`. + cache_manifest: ?*Cache.Manifest, + cache_manifest_mutex: std.Thread.Mutex, + /// This is non-`null` for most of the body of `update`. It is the temporary directory which + /// we initially emit our artifacts to. After the main part of the update is done, it will + /// be closed and moved to its final location, and this field set to `null`. tmp_artifact_directory: ?Cache.Directory, /// Prevents other processes from clobbering files in the output directory. lock: ?Cache.Lock, @@ -1466,17 +1500,16 @@ const CacheUse = union(CacheMode) { } }; - const Incremental = struct { - /// Where build artifacts and incremental compilation metadata serialization go. - artifact_directory: Cache.Directory, - }; - fn deinit(cu: CacheUse) void { switch (cu) { + .none => |none| { + assert(none.tmp_artifact_directory == null); + }, .incremental => |incremental| { incremental.artifact_directory.handle.close(); }, .whole => |whole| { + assert(whole.tmp_artifact_directory == null); whole.releaseLock(); }, } @@ -1503,28 +1536,14 @@ pub const CreateOptions = struct { std_mod: ?*Package.Module = null, root_name: []const u8, sysroot: ?[]const u8 = null, - /// `null` means to not emit a binary file. - emit_bin: ?EmitLoc, - /// `null` means to not emit a C header file. - emit_h: ?EmitLoc = null, - /// `null` means to not emit assembly. - emit_asm: ?EmitLoc = null, - /// `null` means to not emit LLVM IR. - emit_llvm_ir: ?EmitLoc = null, - /// `null` means to not emit LLVM module bitcode. - emit_llvm_bc: ?EmitLoc = null, - /// `null` means to not emit docs. - emit_docs: ?EmitLoc = null, - /// `null` means to not emit an import lib. - emit_implib: ?EmitLoc = null, - /// Normally when using LLD to link, Zig uses a file named "lld.id" in the - /// same directory as the output binary which contains the hash of the link - /// operation, allowing Zig to skip linking when the hash would be unchanged. - /// In the case that the output binary is being emitted into a directory which - /// is externally modified - essentially anything other than zig-cache - then - /// this flag would be set to disable this machinery to avoid false positives. - disable_lld_caching: bool = false, - cache_mode: CacheMode = .incremental, + cache_mode: CacheMode, + emit_h: Emit = .no, + emit_bin: Emit, + emit_asm: Emit = .no, + emit_implib: Emit = .no, + emit_llvm_ir: Emit = .no, + emit_llvm_bc: Emit = .no, + emit_docs: Emit = .no, /// This field is intended to be removed. /// The ELF implementation no longer uses this data, however the MachO and COFF /// implementations still do. @@ -1662,6 +1681,38 @@ pub const CreateOptions = struct { parent_whole_cache: ?ParentWholeCache = null, pub const Entry = link.File.OpenOptions.Entry; + + /// Which fields are valid depends on the `cache_mode` given. + pub const Emit = union(enum) { + /// Do not emit this file. Always valid. + no, + /// Emit this file into its default name in the cache directory. + /// Requires `cache_mode` to not be `.none`. + yes_cache, + /// Emit this file to the given path (absolute or cwd-relative). + /// Requires `cache_mode` to be `.none`. + yes_path: []const u8, + + fn resolve(emit: Emit, arena: Allocator, opts: *const CreateOptions, ea: std.zig.EmitArtifact) Allocator.Error!?[]const u8 { + switch (emit) { + .no => return null, + .yes_cache => { + assert(opts.cache_mode != .none); + return try ea.cacheName(arena, .{ + .root_name = opts.root_name, + .target = opts.root_mod.resolved_target.result, + .output_mode = opts.config.output_mode, + .link_mode = opts.config.link_mode, + .version = opts.version, + }); + }, + .yes_path => |path| { + assert(opts.cache_mode == .none); + return try arena.dupe(u8, path); + }, + } + } + }; }; fn addModuleTableToCacheHash( @@ -1869,13 +1920,18 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil cache.hash.add(options.config.link_libunwind); cache.hash.add(output_mode); cache_helpers.addDebugFormat(&cache.hash, options.config.debug_format); - cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_bin); - cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_implib); - cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_docs); cache.hash.addBytes(options.root_name); cache.hash.add(options.config.wasi_exec_model); cache.hash.add(options.config.san_cov_trace_pc_guard); cache.hash.add(options.debug_compiler_runtime_libs); + // The actual emit paths don't matter. They're only user-specified if we aren't using the + // cache! However, it does matter whether the files are emitted at all. + cache.hash.add(options.emit_bin != .no); + cache.hash.add(options.emit_asm != .no); + cache.hash.add(options.emit_implib != .no); + cache.hash.add(options.emit_llvm_ir != .no); + cache.hash.add(options.emit_llvm_bc != .no); + cache.hash.add(options.emit_docs != .no); // TODO audit this and make sure everything is in it const main_mod = options.main_mod orelse options.root_mod; @@ -1925,7 +1981,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil try zcu.init(options.thread_pool.getIdCount()); break :blk zcu; } else blk: { - if (options.emit_h != null) return error.NoZigModuleForCHeader; + if (options.emit_h != .no) return error.NoZigModuleForCHeader; break :blk null; }; errdefer if (opt_zcu) |zcu| zcu.deinit(); @@ -1938,18 +1994,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .arena = arena, .zcu = opt_zcu, .cache_use = undefined, // populated below - .bin_file = null, // populated below - .implib_emit = null, // handled below - .docs_emit = null, // handled below + .bin_file = null, // populated below if necessary .root_mod = options.root_mod, .config = options.config, .dirs = options.dirs, - .emit_asm = options.emit_asm, - .emit_llvm_ir = options.emit_llvm_ir, - .emit_llvm_bc = options.emit_llvm_bc, .work_queues = @splat(.init(gpa)), - .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa), - .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{}, + .c_object_work_queue = .init(gpa), + .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) .init(gpa) else .{}, .c_source_files = options.c_source_files, .rc_source_files = options.rc_source_files, .cache_parent = cache, @@ -2002,6 +2053,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .file_system_inputs = options.file_system_inputs, .parent_whole_cache = options.parent_whole_cache, .link_diags = .init(gpa), + .emit_bin = try options.emit_bin.resolve(arena, &options, .bin), + .emit_asm = try options.emit_asm.resolve(arena, &options, .@"asm"), + .emit_implib = try options.emit_implib.resolve(arena, &options, .implib), + .emit_llvm_ir = try options.emit_llvm_ir.resolve(arena, &options, .llvm_ir), + .emit_llvm_bc = try options.emit_llvm_bc.resolve(arena, &options, .llvm_bc), + .emit_docs = try options.emit_docs.resolve(arena, &options, .docs), }; // Prevent some footguns by making the "any" fields of config reflect @@ -2068,7 +2125,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .soname = options.soname, .compatibility_version = options.compatibility_version, .build_id = build_id, - .disable_lld_caching = options.disable_lld_caching or options.cache_mode == .whole, .subsystem = options.subsystem, .hash_style = options.hash_style, .enable_link_snapshots = options.enable_link_snapshots, @@ -2087,6 +2143,17 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; switch (options.cache_mode) { + .none => { + const none = try arena.create(CacheUse.None); + none.* = .{ .tmp_artifact_directory = null }; + comp.cache_use = .{ .none = none }; + if (comp.emit_bin) |path| { + comp.bin_file = try link.File.open(arena, comp, .{ + .root_dir = .cwd(), + .sub_path = path, + }, lf_open_opts); + } + }, .incremental => { // Options that are specific to zig source files, that cannot be // modified between incremental updates. @@ -2100,7 +2167,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil hash.addListOfBytes(options.test_filters); hash.addOptionalBytes(options.test_name_prefix); hash.add(options.skip_linker_dependencies); - hash.add(options.emit_h != null); + hash.add(options.emit_h != .no); hash.add(error_limit); // Here we put the root source file path name, but *not* with addFile. @@ -2135,49 +2202,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; comp.cache_use = .{ .incremental = incremental }; - if (options.emit_bin) |emit_bin| { + if (comp.emit_bin) |cache_rel_path| { const emit: Cache.Path = .{ - .root_dir = emit_bin.directory orelse artifact_directory, - .sub_path = emit_bin.basename, + .root_dir = artifact_directory, + .sub_path = cache_rel_path, }; comp.bin_file = try link.File.open(arena, comp, emit, lf_open_opts); } - - if (options.emit_implib) |emit_implib| { - comp.implib_emit = .{ - .root_dir = emit_implib.directory orelse artifact_directory, - .sub_path = emit_implib.basename, - }; - } - - if (options.emit_docs) |emit_docs| { - comp.docs_emit = .{ - .root_dir = emit_docs.directory orelse artifact_directory, - .sub_path = emit_docs.basename, - }; - } }, .whole => { - // For whole cache mode, we don't know where to put outputs from - // the linker until the final cache hash, which is available after - // the compilation is complete. + // For whole cache mode, we don't know where to put outputs from the linker until + // the final cache hash, which is available after the compilation is complete. // - // Therefore, bin_file is left null until the beginning of update(), - // where it may find a cache hit, or use a temporary directory to - // hold output artifacts. + // Therefore, `comp.bin_file` is left `null` (already done) until `update`, where + // it may find a cache hit, or else will use a temporary directory to hold output + // artifacts. const whole = try arena.create(CacheUse.Whole); whole.* = .{ - // This is kept here so that link.File.open can be called later. .lf_open_opts = lf_open_opts, - // This is so that when doing `CacheMode.whole`, the mechanism in update() - // can use it for communicating the result directory via `bin_file.emit`. - // This is used to distinguish between -fno-emit-bin and -femit-bin - // for `CacheMode.whole`. - // This memory will be overwritten with the real digest in update() but - // the basename will be preserved. - .bin_sub_path = try prepareWholeEmitSubPath(arena, options.emit_bin), - .implib_sub_path = try prepareWholeEmitSubPath(arena, options.emit_implib), - .docs_sub_path = try prepareWholeEmitSubPath(arena, options.emit_docs), + .cache_manifest = null, + .cache_manifest_mutex = .{}, .tmp_artifact_directory = null, .lock = null, }; @@ -2245,12 +2289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil } } - const have_bin_emit = switch (comp.cache_use) { - .whole => |whole| whole.bin_sub_path != null, - .incremental => comp.bin_file != null, - }; - - if (have_bin_emit and target.ofmt != .c) { + if (comp.emit_bin != null and target.ofmt != .c) { if (!comp.skip_linker_dependencies) { // If we need to build libc for the target, add work items for it. // We go through the work queue so that building can be done in parallel. @@ -2544,8 +2583,23 @@ pub fn hotCodeSwap( try lf.makeExecutable(); } -fn cleanupAfterUpdate(comp: *Compilation) void { +fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { switch (comp.cache_use) { + .none => |none| { + if (none.tmp_artifact_directory) |*tmp_dir| { + tmp_dir.handle.close(); + none.tmp_artifact_directory = null; + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ + comp.dirs.local_cache.path orelse ".", + std.fs.path.sep, + tmp_dir_sub_path, + @errorName(err), + }); + }; + } + }, .incremental => return, .whole => |whole| { if (whole.cache_manifest) |man| { @@ -2556,10 +2610,18 @@ fn cleanupAfterUpdate(comp: *Compilation) void { lf.destroy(); comp.bin_file = null; } - if (whole.tmp_artifact_directory) |*directory| { - directory.handle.close(); - if (directory.path) |p| comp.gpa.free(p); + if (whole.tmp_artifact_directory) |*tmp_dir| { + tmp_dir.handle.close(); whole.tmp_artifact_directory = null; + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { + log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ + comp.dirs.local_cache.path orelse ".", + std.fs.path.sep, + tmp_dir_sub_path, + @errorName(err), + }); + }; } }, } @@ -2579,14 +2641,27 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { comp.clearMiscFailures(); comp.last_update_was_cache_hit = false; - var man: Cache.Manifest = undefined; - defer cleanupAfterUpdate(comp); - var tmp_dir_rand_int: u64 = undefined; + var man: Cache.Manifest = undefined; + defer cleanupAfterUpdate(comp, tmp_dir_rand_int); // If using the whole caching strategy, we check for *everything* up front, including // C source files. + log.debug("Compilation.update for {s}, CacheMode.{s}", .{ comp.root_name, @tagName(comp.cache_use) }); switch (comp.cache_use) { + .none => |none| { + assert(none.tmp_artifact_directory == null); + none.tmp_artifact_directory = d: { + tmp_dir_rand_int = std.crypto.random.int(u64); + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); + break :d .{ + .path = path, + .handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}), + }; + }; + }, + .incremental => {}, .whole => |whole| { assert(comp.bin_file == null); // We are about to obtain this lock, so here we give other processes a chance first. @@ -2633,10 +2708,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { comp.last_update_was_cache_hit = true; log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name}); const bin_digest = man.finalBin(); - const hex_digest = Cache.binToHex(bin_digest); comp.digest = bin_digest; - comp.wholeCacheModeSetBinFilePath(whole, &hex_digest); assert(whole.lock == null); whole.lock = man.toOwnedLock(); @@ -2645,52 +2718,23 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name}); // Compile the artifacts to a temporary directory. - const tmp_artifact_directory: Cache.Directory = d: { - const s = std.fs.path.sep_str; + whole.tmp_artifact_directory = d: { tmp_dir_rand_int = std.crypto.random.int(u64); - const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); - - const path = try comp.dirs.local_cache.join(gpa, &.{tmp_dir_sub_path}); - errdefer gpa.free(path); - - const handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); - errdefer handle.close(); - + const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); break :d .{ .path = path, - .handle = handle, + .handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}), }; }; - whole.tmp_artifact_directory = tmp_artifact_directory; - - // Now that the directory is known, it is time to create the Emit - // objects and call link.File.open. - - if (whole.implib_sub_path) |sub_path| { - comp.implib_emit = .{ - .root_dir = tmp_artifact_directory, - .sub_path = std.fs.path.basename(sub_path), - }; - } - - if (whole.docs_sub_path) |sub_path| { - comp.docs_emit = .{ - .root_dir = tmp_artifact_directory, - .sub_path = std.fs.path.basename(sub_path), - }; - } - - if (whole.bin_sub_path) |sub_path| { + if (comp.emit_bin) |sub_path| { const emit: Cache.Path = .{ - .root_dir = tmp_artifact_directory, - .sub_path = std.fs.path.basename(sub_path), + .root_dir = whole.tmp_artifact_directory.?, + .sub_path = sub_path, }; comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts); } }, - .incremental => { - log.debug("Compilation.update for {s}, CacheMode.incremental", .{comp.root_name}); - }, } // From this point we add a preliminary set of file system inputs that @@ -2789,11 +2833,18 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { return; } - // Flush below handles -femit-bin but there is still -femit-llvm-ir, - // -femit-llvm-bc, and -femit-asm, in the case of C objects. - comp.emitOthers(); + if (comp.zcu == null and comp.config.output_mode == .Obj and comp.c_object_table.count() == 1) { + // This is `zig build-obj foo.c`. We can emit asm and LLVM IR/bitcode. + const c_obj_path = comp.c_object_table.keys()[0].status.success.object_path; + if (comp.emit_asm) |path| try comp.emitFromCObject(arena, c_obj_path, ".s", path); + if (comp.emit_llvm_ir) |path| try comp.emitFromCObject(arena, c_obj_path, ".ll", path); + if (comp.emit_llvm_bc) |path| try comp.emitFromCObject(arena, c_obj_path, ".bc", path); + } switch (comp.cache_use) { + .none, .incremental => { + try flush(comp, arena, .main, main_progress_node); + }, .whole => |whole| { if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); if (comp.parent_whole_cache) |pwc| { @@ -2805,18 +2856,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { const bin_digest = man.finalBin(); const hex_digest = Cache.binToHex(bin_digest); - // Rename the temporary directory into place. - // Close tmp dir and link.File to avoid open handle during rename. - if (whole.tmp_artifact_directory) |*tmp_directory| { - tmp_directory.handle.close(); - if (tmp_directory.path) |p| gpa.free(p); - whole.tmp_artifact_directory = null; - } else unreachable; - - const s = std.fs.path.sep_str; - const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); - const o_sub_path = "o" ++ s ++ hex_digest; - // Work around windows `AccessDenied` if any files within this // directory are open by closing and reopening the file handles. const need_writable_dance: enum { no, lf_only, lf_and_debug } = w: { @@ -2841,6 +2880,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { break :w .no; }; + // Rename the temporary directory into place. + // Close tmp dir and link.File to avoid open handle during rename. + whole.tmp_artifact_directory.?.handle.close(); + whole.tmp_artifact_directory = null; + const s = std.fs.path.sep_str; + const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); + const o_sub_path = "o" ++ s ++ hex_digest; renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| { return comp.setMiscFailure( .rename_results, @@ -2853,7 +2899,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { ); }; comp.digest = bin_digest; - comp.wholeCacheModeSetBinFilePath(whole, &hex_digest); // The linker flush functions need to know the final output path // for debug info purposes because executable debug info contains @@ -2861,10 +2906,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { if (comp.bin_file) |lf| { lf.emit = .{ .root_dir = comp.dirs.local_cache, - .sub_path = whole.bin_sub_path.?, + .sub_path = try std.fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }), }; - // Has to be after the `wholeCacheModeSetBinFilePath` above. switch (need_writable_dance) { .no => {}, .lf_only => try lf.makeWritable(), @@ -2875,10 +2919,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } - try flush(comp, arena, .{ - .root_dir = comp.dirs.local_cache, - .sub_path = o_sub_path, - }, .main, main_progress_node); + try flush(comp, arena, .main, main_progress_node); // Calling `flush` may have produced errors, in which case the // cache manifest must not be written. @@ -2897,11 +2938,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { assert(whole.lock == null); whole.lock = man.toOwnedLock(); }, - .incremental => |incremental| { - try flush(comp, arena, .{ - .root_dir = incremental.artifact_directory, - }, .main, main_progress_node); - }, } } @@ -2931,10 +2967,47 @@ pub fn appendFileSystemInput(comp: *Compilation, path: Compilation.Path) Allocat fsi.appendSliceAssumeCapacity(path.sub_path); } +fn resolveEmitPath(comp: *Compilation, path: []const u8) Cache.Path { + return .{ + .root_dir = switch (comp.cache_use) { + .none => .cwd(), + .incremental => |i| i.artifact_directory, + .whole => |w| w.tmp_artifact_directory.?, + }, + .sub_path = path, + }; +} +/// Like `resolveEmitPath`, but for calling during `flush`. The returned `Cache.Path` may reference +/// memory from `arena`, and may reference `path` itself. +/// If `kind == .temp`, then the returned path will be in a temporary or cache directory. This is +/// useful for intermediate files, such as the ZCU object file emitted by the LLVM backend. +pub fn resolveEmitPathFlush( + comp: *Compilation, + arena: Allocator, + kind: enum { temp, artifact }, + path: []const u8, +) Allocator.Error!Cache.Path { + switch (comp.cache_use) { + .none => |none| return .{ + .root_dir = switch (kind) { + .temp => none.tmp_artifact_directory.?, + .artifact => .cwd(), + }, + .sub_path = path, + }, + .incremental, .whole => return .{ + .root_dir = comp.dirs.local_cache, + .sub_path = try fs.path.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + path, + }), + }, + } +} fn flush( comp: *Compilation, arena: Allocator, - default_artifact_directory: Cache.Path, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) !void { @@ -2942,19 +3015,32 @@ fn flush( if (zcu.llvm_object) |llvm_object| { // Emit the ZCU object from LLVM now; it's required to flush the output file. // If there's an output file, it wants to decide where the LLVM object goes! - const zcu_obj_emit_loc: ?EmitLoc = if (comp.bin_file) |lf| .{ - .directory = null, - .basename = lf.zcu_object_sub_path.?, - } else null; const sub_prog_node = prog_node.start("LLVM Emit Object", 0); defer sub_prog_node.end(); try llvm_object.emit(.{ .pre_ir_path = comp.verbose_llvm_ir, .pre_bc_path = comp.verbose_llvm_bc, - .bin_path = try resolveEmitLoc(arena, default_artifact_directory, zcu_obj_emit_loc), - .asm_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_asm), - .post_ir_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_ir), - .post_bc_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_bc), + + .bin_path = p: { + const lf = comp.bin_file orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .temp, lf.zcu_object_basename.?); + break :p try p.toStringZ(arena); + }, + .asm_path = p: { + const raw = comp.emit_asm orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .artifact, raw); + break :p try p.toStringZ(arena); + }, + .post_ir_path = p: { + const raw = comp.emit_llvm_ir orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .artifact, raw); + break :p try p.toStringZ(arena); + }, + .post_bc_path = p: { + const raw = comp.emit_llvm_bc orelse break :p null; + const p = try comp.resolveEmitPathFlush(arena, .artifact, raw); + break :p try p.toStringZ(arena); + }, .is_debug = comp.root_mod.optimize_mode == .Debug, .is_small = comp.root_mod.optimize_mode == .ReleaseSmall, @@ -3025,45 +3111,6 @@ fn renameTmpIntoCache( } } -/// Communicate the output binary location to parent Compilations. -fn wholeCacheModeSetBinFilePath( - comp: *Compilation, - whole: *CacheUse.Whole, - digest: *const [Cache.hex_digest_len]u8, -) void { - const digest_start = 2; // "o/[digest]/[basename]" - - if (whole.bin_sub_path) |sub_path| { - @memcpy(sub_path[digest_start..][0..digest.len], digest); - } - - if (whole.implib_sub_path) |sub_path| { - @memcpy(sub_path[digest_start..][0..digest.len], digest); - - comp.implib_emit = .{ - .root_dir = comp.dirs.local_cache, - .sub_path = sub_path, - }; - } - - if (whole.docs_sub_path) |sub_path| { - @memcpy(sub_path[digest_start..][0..digest.len], digest); - - comp.docs_emit = .{ - .root_dir = comp.dirs.local_cache, - .sub_path = sub_path, - }; - } -} - -fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemory}!?[]u8 { - const emit = opt_emit orelse return null; - if (emit.directory != null) return null; - const s = std.fs.path.sep_str; - const format = "o" ++ s ++ ("x" ** Cache.hex_digest_len) ++ s ++ "{s}"; - return try std.fmt.allocPrint(arena, format, .{emit.basename}); -} - /// This is only observed at compile-time and used to emit a compile error /// to remind the programmer to update multiple related pieces of code that /// are in different locations. Bump this number when adding or deleting @@ -3084,7 +3131,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addListOfBytes(comp.test_filters); man.hash.addOptionalBytes(comp.test_name_prefix); man.hash.add(comp.skip_linker_dependencies); - //man.hash.add(zcu.emit_h != null); + //man.hash.add(zcu.emit_h != .no); man.hash.add(zcu.error_limit); } else { cache_helpers.addModule(&man.hash, comp.root_mod); @@ -3130,10 +3177,6 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addListOfBytes(comp.framework_dirs); man.hash.addListOfBytes(comp.windows_libs.keys()); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc); - man.hash.addListOfBytes(comp.global_cc_argv); const opts = comp.cache_use.whole.lf_open_opts; @@ -3211,54 +3254,39 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addOptional(opts.minor_subsystem_version); } -fn emitOthers(comp: *Compilation) void { - if (comp.config.output_mode != .Obj or comp.zcu != null or - comp.c_object_table.count() == 0) - { - return; - } - const obj_path = comp.c_object_table.keys()[0].status.success.object_path; - const ext = std.fs.path.extension(obj_path.sub_path); - const dirname = obj_path.sub_path[0 .. obj_path.sub_path.len - ext.len]; - // This obj path always ends with the object file extension, but if we change the - // extension to .ll, .bc, or .s, then it will be the path to those things. - const outs = [_]struct { - emit: ?EmitLoc, - ext: []const u8, - }{ - .{ .emit = comp.emit_asm, .ext = ".s" }, - .{ .emit = comp.emit_llvm_ir, .ext = ".ll" }, - .{ .emit = comp.emit_llvm_bc, .ext = ".bc" }, +fn emitFromCObject( + comp: *Compilation, + arena: Allocator, + c_obj_path: Cache.Path, + new_ext: []const u8, + unresolved_emit_path: []const u8, +) Allocator.Error!void { + // The dirname and stem (i.e. everything but the extension), of the sub path of the C object. + // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc). + const c_obj_dir_and_stem: []const u8 = p: { + const p = c_obj_path.sub_path; + const ext_len = fs.path.extension(p).len; + break :p p[0 .. p.len - ext_len]; }; - for (outs) |out| { - if (out.emit) |loc| { - if (loc.directory) |directory| { - const src_path = std.fmt.allocPrint(comp.gpa, "{s}{s}", .{ - dirname, out.ext, - }) catch |err| { - log.err("unable to copy {s}{s}: {s}", .{ dirname, out.ext, @errorName(err) }); - continue; - }; - defer comp.gpa.free(src_path); - obj_path.root_dir.handle.copyFile(src_path, directory.handle, loc.basename, .{}) catch |err| { - log.err("unable to copy {s}: {s}", .{ src_path, @errorName(err) }); - }; - } - } - } -} + const src_path: Cache.Path = .{ + .root_dir = c_obj_path.root_dir, + .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ + c_obj_dir_and_stem, + new_ext, + }), + }; + const emit_path = comp.resolveEmitPath(unresolved_emit_path); -fn resolveEmitLoc( - arena: Allocator, - default_artifact_directory: Cache.Path, - opt_loc: ?EmitLoc, -) Allocator.Error!?[*:0]const u8 { - const loc = opt_loc orelse return null; - const slice = if (loc.directory) |directory| - try directory.joinZ(arena, &.{loc.basename}) - else - try default_artifact_directory.joinStringZ(arena, loc.basename); - return slice.ptr; + src_path.root_dir.handle.copyFile( + src_path.sub_path, + emit_path.root_dir.handle, + emit_path.sub_path, + .{}, + ) catch |err| log.err("unable to copy '{}' to '{}': {s}", .{ + src_path, + emit_path, + @errorName(err), + }); } /// Having the file open for writing is problematic as far as executing the @@ -4179,7 +4207,7 @@ fn performAllTheWorkInner( comp.link_task_queue.start(comp); - if (comp.docs_emit != null) { + if (comp.emit_docs != null) { dev.check(.docs_emit); comp.thread_pool.spawnWg(&work_queue_wait_group, workerDocsCopy, .{comp}); work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node }); @@ -4457,7 +4485,7 @@ fn performAllTheWorkInner( }; } }, - .incremental => {}, + .none, .incremental => {}, } if (any_fatal_files or @@ -4721,12 +4749,12 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{}); - const emit = comp.docs_emit.?; - var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { + const docs_path = comp.resolveEmitPath(comp.emit_docs.?); + var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, - "unable to create output directory '{}{s}': {s}", - .{ emit.root_dir, emit.sub_path, @errorName(err) }, + "unable to create output directory '{}': {s}", + .{ docs_path, @errorName(err) }, ); }; defer out_dir.close(); @@ -4745,8 +4773,8 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, - "unable to create '{}{s}/sources.tar': {s}", - .{ emit.root_dir, emit.sub_path, @errorName(err) }, + "unable to create '{}/sources.tar': {s}", + .{ docs_path, @errorName(err) }, ); }; defer tar_file.close(); @@ -4896,11 +4924,6 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .parent = root_mod, }); try root_mod.deps.put(arena, "Walk", walk_mod); - const bin_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = resolved_target.result, - .output_mode = output_mode, - }); const sub_compilation = try Compilation.create(gpa, arena, .{ .dirs = dirs, @@ -4912,10 +4935,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = .{ - .directory = null, // Put it in the cache directory. - .basename = bin_basename, - }, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, @@ -4930,27 +4950,31 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node); - const emit = comp.docs_emit.?; - var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| { + var crt_file = try sub_compilation.toCrtFile(); + defer crt_file.deinit(gpa); + + const docs_bin_file = crt_file.full_object_path; + assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory + + const docs_path = comp.resolveEmitPath(comp.emit_docs.?); + var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| { return comp.lockAndSetMiscFailure( .docs_copy, - "unable to create output directory '{}{s}': {s}", - .{ emit.root_dir, emit.sub_path, @errorName(err) }, + "unable to create output directory '{}': {s}", + .{ docs_path, @errorName(err) }, ); }; defer out_dir.close(); - sub_compilation.dirs.local_cache.handle.copyFile( - sub_compilation.cache_use.whole.bin_sub_path.?, + crt_file.full_object_path.root_dir.handle.copyFile( + crt_file.full_object_path.sub_path, out_dir, "main.wasm", .{}, ) catch |err| { - return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}{s}' to '{}{s}': {s}", .{ - sub_compilation.dirs.local_cache, - sub_compilation.cache_use.whole.bin_sub_path.?, - emit.root_dir, - emit.sub_path, + return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}' to '{}': {s}", .{ + crt_file.full_object_path, + docs_path, @errorName(err), }); }; @@ -5212,7 +5236,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module defer whole.cache_manifest_mutex.unlock(); try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); }, - .incremental => {}, + .incremental, .none => {}, } const bin_digest = man.finalBin(); @@ -5557,9 +5581,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr defer man.deinit(); man.hash.add(comp.clang_preprocessor_mode); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); - cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc); + man.hash.addOptionalBytes(comp.emit_asm); + man.hash.addOptionalBytes(comp.emit_llvm_ir); + man.hash.addOptionalBytes(comp.emit_llvm_bc); try cache_helpers.hashCSource(&man, c_object.src); @@ -5793,7 +5817,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); } }, - .incremental => {}, + .incremental, .none => {}, } } @@ -6037,7 +6061,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 defer whole.cache_manifest_mutex.unlock(); try whole_cache_manifest.addFilePost(dep_file_path); }, - .incremental => {}, + .incremental, .none => {}, } } } @@ -7209,12 +7233,6 @@ fn buildOutputFromZig( .cc_argv = &.{}, .parent = null, }); - const target = comp.getTarget(); - const bin_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - }); const parent_whole_cache: ?ParentWholeCache = switch (comp.cache_use) { .whole => |whole| .{ @@ -7227,7 +7245,7 @@ fn buildOutputFromZig( 3, // global cache is the same }, }, - .incremental => null, + .incremental, .none => null, }; const sub_compilation = try Compilation.create(gpa, arena, .{ @@ -7240,13 +7258,9 @@ fn buildOutputFromZig( .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = .{ - .directory = null, // Put it in the cache directory. - .basename = bin_basename, - }, + .emit_bin = .yes_cache, .function_sections = true, .data_sections = true, - .emit_h = null, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, @@ -7366,13 +7380,9 @@ pub fn build_crt_file( .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = .{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }, + .emit_bin = .yes_cache, .function_sections = options.function_sections orelse false, .data_sections = options.data_sections orelse false, - .emit_h = null, .c_source_files = c_source_files, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, @@ -7444,7 +7454,11 @@ pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile { return .{ .full_object_path = .{ .root_dir = comp.dirs.local_cache, - .sub_path = try comp.gpa.dupe(u8, comp.cache_use.whole.bin_sub_path.?), + .sub_path = try std.fs.path.join(comp.gpa, &.{ + "o", + &Cache.binToHex(comp.digest.?), + comp.emit_bin.?, + }), }, .lock = comp.cache_use.whole.moveLock(), }; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index ffc103310b..5215f787ef 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -2493,7 +2493,7 @@ fn newEmbedFile( cache: { const whole = switch (zcu.comp.cache_use) { .whole => |whole| whole, - .incremental => break :cache, + .incremental, .none => break :cache, }; const man = whole.cache_manifest orelse break :cache; const ip_str = opt_ip_str orelse break :cache; // this will be a compile error @@ -3377,7 +3377,7 @@ pub fn populateTestFunctions( } // The linker thread is not running, so we actually need to dispatch this task directly. - @import("../link.zig").doZcuTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index }); + @import("../link.zig").linkTestFunctionsNav(pt, nav_index); } } diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index d90ba974fc..0d14b6fb47 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -1019,10 +1019,6 @@ fn buildSharedLib( defer tracy.end(); const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }); - const emit_bin = Compilation.EmitLoc{ - .directory = bin_directory, - .basename = basename, - }; const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; @@ -1082,8 +1078,7 @@ fn buildSharedLib( .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index ed5eae377f..cb8dd4b460 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -1185,10 +1185,6 @@ fn buildSharedLib( defer tracy.end(); const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }); - const emit_bin = Compilation.EmitLoc{ - .directory = bin_directory, - .basename = basename, - }; const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; @@ -1248,8 +1244,7 @@ fn buildSharedLib( .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/libs/libcxx.zig b/src/libs/libcxx.zig index eb9f5df855..0009bfe120 100644 --- a/src/libs/libcxx.zig +++ b/src/libs/libcxx.zig @@ -122,17 +122,6 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! const output_mode = .Lib; const link_mode = .static; const target = comp.root_mod.resolved_target.result; - const basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - .link_mode = link_mode, - }); - - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" }); const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" }); @@ -271,8 +260,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .c_source_files = c_source_files.items, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, @@ -327,17 +315,6 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr const output_mode = .Lib; const link_mode = .static; const target = comp.root_mod.resolved_target.result; - const basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - .link_mode = link_mode, - }); - - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" }); const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" }); @@ -467,8 +444,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .root_name = root_name, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .c_source_files = c_source_files.items, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, diff --git a/src/libs/libtsan.zig b/src/libs/libtsan.zig index 0c59d85bc5..f2cd6831f7 100644 --- a/src/libs/libtsan.zig +++ b/src/libs/libtsan.zig @@ -45,11 +45,6 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .link_mode = link_mode, }); - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; - const optimize_mode = comp.compilerRtOptMode(); const strip = comp.compilerRtStrip(); const unwind_tables: std.builtin.UnwindTables = @@ -287,8 +282,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .root_mod = root_mod, .root_name = root_name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .c_source_files = c_source_files.items, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, diff --git a/src/libs/libunwind.zig b/src/libs/libunwind.zig index ccea649c17..711d63ebbc 100644 --- a/src/libs/libunwind.zig +++ b/src/libs/libunwind.zig @@ -31,7 +31,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr const unwind_tables: std.builtin.UnwindTables = if (target.cpu.arch == .x86 and target.os.tag == .windows) .none else .@"async"; const config = Compilation.Config.resolve(.{ - .output_mode = .Lib, + .output_mode = output_mode, .resolved_target = comp.root_mod.resolved_target, .is_test = false, .have_zcu = false, @@ -85,17 +85,6 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr }; const root_name = "unwind"; - const link_mode = .static; - const basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target, - .output_mode = output_mode, - .link_mode = link_mode, - }); - const emit_bin = Compilation.EmitLoc{ - .directory = null, // Put it in the cache directory. - .basename = basename, - }; var c_source_files: [unwind_src_list.len]Compilation.CSourceFile = undefined; for (unwind_src_list, 0..) |unwind_src, i| { var cflags = std.ArrayList([]const u8).init(arena); @@ -160,7 +149,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .main_mod = null, .thread_pool = comp.thread_pool, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, + .emit_bin = .yes_cache, .function_sections = comp.function_sections, .c_source_files = &c_source_files, .verbose_cc = comp.verbose_cc, diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 21aeee98b5..7c4e71c974 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -252,8 +252,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro .thread_pool = comp.thread_pool, .root_name = "c", .libc_installation = comp.libc_installation, - .emit_bin = .{ .directory = null, .basename = "libc.so" }, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 7121c308f5..f19c528d5d 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -684,10 +684,6 @@ fn buildSharedLib( defer tracy.end(); const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover }); - const emit_bin = Compilation.EmitLoc{ - .directory = bin_directory, - .basename = basename, - }; const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 }; const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename; @@ -746,8 +742,7 @@ fn buildSharedLib( .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .verbose_cc = comp.verbose_cc, .verbose_link = comp.verbose_link, .verbose_air = comp.verbose_air, diff --git a/src/link.zig b/src/link.zig index 577d7ba82c..bbd0163d23 100644 --- a/src/link.zig +++ b/src/link.zig @@ -384,9 +384,11 @@ pub const File = struct { emit: Path, file: ?fs.File, - /// When linking with LLD, this linker code will output an object file only at - /// this location, and then this path can be placed on the LLD linker line. - zcu_object_sub_path: ?[]const u8 = null, + /// When using the LLVM backend, the emitted object is written to a file with this name. This + /// object file then becomes a normal link input to LLD or a self-hosted linker. + /// + /// To convert this to an actual path, see `Compilation.resolveEmitPath` (with `kind == .temp`). + zcu_object_basename: ?[]const u8 = null, gc_sections: bool, print_gc_sections: bool, build_id: std.zig.BuildId, @@ -433,7 +435,6 @@ pub const File = struct { export_symbol_names: []const []const u8, global_base: ?u64, build_id: std.zig.BuildId, - disable_lld_caching: bool, hash_style: Lld.Elf.HashStyle, sort_section: ?Lld.Elf.SortSection, major_subsystem_version: ?u16, @@ -1083,7 +1084,7 @@ pub const File = struct { // In this case, an object file is created by the LLVM backend, so // there is no prelink phase. The Zig code is linked as a standard // object along with the others. - if (base.zcu_object_sub_path != null) return; + if (base.zcu_object_basename != null) return; switch (base.tag) { inline .wasm => |tag| { @@ -1496,6 +1497,31 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { }, } } +/// After the main pipeline is done, but before flush, the compilation may need to link one final +/// `Nav` into the binary: the `builtin.test_functions` value. Since the link thread isn't running +/// by then, we expose this function which can be called directly. +pub fn linkTestFunctionsNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) void { + const zcu = pt.zcu; + const comp = zcu.comp; + const diags = &comp.link_diags; + if (zcu.llvm_object) |llvm_object| { + llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + }; + } else if (comp.bin_file) |lf| { + lf.updateNav(pt, nav_index) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + error.CodegenFail => zcu.assertCodegenFailed(nav_index), + error.Overflow, error.RelocationNotByteAligned => { + switch (zcu.codegenFail(nav_index, "unable to codegen: {s}", .{@errorName(err)})) { + error.CodegenFail => return, + error.OutOfMemory => return diags.setAllocFailure(), + } + // Not a retryable failure. + }, + }; + } +} /// Provided by the CLI, processed into `LinkInput` instances at the start of /// the compilation pipeline. diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bb8faf583d..81376c45d8 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -224,21 +224,16 @@ pub fn createEmpty( else => 0x1000, }; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try allocPrint(arena, "{s}.obj", .{emit.sub_path}); - const coff = try arena.create(Coff); coff.* = .{ .base = .{ .tag = .coff, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.obj", .{fs.path.stem(emit.sub_path)}) + else + null, .stack_size = options.stack_size orelse 16777216, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), .print_gc_sections = options.print_gc_sections, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 34e04ad557..498bc734c3 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -249,14 +249,6 @@ pub fn createEmpty( const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic; const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); - var rpath_table: std.StringArrayHashMapUnmanaged(void) = .empty; try rpath_table.entries.resize(arena, options.rpath_list.len); @memcpy(rpath_table.entries.items(.key), options.rpath_list); @@ -268,7 +260,10 @@ pub fn createEmpty( .tag = .elf, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) + else + null, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug and output_mode != .Obj), .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 16777216, @@ -770,17 +765,13 @@ fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void { const gpa = comp.gpa; const diags = &comp.link_diags; - const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{ - .root_dir = self.base.emit.root_dir, - .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, + const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, raw); } else null; if (self.zigObjectPtr()) |zig_object| try zig_object.flush(self, tid); - if (module_obj_path) |path| openParseObjectReportingFailure(self, path); + if (zcu_obj_path) |path| openParseObjectReportingFailure(self, path); switch (comp.config.output_mode) { .Obj => return relocatable.flushObject(self, comp), diff --git a/src/link/Goff.zig b/src/link/Goff.zig index d0c2b8e80b..c222ae029f 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -41,7 +41,7 @@ pub fn createEmpty( .tag = .goff, .comp = comp, .emit = emit, - .zcu_object_sub_path = emit.sub_path, + .zcu_object_basename = emit.sub_path, .gc_sections = options.gc_sections orelse false, .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 0, diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 3b7b2b6740..dd50bd2a2f 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1,5 +1,4 @@ base: link.File, -disable_caching: bool, ofmt: union(enum) { elf: Elf, coff: Coff, @@ -231,7 +230,7 @@ pub fn createEmpty( .tag = .lld, .comp = comp, .emit = emit, - .zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }), + .zcu_object_basename = try allocPrint(arena, "{s}_zcu.{s}", .{ fs.path.stem(emit.sub_path), obj_file_ext }), .gc_sections = gc_sections, .print_gc_sections = options.print_gc_sections, .stack_size = stack_size, @@ -239,7 +238,6 @@ pub fn createEmpty( .file = null, .build_id = options.build_id, }, - .disable_caching = options.disable_lld_caching, .ofmt = switch (target.ofmt) { .coff => .{ .coff = try .init(comp, options) }, .elf => .{ .elf = try .init(comp, options) }, @@ -289,14 +287,11 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { const full_out_path_z = try arena.dupeZ(u8, full_out_path); const opt_zcu = comp.zcu; - // If there is no Zig code to compile, then we should skip flushing the output file - // because it will not be part of the linker line anyway. - const zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: { - const dirname = fs.path.dirname(full_out_path_z) orelse "."; - break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); + const zcu_obj_path: ?Cache.Path = if (opt_zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; - log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"}); + log.debug("zcu_obj_path={?}", .{zcu_obj_path}); const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj) comp.compiler_rt_obj.?.full_object_path @@ -330,7 +325,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { for (comp.win32_resource_table.keys()) |key| { object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path)); } - if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p)); + if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); @@ -368,14 +363,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else { - break :p base.zcu_object_sub_path.?; - } + const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; const is_lib = comp.config.output_mode == .Lib; @@ -402,8 +391,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; - if (module_obj_path) |p| - break :blk Cache.Path.initCwd(p); + if (zcu_obj_path) |p| + break :blk p; // TODO I think this is unreachable. Audit this situation when solving the above TODO // regarding eliding redundant object -> object transformations. @@ -513,9 +502,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - if (comp.implib_emit) |emit| { - const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + if (comp.emit_implib) |raw_emit_path| { + const path = try comp.resolveEmitPathFlush(arena, .temp, raw_emit_path); + try argv.append(try allocPrint(arena, "-IMPLIB:{}", .{path})); } if (comp.config.link_libc) { @@ -556,8 +545,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { try argv.append(key.status.success.res_path); } - if (module_obj_path) |p| { - try argv.append(p); + if (zcu_obj_path) |p| { + try argv.append(try p.toString(arena)); } if (coff.module_definition_file) |def| { @@ -808,14 +797,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else { - break :p base.zcu_object_sub_path.?; - } + const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; const output_mode = comp.config.output_mode; @@ -862,8 +845,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; - if (module_obj_path) |p| - break :blk Cache.Path.initCwd(p); + if (zcu_obj_path) |p| + break :blk p; // TODO I think this is unreachable. Audit this situation when solving the above TODO // regarding eliding redundant object -> object transformations. @@ -1151,8 +1134,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { - try argv.append(p); + if (zcu_obj_path) |p| { + try argv.append(try p.toString(arena)); } if (comp.tsan_lib) |lib| { @@ -1387,14 +1370,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { const directory = base.emit.root_dir; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (comp.zcu != null) p: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? }); - } else { - break :p base.zcu_object_sub_path.?; - } + const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?); } else null; const is_obj = comp.config.output_mode == .Obj; @@ -1419,8 +1396,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { if (comp.c_object_table.count() != 0) break :blk comp.c_object_table.keys()[0].status.success.object_path; - if (module_obj_path) |p| - break :blk Cache.Path.initCwd(p); + if (zcu_obj_path) |p| + break :blk p; // TODO I think this is unreachable. Audit this situation when solving the above TODO // regarding eliding redundant object -> object transformations. @@ -1610,8 +1587,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { - try argv.append(p); + if (zcu_obj_path) |p| { + try argv.append(try p.toString(arena)); } if (compiler_rt_path) |p| { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8fd85df0a3..6c081653ea 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -173,13 +173,6 @@ pub fn createEmpty( const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); const allow_shlib_undefined = options.allow_shlib_undefined orelse false; const self = try arena.create(MachO); @@ -188,7 +181,10 @@ pub fn createEmpty( .tag = .macho, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) + else + null, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 16777216, @@ -351,21 +347,16 @@ pub fn flush( const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); - const directory = self.base.emit.root_dir; - const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{ - .root_dir = directory, - .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, + const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: { + break :p try comp.resolveEmitPathFlush(arena, .temp, raw); } else null; // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); if (self.getZigObject()) |zo| try zo.flush(self, tid); - if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); - if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); + if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path); + if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path); var positionals = std.ArrayList(link.Input).init(gpa); defer positionals.deinit(); @@ -387,7 +378,7 @@ pub fn flush( positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path)); } - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); + if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); if (comp.config.any_sanitize_thread) { try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path)); @@ -636,12 +627,9 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { const directory = self.base.emit.root_dir; const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); - const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, path }); - } else { - break :blk path; - } + const zcu_obj_path: ?[]const u8 = if (self.base.zcu_object_basename) |raw| p: { + const p = try comp.resolveEmitPathFlush(arena, .temp, raw); + break :p try p.toString(arena); } else null; var argv = std.ArrayList([]const u8).init(arena); @@ -670,7 +658,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { + if (zcu_obj_path) |p| { try argv.append(p); } } else { @@ -762,7 +750,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append(try key.status.success.object_path.toString(arena)); } - if (module_obj_path) |p| { + if (zcu_obj_path) |p| { try argv.append(p); } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 67e530b5cc..82293b9c45 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2951,21 +2951,16 @@ pub fn createEmpty( const output_mode = comp.config.output_mode; const wasi_exec_model = comp.config.wasi_exec_model; - // If using LLVM to generate the object file for the zig compilation unit, - // we need a place to put the object file so that it can be subsequently - // handled. - const zcu_object_sub_path = if (!use_llvm) - null - else - try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); - const wasm = try arena.create(Wasm); wasm.* = .{ .base = .{ .tag = .wasm, .comp = comp, .emit = emit, - .zcu_object_sub_path = zcu_object_sub_path, + .zcu_object_basename = if (use_llvm) + try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) + else + null, // Garbage collection is so crucial to WebAssembly that we design // the linker around the assumption that it will be on in the vast // majority of cases, and therefore express "no garbage collection" @@ -3834,15 +3829,9 @@ pub fn flush( if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items); - if (wasm.base.zcu_object_sub_path) |path| { - const module_obj_path: Path = .{ - .root_dir = wasm.base.emit.root_dir, - .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname| - try fs.path.join(arena, &.{ dirname, path }) - else - path, - }; - openParseObjectReportingFailure(wasm, module_obj_path); + if (wasm.base.zcu_object_basename) |raw| { + const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw); + openParseObjectReportingFailure(wasm, zcu_obj_path); try prelink(wasm, prog_node); } diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index 97ea300ed2..93fda27f3f 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -41,7 +41,7 @@ pub fn createEmpty( .tag = .xcoff, .comp = comp, .emit = emit, - .zcu_object_sub_path = emit.sub_path, + .zcu_object_basename = emit.sub_path, .gc_sections = options.gc_sections orelse false, .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 0, diff --git a/src/main.zig b/src/main.zig index f7ad35d7cd..dc1d66381b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -699,55 +699,21 @@ const Emit = union(enum) { yes_default_path, yes: []const u8, - const Resolved = struct { - data: ?Compilation.EmitLoc, - dir: ?fs.Dir, - - fn deinit(self: *Resolved) void { - if (self.dir) |*dir| { - dir.close(); - } - } - }; - - fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: bool) !Resolved { - var resolved: Resolved = .{ .data = null, .dir = null }; - errdefer resolved.deinit(); - - switch (emit) { - .no => {}, - .yes_default_path => { - resolved.data = Compilation.EmitLoc{ - .directory = if (output_to_cache) null else .{ - .path = null, - .handle = fs.cwd(), - }, - .basename = default_basename, - }; - }, - .yes => |full_path| { - const basename = fs.path.basename(full_path); - if (fs.path.dirname(full_path)) |dirname| { - const handle = try fs.cwd().openDir(dirname, .{}); - resolved = .{ - .dir = handle, - .data = Compilation.EmitLoc{ - .basename = basename, - .directory = .{ - .path = dirname, - .handle = handle, - }, - }, - }; - } else { - resolved.data = Compilation.EmitLoc{ - .basename = basename, - .directory = .{ .path = null, .handle = fs.cwd() }, - }; + const OutputToCacheReason = enum { listen, @"zig run", @"zig test" }; + fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit { + return switch (emit) { + .no => .no, + .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename }, + .yes => |path| if (output_to_cache) |reason| { + switch (reason) { + .listen => fatal("--listen incompatible with explicit output path '{s}'", .{path}), + .@"zig run", .@"zig test" => fatal( + "'{s}' with explicit output path '{s}' requires explicit '-femit-bin=path' or '-fno-emit-bin'", + .{ @tagName(reason), path }, + ), } - }, - } - return resolved; + } else .{ .yes_path = path }, + }; } }; @@ -2830,7 +2796,7 @@ fn buildOutputType( .link => { create_module.opts.output_mode = if (is_shared_lib) .Lib else .Exe; if (emit_bin != .no) { - emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out; + emit_bin = if (out_path) |p| .{ .yes = p } else .yes_a_out; } if (emit_llvm) { fatal("-emit-llvm cannot be used when linking", .{}); @@ -3208,7 +3174,17 @@ fn buildOutputType( var cleanup_emit_bin_dir: ?fs.Dir = null; defer if (cleanup_emit_bin_dir) |*dir| dir.close(); - const output_to_cache = listen != .none; + // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if + // the binary is requested with no explicit path (as is the default), we emit to the cache. + const output_to_cache: ?Emit.OutputToCacheReason = switch (listen) { + .stdio, .ip4 => .listen, + .none => if (arg_mode == .run and emit_bin == .yes_default_path) + .@"zig run" + else if (arg_mode == .zig_test and emit_bin == .yes_default_path) + .@"zig test" + else + null, + }; const optional_version = if (have_version) version else null; const root_name = if (provided_name) |n| n else main_mod.fully_qualified_name; @@ -3225,150 +3201,48 @@ fn buildOutputType( }, }; - const a_out_basename = switch (target.ofmt) { - .coff => "a.exe", - else => "a.out", - }; - - const emit_bin_loc: ?Compilation.EmitLoc = switch (emit_bin) { - .no => null, - .yes_default_path => Compilation.EmitLoc{ - .directory = blk: { - switch (arg_mode) { - .run, .zig_test => break :blk null, - .build, .cc, .cpp, .translate_c, .zig_test_obj => { - if (output_to_cache) { - break :blk null; - } else { - break :blk .{ .path = null, .handle = fs.cwd() }; - } - }, - } - }, - .basename = if (clang_preprocessor_mode == .pch) - try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}) - else - try std.zig.binNameAlloc(arena, .{ + const emit_bin_resolved: Compilation.CreateOptions.Emit = switch (emit_bin) { + .no => .no, + .yes_default_path => emit: { + if (output_to_cache != null) break :emit .yes_cache; + const name = switch (clang_preprocessor_mode) { + .pch => try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}), + else => try std.zig.binNameAlloc(arena, .{ .root_name = root_name, .target = target, .output_mode = create_module.resolved_options.output_mode, .link_mode = create_module.resolved_options.link_mode, .version = optional_version, }), + }; + break :emit .{ .yes_path = name }; }, - .yes => |full_path| b: { - const basename = fs.path.basename(full_path); - if (fs.path.dirname(full_path)) |dirname| { - const handle = fs.cwd().openDir(dirname, .{}) catch |err| { - fatal("unable to open output directory '{s}': {s}", .{ dirname, @errorName(err) }); - }; - cleanup_emit_bin_dir = handle; - break :b Compilation.EmitLoc{ - .basename = basename, - .directory = .{ - .path = dirname, - .handle = handle, - }, - }; - } else { - break :b Compilation.EmitLoc{ - .basename = basename, - .directory = .{ .path = null, .handle = fs.cwd() }, - }; - } - }, - .yes_a_out => Compilation.EmitLoc{ - .directory = .{ .path = null, .handle = fs.cwd() }, - .basename = a_out_basename, + .yes => |path| if (output_to_cache != null) { + assert(output_to_cache == .listen); // there was an explicit bin path + fatal("--listen incompatible with explicit output path '{s}'", .{path}); + } else .{ .yes_path = path }, + .yes_a_out => emit: { + assert(output_to_cache == null); + break :emit .{ .yes_path = switch (target.ofmt) { + .coff => "a.exe", + else => "a.out", + } }; }, }; const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name}); - var emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache) catch |err| { - switch (emit_h) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_h_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_h_resolved.deinit(); + const emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache); const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name}); - var emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache) catch |err| { - switch (emit_asm) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_asm_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_asm_resolved.deinit(); + const emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache); const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name}); - var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache) catch |err| { - switch (emit_llvm_ir) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_llvm_ir_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_llvm_ir_resolved.deinit(); + const emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache); const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name}); - var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache) catch |err| { - switch (emit_llvm_bc) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ - default_llvm_bc_basename, @errorName(err), - }); - }, - .no => unreachable, - } - }; - defer emit_llvm_bc_resolved.deinit(); + const emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache); - var emit_docs_resolved = emit_docs.resolve("docs", output_to_cache) catch |err| { - switch (emit_docs) { - .yes => |p| { - fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{ - p, @errorName(err), - }); - }, - .yes_default_path => { - fatal("unable to open directory 'docs': {s}", .{@errorName(err)}); - }, - .no => unreachable, - } - }; - defer emit_docs_resolved.deinit(); + const emit_docs_resolved = emit_docs.resolve("docs", output_to_cache); const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) { .Obj => false, @@ -3378,7 +3252,7 @@ fn buildOutputType( // Note that cmake when targeting Windows will try to execute // zig cc to make an executable and output an implib too. const implib_eligible = is_exe_or_dyn_lib and - emit_bin_loc != null and target.os.tag == .windows; + emit_bin_resolved != .no and target.os.tag == .windows; if (!implib_eligible) { if (!emit_implib_arg_provided) { emit_implib = .no; @@ -3387,22 +3261,18 @@ fn buildOutputType( } } const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name}); - var emit_implib_resolved = switch (emit_implib) { - .no => Emit.Resolved{ .data = null, .dir = null }, - .yes => |p| emit_implib.resolve(default_implib_basename, output_to_cache) catch |err| { - fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{ - p, @errorName(err), + const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) { + .no => .no, + .yes => emit_implib.resolve(default_implib_basename, output_to_cache), + .yes_default_path => emit: { + if (output_to_cache != null) break :emit .yes_cache; + const p = try fs.path.join(arena, &.{ + fs.path.dirname(emit_bin_resolved.yes_path) orelse ".", + default_implib_basename, }); - }, - .yes_default_path => Emit.Resolved{ - .data = Compilation.EmitLoc{ - .directory = emit_bin_loc.?.directory, - .basename = default_implib_basename, - }, - .dir = null, + break :emit .{ .yes_path = p }; }, }; - defer emit_implib_resolved.deinit(); var thread_pool: ThreadPool = undefined; try thread_pool.init(.{ @@ -3456,7 +3326,7 @@ fn buildOutputType( src.src_path = try dirs.local_cache.join(arena, &.{sub_path}); } - if (build_options.have_llvm and emit_asm != .no) { + if (build_options.have_llvm and emit_asm_resolved != .no) { // LLVM has no way to set this non-globally. const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" }; @import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv); @@ -3472,23 +3342,11 @@ fn buildOutputType( fatal("--debug-incremental requires -fincremental", .{}); } - const disable_lld_caching = !output_to_cache; - const cache_mode: Compilation.CacheMode = b: { + // Once incremental compilation is the default, we'll want some smarter logic here, + // considering things like the backend in use and whether there's a ZCU. + if (output_to_cache == null) break :b .none; if (incremental) break :b .incremental; - if (disable_lld_caching) break :b .incremental; - if (!create_module.resolved_options.have_zcu) break :b .whole; - - // TODO: once we support incremental compilation for the LLVM backend - // via saving the LLVM module into a bitcode file and restoring it, - // along with compiler state, this clause can be removed so that - // incremental cache mode is used for LLVM backend too. - if (create_module.resolved_options.use_llvm) break :b .whole; - - // Eventually, this default should be `.incremental`. However, since incremental - // compilation is currently an opt-in feature, it makes a strictly worse default cache mode - // than `.whole`. - // https://github.com/ziglang/zig/issues/21165 break :b .whole; }; @@ -3510,13 +3368,13 @@ fn buildOutputType( .main_mod = main_mod, .root_mod = root_mod, .std_mod = std_mod, - .emit_bin = emit_bin_loc, - .emit_h = emit_h_resolved.data, - .emit_asm = emit_asm_resolved.data, - .emit_llvm_ir = emit_llvm_ir_resolved.data, - .emit_llvm_bc = emit_llvm_bc_resolved.data, - .emit_docs = emit_docs_resolved.data, - .emit_implib = emit_implib_resolved.data, + .emit_bin = emit_bin_resolved, + .emit_h = emit_h_resolved, + .emit_asm = emit_asm_resolved, + .emit_llvm_ir = emit_llvm_ir_resolved, + .emit_llvm_bc = emit_llvm_bc_resolved, + .emit_docs = emit_docs_resolved, + .emit_implib = emit_implib_resolved, .lib_directories = create_module.lib_directories.items, .rpath_list = create_module.rpath_list.items, .symbol_wrap_set = symbol_wrap_set, @@ -3599,7 +3457,6 @@ fn buildOutputType( .test_filters = test_filters.items, .test_name_prefix = test_name_prefix, .test_runner_path = test_runner_path, - .disable_lld_caching = disable_lld_caching, .cache_mode = cache_mode, .subsystem = subsystem, .debug_compile_errors = debug_compile_errors, @@ -3744,13 +3601,8 @@ fn buildOutputType( }) { dev.checkAny(&.{ .run_command, .test_command }); - if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: { + if (test_exec_args.items.len == 0 and target.ofmt == .c and emit_bin_resolved != .no) { // Default to using `zig run` to execute the produced .c code from `zig test`. - const c_code_loc = emit_bin_loc orelse break :default_exec_args; - const c_code_directory = c_code_loc.directory orelse comp.bin_file.?.emit.root_dir; - const c_code_path = try fs.path.join(arena, &[_][]const u8{ - c_code_directory.path orelse ".", c_code_loc.basename, - }); try test_exec_args.appendSlice(arena, &.{ self_exe_path, "run" }); if (dirs.zig_lib.path) |p| { try test_exec_args.appendSlice(arena, &.{ "-I", p }); @@ -3775,7 +3627,7 @@ fn buildOutputType( if (create_module.dynamic_linker) |dl| { try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl }); } - try test_exec_args.append(arena, c_code_path); + try test_exec_args.append(arena, null); // placeholder for the path of the emitted C source file } try runOrTest( @@ -4354,12 +4206,22 @@ fn runOrTest( runtime_args_start: ?usize, link_libc: bool, ) !void { - const lf = comp.bin_file orelse return; - // A naive `directory.join` here will indeed get the correct path to the binary, - // however, in the case of cwd, we actually want `./foo` so that the path can be executed. - const exe_path = try fs.path.join(arena, &[_][]const u8{ - lf.emit.root_dir.path orelse ".", lf.emit.sub_path, - }); + const raw_emit_bin = comp.emit_bin orelse return; + const exe_path = switch (comp.cache_use) { + .none => p: { + if (fs.path.isAbsolute(raw_emit_bin)) break :p raw_emit_bin; + // Use `fs.path.join` to make a file in the cwd is still executed properly. + break :p try fs.path.join(arena, &.{ + ".", + raw_emit_bin, + }); + }, + .whole, .incremental => try comp.dirs.local_cache.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + raw_emit_bin, + }), + }; var argv = std.ArrayList([]const u8).init(gpa); defer argv.deinit(); @@ -5087,16 +4949,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { }; }; - const exe_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = "build", - .target = resolved_target.result, - .output_mode = .Exe, - }); - const emit_bin: Compilation.EmitLoc = .{ - .directory = null, // Use the local zig-cache. - .basename = exe_basename, - }; - process.raiseFileDescriptorLimit(); const cwd_path = try introspect.getResolvedCwd(arena); @@ -5357,8 +5209,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { .config = config, .root_mod = root_mod, .main_mod = build_mod, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .self_exe_path = self_exe_path, .thread_pool = &thread_pool, .verbose_cc = verbose_cc, @@ -5386,8 +5237,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { // Since incremental compilation isn't done yet, we use cache_mode = whole // above, and thus the output file is already closed. //try comp.makeBinFileExecutable(); - child_argv.items[argv_index_exe] = - try dirs.local_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?}); + child_argv.items[argv_index_exe] = try dirs.local_cache.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + comp.emit_bin.?, + }); } if (process.can_spawn) { @@ -5504,16 +5358,6 @@ fn jitCmd( .is_explicit_dynamic_linker = false, }; - const exe_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = options.cmd_name, - .target = resolved_target.result, - .output_mode = .Exe, - }); - const emit_bin: Compilation.EmitLoc = .{ - .directory = null, // Use the global zig-cache. - .basename = exe_basename, - }; - const self_exe_path = fs.selfExePathAlloc(arena) catch |err| { fatal("unable to find self exe path: {s}", .{@errorName(err)}); }; @@ -5605,8 +5449,7 @@ fn jitCmd( .config = config, .root_mod = root_mod, .main_mod = root_mod, - .emit_bin = emit_bin, - .emit_h = null, + .emit_bin = .yes_cache, .self_exe_path = self_exe_path, .thread_pool = &thread_pool, .cache_mode = .whole, @@ -5637,7 +5480,11 @@ fn jitCmd( }; } - const exe_path = try dirs.global_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?}); + const exe_path = try dirs.global_cache.join(arena, &.{ + "o", + &Cache.binToHex(comp.digest.?), + comp.emit_bin.?, + }); child_argv.appendAssumeCapacity(exe_path); } diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 6e69b93b96..6c048f7a87 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -314,7 +314,7 @@ const Eval = struct { const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len]; const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*); - const bin_name = try std.zig.binNameAlloc(arena, .{ + const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ .root_name = "root", // corresponds to the module name "root" .target = eval.target.resolved, .output_mode = .Exe, -- cgit v1.2.3 From c95b1bf2d3c3ae7595e15ad521952b77d5063801 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 7 Jun 2025 04:51:26 -0400 Subject: x86_64: remove air references from mir --- lib/std/zig/Zir.zig | 9 + src/Air.zig | 4 +- src/Air/print.zig | 5 +- src/Compilation.zig | 3 - src/Sema.zig | 22 +- src/Type.zig | 24 +- src/Zcu/PerThread.zig | 14 +- src/arch/aarch64/CodeGen.zig | 25 +- src/arch/aarch64/Mir.zig | 2 - src/arch/arm/CodeGen.zig | 26 +- src/arch/arm/Mir.zig | 2 - src/arch/riscv64/CodeGen.zig | 22 +- src/arch/riscv64/Mir.zig | 2 - src/arch/sparc64/CodeGen.zig | 30 +-- src/arch/sparc64/Mir.zig | 2 - src/arch/wasm/CodeGen.zig | 4 +- src/arch/x86_64/CodeGen.zig | 564 +++++++++++++++++++++---------------------- src/arch/x86_64/Emit.zig | 190 ++++++++------- src/arch/x86_64/Lower.zig | 38 ++- src/arch/x86_64/Mir.zig | 119 +++++---- src/codegen.zig | 6 +- src/codegen/llvm.zig | 15 +- src/link.zig | 13 +- src/link/C.zig | 6 - src/link/Coff.zig | 5 - src/link/Dwarf.zig | 177 ++++++++++++-- src/link/Elf.zig | 4 +- src/link/Elf/ZigObject.zig | 5 - src/link/Goff.zig | 3 - src/link/MachO.zig | 4 +- src/link/MachO/ZigObject.zig | 5 - src/link/Plan9.zig | 4 - src/link/Wasm.zig | 3 - src/link/Xcoff.zig | 3 - 34 files changed, 744 insertions(+), 616 deletions(-) (limited to 'src/Compilation.zig') diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 440b4df9fc..17643e6f1e 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -4861,6 +4861,15 @@ pub fn getParamBody(zir: Zir, fn_inst: Inst.Index) []const Zir.Inst.Index { } } +pub fn getParamName(zir: Zir, param_inst: Inst.Index) ?NullTerminatedString { + const inst = zir.instructions.get(@intFromEnum(param_inst)); + return switch (inst.tag) { + .param, .param_comptime => zir.extraData(Inst.Param, inst.data.pl_tok.payload_index).data.name, + .param_anytype, .param_anytype_comptime => inst.data.str_tok.start, + else => null, + }; +} + pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); diff --git a/src/Air.zig b/src/Air.zig index 4810766e71..74f4c57424 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1153,9 +1153,7 @@ pub const Inst = struct { ty: Type, arg: struct { ty: Ref, - /// Index into `extra` of a null-terminated string representing the parameter name. - /// This is `.none` if debug info is stripped. - name: NullTerminatedString, + zir_param_index: u32, }, ty_op: struct { ty: Ref, diff --git a/src/Air/print.zig b/src/Air/print.zig index 343c640a63..7f5f396cae 100644 --- a/src/Air/print.zig +++ b/src/Air/print.zig @@ -363,10 +363,7 @@ const Writer = struct { fn writeArg(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const arg = w.air.instructions.items(.data)[@intFromEnum(inst)].arg; try w.writeType(s, arg.ty.toType()); - switch (arg.name) { - .none => {}, - _ => try s.print(", \"{}\"", .{std.zig.fmtEscapes(arg.name.toSlice(w.air))}), - } + try s.print(", {d}", .{arg.zir_param_index}); } fn writeTyOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { diff --git a/src/Compilation.zig b/src/Compilation.zig index b9b51222eb..fe4671848d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4589,10 +4589,8 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ .func = func.func, .mir = shared_mir, - .air = undefined, } }); } else { - const emit_needs_air = !zcu.backendSupportsFeature(.separate_thread); { const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); @@ -4602,7 +4600,6 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ .func = func.func, .mir = shared_mir, - .air = if (emit_needs_air) &air else undefined, } }); air.deinit(gpa); } diff --git a/src/Sema.zig b/src/Sema.zig index 87837d96d5..97c9217a5e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -35088,24 +35088,24 @@ pub fn resolveUnionLayout(sema: *Sema, ty: Type) SemaError!void { var max_align: Alignment = .@"1"; for (0..union_type.field_types.len) |field_index| { const field_ty: Type = .fromInterned(union_type.field_types.get(ip)[field_index]); + if (field_ty.isNoReturn(pt.zcu)) continue; - if (try field_ty.comptimeOnlySema(pt) or field_ty.zigTypeTag(pt.zcu) == .noreturn) continue; // TODO: should this affect alignment? - - max_size = @max(max_size, field_ty.abiSizeSema(pt) catch |err| switch (err) { - error.AnalysisFail => { - const msg = sema.err orelse return err; - try sema.addFieldErrNote(ty, field_index, msg, "while checking this field", .{}); - return err; - }, - else => return err, - }); + if (try field_ty.hasRuntimeBitsSema(pt)) { + max_size = @max(max_size, field_ty.abiSizeSema(pt) catch |err| switch (err) { + error.AnalysisFail => { + const msg = sema.err orelse return err; + try sema.addFieldErrNote(ty, field_index, msg, "while checking this field", .{}); + return err; + }, + else => return err, + }); + } const explicit_align = union_type.fieldAlign(ip, field_index); const field_align = if (explicit_align != .none) explicit_align else try field_ty.abiAlignmentSema(pt); - max_align = max_align.max(field_align); } diff --git a/src/Type.zig b/src/Type.zig index 00f1c70129..64b389cf5f 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -177,6 +177,7 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error const zcu = pt.zcu; const ip = &zcu.intern_pool; switch (ip.indexToKey(ty.toIntern())) { + .undef => return writer.writeAll("@as(type, undefined)"), .int_type => |int_type| { const sign_char: u8 = switch (int_type.signedness) { .signed => 'i', @@ -398,7 +399,6 @@ pub fn print(ty: Type, writer: anytype, pt: Zcu.PerThread) @TypeOf(writer).Error }, // values, not types - .undef, .simple_value, .variable, .@"extern", @@ -3921,23 +3921,25 @@ pub fn getUnionLayout(loaded_union: InternPool.LoadedUnionType, zcu: *const Zcu) var payload_size: u64 = 0; var payload_align: InternPool.Alignment = .@"1"; for (loaded_union.field_types.get(ip), 0..) |field_ty, field_index| { - if (!Type.fromInterned(field_ty).hasRuntimeBitsIgnoreComptime(zcu)) continue; + if (Type.fromInterned(field_ty).isNoReturn(zcu)) continue; const explicit_align = loaded_union.fieldAlign(ip, field_index); const field_align = if (explicit_align != .none) explicit_align else Type.fromInterned(field_ty).abiAlignment(zcu); - const field_size = Type.fromInterned(field_ty).abiSize(zcu); - if (field_size > payload_size) { - payload_size = field_size; - biggest_field = @intCast(field_index); - } - if (field_align.compare(.gte, payload_align)) { - payload_align = field_align; - most_aligned_field = @intCast(field_index); - most_aligned_field_size = field_size; + if (Type.fromInterned(field_ty).hasRuntimeBits(zcu)) { + const field_size = Type.fromInterned(field_ty).abiSize(zcu); + if (field_size > payload_size) { + payload_size = field_size; + biggest_field = @intCast(field_index); + } + if (field_align.compare(.gte, payload_align)) { + most_aligned_field = @intCast(field_index); + most_aligned_field_size = field_size; + } } + payload_align = payload_align.max(field_align); } const have_tag = loaded_union.flagsUnordered(ip).runtime_tag.hasTag(); if (!have_tag or !Type.fromInterned(loaded_union.enum_tag_ty).hasRuntimeBits(zcu)) { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 6f0eeba864..f8efa40dc0 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -2893,17 +2893,10 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE runtime_params_len; var runtime_param_index: usize = 0; - for (fn_info.param_body[0..src_params_len]) |inst| { + for (fn_info.param_body[0..src_params_len], 0..) |inst, zir_param_index| { const gop = sema.inst_map.getOrPutAssumeCapacity(inst); if (gop.found_existing) continue; // provided above by comptime arg - const param_inst_info = sema.code.instructions.get(@intFromEnum(inst)); - const param_name: Zir.NullTerminatedString = switch (param_inst_info.tag) { - .param_anytype => param_inst_info.data.str_tok.start, - .param => sema.code.extraData(Zir.Inst.Param, param_inst_info.data.pl_tok.payload_index).data.name, - else => unreachable, - }; - const param_ty = fn_ty_info.param_types.get(ip)[runtime_param_index]; runtime_param_index += 1; @@ -2923,10 +2916,7 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE .tag = .arg, .data = .{ .arg = .{ .ty = Air.internedToRef(param_ty), - .name = if (inner_block.ownerModule().strip) - .none - else - try sema.appendAirString(sema.code.nullTerminatedString(param_name)), + .zir_param_index = @intCast(zir_param_index), } }, }); } diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 0c29fd96e2..4aaf6bf85c 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -4208,15 +4208,22 @@ fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void { while (self.args[arg_index] == .none) arg_index += 1; self.arg_index = arg_index + 1; - const ty = self.typeOfIndex(inst); - const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; - const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - if (name != .none) try self.dbg_info_relocs.append(self.gpa, .{ - .tag = tag, - .ty = ty, - .name = name.toSlice(self.air), - .mcv = self.args[arg_index], - }); + const zcu = self.pt.zcu; + const func_zir = zcu.funcInfo(self.func_index).zir_body_inst.resolveFull(&zcu.intern_pool).?; + const file = zcu.fileByIndex(func_zir.file); + if (!file.mod.?.strip) { + const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; + const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg; + const ty = self.typeOfIndex(inst); + const zir = &file.zir.?; + const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?); + try self.dbg_info_relocs.append(self.gpa, .{ + .tag = tag, + .ty = ty, + .name = name, + .mcv = self.args[arg_index], + }); + } const result: MCValue = if (self.liveness.isUnused(inst)) .dead else self.args[arg_index]; return self.finishAir(inst, result, .{ .none, .none, .none }); diff --git a/src/arch/aarch64/Mir.zig b/src/arch/aarch64/Mir.zig index 34fcc64c7e..88089c8488 100644 --- a/src/arch/aarch64/Mir.zig +++ b/src/arch/aarch64/Mir.zig @@ -514,9 +514,7 @@ pub fn emit( func_index: InternPool.Index, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, - air: *const @import("../../Air.zig"), ) codegen.CodeGenError!void { - _ = air; // using this would be a bug const zcu = pt.zcu; const func = zcu.funcInfo(func_index); const nav = func.owner_nav; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 3868011557..09304bf1de 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -4191,16 +4191,22 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { while (self.args[arg_index] == .none) arg_index += 1; self.arg_index = arg_index + 1; - const ty = self.typeOfIndex(inst); - const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; - - const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - if (name != .none) try self.dbg_info_relocs.append(self.gpa, .{ - .tag = tag, - .ty = ty, - .name = name.toSlice(self.air), - .mcv = self.args[arg_index], - }); + const zcu = self.pt.zcu; + const func_zir = zcu.funcInfo(self.func_index).zir_body_inst.resolveFull(&zcu.intern_pool).?; + const file = zcu.fileByIndex(func_zir.file); + if (!file.mod.?.strip) { + const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; + const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg; + const ty = self.typeOfIndex(inst); + const zir = &file.zir.?; + const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?); + try self.dbg_info_relocs.append(self.gpa, .{ + .tag = tag, + .ty = ty, + .name = name, + .mcv = self.args[arg_index], + }); + } const result: MCValue = if (self.liveness.isUnused(inst)) .dead else self.args[arg_index]; return self.finishAir(inst, result, .{ .none, .none, .none }); diff --git a/src/arch/arm/Mir.zig b/src/arch/arm/Mir.zig index 0366663eae..5b7585a2ca 100644 --- a/src/arch/arm/Mir.zig +++ b/src/arch/arm/Mir.zig @@ -294,9 +294,7 @@ pub fn emit( func_index: InternPool.Index, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, - air: *const @import("../../Air.zig"), ) codegen.CodeGenError!void { - _ = air; // using this would be a bug const zcu = pt.zcu; const func = zcu.funcInfo(func_index); const nav = func.owner_nav; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 9b5e0ed69b..080760bbab 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -70,6 +70,7 @@ mod: *Package.Module, target: *const std.Target, args: []MCValue, ret_mcv: InstTracking, +func_index: InternPool.Index, fn_type: Type, arg_index: usize, src_loc: Zcu.LazySrcLoc, @@ -774,6 +775,7 @@ pub fn generate( .owner = .{ .nav_index = func.owner_nav }, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` + .func_index = func_index, .fn_type = fn_type, .arg_index = 0, .branch_stack = &branch_stack, @@ -877,6 +879,7 @@ pub fn generateLazy( .owner = .{ .lazy_sym = lazy_sym }, .args = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` + .func_index = undefined, .fn_type = undefined, .arg_index = 0, .branch_stack = undefined, @@ -4724,10 +4727,8 @@ fn airFieldParentPtr(func: *Func, inst: Air.Inst.Index) !void { return func.fail("TODO implement codegen airFieldParentPtr", .{}); } -fn genArgDbgInfo(func: *const Func, inst: Air.Inst.Index, mcv: MCValue) InnerError!void { - const arg = func.air.instructions.items(.data)[@intFromEnum(inst)].arg; - const ty = arg.ty.toType(); - if (arg.name == .none) return; +fn genArgDbgInfo(func: *const Func, name: []const u8, ty: Type, mcv: MCValue) InnerError!void { + assert(!func.mod.strip); // TODO: Add a pseudo-instruction or something to defer this work until Emit. // We aren't allowed to interact with linker state here. @@ -4736,7 +4737,7 @@ fn genArgDbgInfo(func: *const Func, inst: Air.Inst.Index, mcv: MCValue) InnerErr .dwarf => |dw| switch (mcv) { .register => |reg| dw.genLocalDebugInfo( .local_arg, - arg.name.toSlice(func.air), + name, ty, .{ .reg = reg.dwarfNum() }, ) catch |err| return func.fail("failed to generate debug info: {s}", .{@errorName(err)}), @@ -4749,6 +4750,8 @@ fn genArgDbgInfo(func: *const Func, inst: Air.Inst.Index, mcv: MCValue) InnerErr } fn airArg(func: *Func, inst: Air.Inst.Index) InnerError!void { + const zcu = func.pt.zcu; + var arg_index = func.arg_index; // we skip over args that have no bits @@ -4765,7 +4768,14 @@ fn airArg(func: *Func, inst: Air.Inst.Index) InnerError!void { try func.genCopy(arg_ty, dst_mcv, src_mcv); - try func.genArgDbgInfo(inst, src_mcv); + const arg = func.air.instructions.items(.data)[@intFromEnum(inst)].arg; + // can delete `func.func_index` if this logic is moved to emit + const func_zir = zcu.funcInfo(func.func_index).zir_body_inst.resolveFull(&zcu.intern_pool).?; + const file = zcu.fileByIndex(func_zir.file); + const zir = &file.zir.?; + const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?); + + try func.genArgDbgInfo(name, arg_ty, src_mcv); break :result dst_mcv; }; diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig index eef3fe7511..2ad75e4677 100644 --- a/src/arch/riscv64/Mir.zig +++ b/src/arch/riscv64/Mir.zig @@ -117,9 +117,7 @@ pub fn emit( func_index: InternPool.Index, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, - air: *const @import("../../Air.zig"), ) codegen.CodeGenError!void { - _ = air; // using this would be a bug const zcu = pt.zcu; const comp = zcu.comp; const gpa = comp.gpa; diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 180aaedd3c..b35f45dd64 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -995,23 +995,29 @@ fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void { self.arg_index += 1; const ty = self.typeOfIndex(inst); - - const arg = self.args[arg_index]; - const mcv = blk: { - switch (arg) { + const mcv: MCValue = blk: { + switch (self.args[arg_index]) { .stack_offset => |off| { const abi_size = math.cast(u32, ty.abiSize(zcu)) orelse { return self.fail("type '{}' too big to fit into stack frame", .{ty.fmt(pt)}); }; const offset = off + abi_size; - break :blk MCValue{ .stack_offset = offset }; + break :blk .{ .stack_offset = offset }; }, - else => break :blk arg, + else => |mcv| break :blk mcv, } }; - self.genArgDbgInfo(inst, mcv) catch |err| - return self.fail("failed to generate debug info for parameter: {s}", .{@errorName(err)}); + const func_zir = zcu.funcInfo(self.func_index).zir_body_inst.resolveFull(&zcu.intern_pool).?; + const file = zcu.fileByIndex(func_zir.file); + if (!file.mod.?.strip) { + const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg; + const zir = &file.zir.?; + const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?); + + self.genArgDbgInfo(name, ty, mcv) catch |err| + return self.fail("failed to generate debug info for parameter: {s}", .{@errorName(err)}); + } if (self.liveness.isUnused(inst)) return self.finishAirBookkeeping(); @@ -3539,11 +3545,7 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Air. self.finishAirBookkeeping(); } -fn genArgDbgInfo(self: Self, inst: Air.Inst.Index, mcv: MCValue) !void { - const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg; - const ty = arg.ty.toType(); - if (arg.name == .none) return; - +fn genArgDbgInfo(self: Self, name: []const u8, ty: Type, mcv: MCValue) !void { // TODO: Add a pseudo-instruction or something to defer this work until Emit. // We aren't allowed to interact with linker state here. if (true) return; @@ -3551,7 +3553,7 @@ fn genArgDbgInfo(self: Self, inst: Air.Inst.Index, mcv: MCValue) !void { .dwarf => |dw| switch (mcv) { .register => |reg| try dw.genLocalDebugInfo( .local_arg, - arg.name.toSlice(self.air), + name, ty, .{ .reg = reg.dwarfNum() }, ), diff --git a/src/arch/sparc64/Mir.zig b/src/arch/sparc64/Mir.zig index 26c5c3267b..842ac10fed 100644 --- a/src/arch/sparc64/Mir.zig +++ b/src/arch/sparc64/Mir.zig @@ -382,9 +382,7 @@ pub fn emit( func_index: InternPool.Index, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, - air: *const @import("../../Air.zig"), ) codegen.CodeGenError!void { - _ = air; // using this would be a bug const zcu = pt.zcu; const func = zcu.funcInfo(func_index); const nav = func.owner_nav; diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 2993923589..2936025a49 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1877,7 +1877,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .dbg_inline_block => cg.airDbgInlineBlock(inst), .dbg_var_ptr => cg.airDbgVar(inst, .local_var, true), .dbg_var_val => cg.airDbgVar(inst, .local_var, false), - .dbg_arg_inline => cg.airDbgVar(inst, .local_arg, false), + .dbg_arg_inline => cg.airDbgVar(inst, .arg, false), .call => cg.airCall(inst, .auto), .call_always_tail => cg.airCall(inst, .always_tail), @@ -6427,7 +6427,7 @@ fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { fn airDbgVar( cg: *CodeGen, inst: Air.Inst.Index, - local_tag: link.File.Dwarf.WipNav.LocalTag, + local_tag: link.File.Dwarf.WipNav.LocalVarTag, is_ptr: bool, ) InnerError!void { _ = is_ptr; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 1d95c8db77..7d88307ba5 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -129,7 +129,6 @@ target: *const std.Target, owner: Owner, inline_func: InternPool.Index, mod: *Module, -arg_index: u32, args: []MCValue, va_info: union { sysv: struct { @@ -151,6 +150,8 @@ eflags_inst: ?Air.Inst.Index = null, mir_instructions: std.MultiArrayList(Mir.Inst) = .empty, /// MIR extra data mir_extra: std.ArrayListUnmanaged(u32) = .empty, +mir_local_name_bytes: std.ArrayListUnmanaged(u8) = .empty, +mir_local_types: std.ArrayListUnmanaged(InternPool.Index) = .empty, mir_table: std.ArrayListUnmanaged(Mir.Inst.Index) = .empty, /// The value is an offset into the `Function` `code` from the beginning. @@ -978,8 +979,10 @@ pub fn generate( const gpa = zcu.gpa; const ip = &zcu.intern_pool; const func = zcu.funcInfo(func_index); + const func_zir = func.zir_body_inst.resolveFull(ip).?; + const file = zcu.fileByIndex(func_zir.file); const fn_type: Type = .fromInterned(func.ty); - const mod = zcu.navFileScope(func.owner_nav).mod.?; + const mod = file.mod.?; var function: CodeGen = .{ .gpa = gpa, @@ -991,7 +994,6 @@ pub fn generate( .bin_file = bin_file, .owner = .{ .nav_index = func.owner_nav }, .inline_func = func_index, - .arg_index = undefined, .args = undefined, // populated after `resolveCallingConventionValues` .va_info = undefined, // populated after `resolveCallingConventionValues` .ret_mcv = undefined, // populated after `resolveCallingConventionValues` @@ -1011,6 +1013,8 @@ pub fn generate( function.inst_tracking.deinit(gpa); function.epilogue_relocs.deinit(gpa); function.mir_instructions.deinit(gpa); + function.mir_local_name_bytes.deinit(gpa); + function.mir_local_types.deinit(gpa); function.mir_extra.deinit(gpa); function.mir_table.deinit(gpa); } @@ -1078,7 +1082,7 @@ pub fn generate( ); } - function.gen() catch |err| switch (err) { + function.gen(&file.zir.?, func_zir.inst, func.comptime_args, call_info.air_arg_count) catch |err| switch (err) { error.CodegenFail => return error.CodegenFail, error.OutOfRegisters => return function.fail("ran out of registers (Zig compiler bug)", .{}), else => |e| return e, @@ -1097,17 +1101,32 @@ pub fn generate( var mir: Mir = .{ .instructions = .empty, .extra = &.{}, + .local_name_bytes = &.{}, + .local_types = &.{}, .table = &.{}, .frame_locs = .empty, }; errdefer mir.deinit(gpa); mir.instructions = function.mir_instructions.toOwnedSlice(); mir.extra = try function.mir_extra.toOwnedSlice(gpa); + mir.local_name_bytes = try function.mir_local_name_bytes.toOwnedSlice(gpa); + mir.local_types = try function.mir_local_types.toOwnedSlice(gpa); mir.table = try function.mir_table.toOwnedSlice(gpa); mir.frame_locs = function.frame_locs.toOwnedSlice(); return mir; } +pub fn toTmpMir(cg: *CodeGen) Mir { + return .{ + .instructions = cg.mir_instructions.slice(), + .extra = cg.mir_extra.items, + .local_name_bytes = cg.mir_local_name_bytes.items, + .local_types = cg.mir_local_types.items, + .table = cg.mir_table.items, + .frame_locs = cg.frame_locs.slice(), + }; +} + pub fn generateLazy( bin_file: *link.File, pt: Zcu.PerThread, @@ -1130,7 +1149,6 @@ pub fn generateLazy( .bin_file = bin_file, .owner = .{ .lazy_sym = lazy_sym }, .inline_func = undefined, - .arg_index = undefined, .args = undefined, .va_info = undefined, .ret_mcv = undefined, @@ -1141,6 +1159,8 @@ pub fn generateLazy( defer { function.inst_tracking.deinit(gpa); function.mir_instructions.deinit(gpa); + function.mir_local_name_bytes.deinit(gpa); + function.mir_local_types.deinit(gpa); function.mir_extra.deinit(gpa); function.mir_table.deinit(gpa); } @@ -1156,21 +1176,12 @@ pub fn generateLazy( else => |e| return e, }; - var mir: Mir = .{ - .instructions = function.mir_instructions.toOwnedSlice(), - .extra = try function.mir_extra.toOwnedSlice(gpa), - .table = try function.mir_table.toOwnedSlice(gpa), - .frame_locs = function.frame_locs.toOwnedSlice(), - }; - defer mir.deinit(gpa); - var emit: Emit = .{ - .air = function.air, .lower = .{ .bin_file = bin_file, .target = function.target, .allocator = gpa, - .mir = mir, + .mir = function.toTmpMir(), .cc = .auto, .src_loc = src_loc, .output_mode = comp.config.output_mode, @@ -1240,22 +1251,16 @@ fn formatWipMir( writer: anytype, ) @TypeOf(writer).Error!void { const comp = data.self.bin_file.comp; - const mod = comp.root_mod; var lower: Lower = .{ .bin_file = data.self.bin_file, .target = data.self.target, .allocator = data.self.gpa, - .mir = .{ - .instructions = data.self.mir_instructions.slice(), - .extra = data.self.mir_extra.items, - .table = data.self.mir_table.items, - .frame_locs = (std.MultiArrayList(Mir.FrameLoc){}).slice(), - }, + .mir = data.self.toTmpMir(), .cc = .auto, .src_loc = data.self.src_loc, .output_mode = comp.config.output_mode, .link_mode = comp.config.link_mode, - .pic = mod.pic, + .pic = data.self.mod.pic, }; var first = true; for ((lower.lowerMir(data.inst) catch |err| switch (err) { @@ -1291,7 +1296,9 @@ fn formatWipMir( .pseudo_dbg_epilogue_begin_none, .pseudo_dbg_enter_block_none, .pseudo_dbg_leave_block_none, + .pseudo_dbg_arg_none, .pseudo_dbg_var_args_none, + .pseudo_dbg_var_none, .pseudo_dead_none, => {}, .pseudo_dbg_line_stmt_line_column, .pseudo_dbg_line_line_column => try writer.print( @@ -1299,57 +1306,47 @@ fn formatWipMir( mir_inst.data.line_column, ), .pseudo_dbg_enter_inline_func, .pseudo_dbg_leave_inline_func => try writer.print(" {}", .{ - ip.getNav(ip.indexToKey(mir_inst.data.func).func.owner_nav).name.fmt(ip), + ip.getNav(ip.indexToKey(mir_inst.data.ip_index).func.owner_nav).name.fmt(ip), }), - .pseudo_dbg_local_a => try writer.print(" {}", .{mir_inst.data.a.air_inst}), - .pseudo_dbg_local_ai_s => try writer.print(" {}, {d}", .{ - mir_inst.data.ai.air_inst, - @as(i32, @bitCast(mir_inst.data.ai.i)), + .pseudo_dbg_arg_i_s, .pseudo_dbg_var_i_s => try writer.print(" {d}", .{ + @as(i32, @bitCast(mir_inst.data.i.i)), }), - .pseudo_dbg_local_ai_u => try writer.print(" {}, {d}", .{ - mir_inst.data.ai.air_inst, - mir_inst.data.ai.i, + .pseudo_dbg_arg_i_u, .pseudo_dbg_var_i_u => try writer.print(" {d}", .{ + mir_inst.data.i.i, }), - .pseudo_dbg_local_ai_64 => try writer.print(" {}, {d}", .{ - mir_inst.data.ai.air_inst, - lower.mir.extraData(Mir.Imm64, mir_inst.data.ai.i).data.decode(), + .pseudo_dbg_arg_i_64, .pseudo_dbg_var_i_64 => try writer.print(" {d}", .{ + mir_inst.data.i64, }), - .pseudo_dbg_local_as => { + .pseudo_dbg_arg_reloc, .pseudo_dbg_var_reloc => { const mem_op: encoder.Instruction.Operand = .{ .mem = .initSib(.qword, .{ - .base = .{ .reloc = mir_inst.data.as.sym_index }, + .base = .{ .reloc = mir_inst.data.reloc.sym_index }, + .disp = mir_inst.data.reloc.off, }) }; - try writer.print(" {}, {}", .{ mir_inst.data.as.air_inst, mem_op.fmt(.m) }); + try writer.print(" {}", .{mem_op.fmt(.m)}); }, - .pseudo_dbg_local_aso => { - const sym_off = lower.mir.extraData(bits.SymbolOffset, mir_inst.data.ax.payload).data; + .pseudo_dbg_arg_ro, .pseudo_dbg_var_ro => { const mem_op: encoder.Instruction.Operand = .{ .mem = .initSib(.qword, .{ - .base = .{ .reloc = sym_off.sym_index }, - .disp = sym_off.off, + .base = .{ .reg = mir_inst.data.ro.reg }, + .disp = mir_inst.data.ro.off, }) }; - try writer.print(" {}, {}", .{ mir_inst.data.ax.air_inst, mem_op.fmt(.m) }); + try writer.print(" {}", .{mem_op.fmt(.m)}); }, - .pseudo_dbg_local_aro => { - const air_off = lower.mir.extraData(Mir.AirOffset, mir_inst.data.rx.payload).data; + .pseudo_dbg_arg_fa, .pseudo_dbg_var_fa => { const mem_op: encoder.Instruction.Operand = .{ .mem = .initSib(.qword, .{ - .base = .{ .reg = mir_inst.data.rx.r1 }, - .disp = air_off.off, + .base = .{ .frame = mir_inst.data.fa.index }, + .disp = mir_inst.data.fa.off, }) }; - try writer.print(" {}, {}", .{ air_off.air_inst, mem_op.fmt(.m) }); + try writer.print(" {}", .{mem_op.fmt(.m)}); }, - .pseudo_dbg_local_af => { - const frame_addr = lower.mir.extraData(bits.FrameAddr, mir_inst.data.ax.payload).data; - const mem_op: encoder.Instruction.Operand = .{ .mem = .initSib(.qword, .{ - .base = .{ .frame = frame_addr.index }, - .disp = frame_addr.off, - }) }; - try writer.print(" {}, {}", .{ mir_inst.data.ax.air_inst, mem_op.fmt(.m) }); - }, - .pseudo_dbg_local_am => { + .pseudo_dbg_arg_m, .pseudo_dbg_var_m => { const mem_op: encoder.Instruction.Operand = .{ - .mem = lower.mir.extraData(Mir.Memory, mir_inst.data.ax.payload).data.decode(), + .mem = lower.mir.extraData(Mir.Memory, mir_inst.data.x.payload).data.decode(), }; - try writer.print(" {}, {}", .{ mir_inst.data.ax.air_inst, mem_op.fmt(.m) }); + try writer.print(" {}", .{mem_op.fmt(.m)}); }, + .pseudo_dbg_arg_val, .pseudo_dbg_var_val => try writer.print(" {}", .{ + Value.fromInterned(mir_inst.data.ip_index).fmtValue(data.self.pt), + }), } } } @@ -1640,124 +1637,6 @@ fn asmPlaceholder(self: *CodeGen) !Mir.Inst.Index { }); } -const MirTagAir = enum { dbg_local }; - -fn asmAir(self: *CodeGen, tag: MirTagAir, inst: Air.Inst.Index) !void { - _ = try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_a, - }, - .data = .{ .a = .{ .air_inst = inst } }, - }); -} - -fn asmAirImmediate(self: *CodeGen, tag: MirTagAir, inst: Air.Inst.Index, imm: Immediate) !void { - switch (imm) { - .signed => |s| _ = try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_ai_s, - }, - .data = .{ .ai = .{ - .air_inst = inst, - .i = @bitCast(s), - } }, - }), - .unsigned => |u| _ = if (std.math.cast(u32, u)) |small| try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_ai_u, - }, - .data = .{ .ai = .{ - .air_inst = inst, - .i = small, - } }, - }) else try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_ai_64, - }, - .data = .{ .ai = .{ - .air_inst = inst, - .i = try self.addExtra(Mir.Imm64.encode(u)), - } }, - }), - .reloc => |sym_off| _ = if (sym_off.off == 0) try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_as, - }, - .data = .{ .as = .{ - .air_inst = inst, - .sym_index = sym_off.sym_index, - } }, - }) else try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_aso, - }, - .data = .{ .ax = .{ - .air_inst = inst, - .payload = try self.addExtra(sym_off), - } }, - }), - } -} - -fn asmAirRegisterImmediate( - self: *CodeGen, - tag: MirTagAir, - inst: Air.Inst.Index, - reg: Register, - imm: Immediate, -) !void { - _ = try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_aro, - }, - .data = .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.AirOffset{ - .air_inst = inst, - .off = imm.signed, - }), - } }, - }); -} - -fn asmAirFrameAddress( - self: *CodeGen, - tag: MirTagAir, - inst: Air.Inst.Index, - frame_addr: bits.FrameAddr, -) !void { - _ = try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_af, - }, - .data = .{ .ax = .{ - .air_inst = inst, - .payload = try self.addExtra(frame_addr), - } }, - }); -} - -fn asmAirMemory(self: *CodeGen, tag: MirTagAir, inst: Air.Inst.Index, m: Memory) !void { - _ = try self.addInst(.{ - .tag = .pseudo, - .ops = switch (tag) { - .dbg_local => .pseudo_dbg_local_am, - }, - .data = .{ .ax = .{ - .air_inst = inst, - .payload = try self.addExtra(Mir.Memory.encode(m)), - } }, - }); -} - fn asmOpOnly(self: *CodeGen, tag: Mir.Inst.FixedTag) !void { _ = try self.addInst(.{ .tag = tag[1], @@ -2233,7 +2112,13 @@ fn asmMemoryRegisterImmediate( }); } -fn gen(self: *CodeGen) InnerError!void { +fn gen( + self: *CodeGen, + zir: *const std.zig.Zir, + func_zir_inst: std.zig.Zir.Inst.Index, + comptime_args: InternPool.Index.Slice, + air_arg_count: u32, +) InnerError!void { const pt = self.pt; const zcu = pt.zcu; const fn_info = zcu.typeToFunc(self.fn_type).?; @@ -2303,7 +2188,7 @@ fn gen(self: *CodeGen) InnerError!void { if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_prologue_end_none); - try self.genBody(self.air.getMainBody()); + try self.genMainBody(zir, func_zir_inst, comptime_args, air_arg_count); const epilogue = if (self.epilogue_relocs.items.len > 0) epilogue: { var last_inst: Mir.Inst.Index = @intCast(self.mir_instructions.len - 1); @@ -2438,20 +2323,81 @@ fn gen(self: *CodeGen) InnerError!void { } } else { if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_prologue_end_none); - try self.genBody(self.air.getMainBody()); + try self.genMainBody(zir, func_zir_inst, comptime_args, air_arg_count); if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_epilogue_begin_none); } } -fn checkInvariantsAfterAirInst(self: *CodeGen) void { - assert(!self.register_manager.lockedRegsExist()); +fn genMainBody( + cg: *CodeGen, + zir: *const std.zig.Zir, + func_zir_inst: std.zig.Zir.Inst.Index, + comptime_args: InternPool.Index.Slice, + air_arg_count: u32, +) InnerError!void { + const pt = cg.pt; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + const main_body = cg.air.getMainBody(); + const air_args_body = main_body[0..air_arg_count]; + try cg.genBody(air_args_body); + + if (!cg.mod.strip) { + var air_arg_index: usize = 0; + const fn_info = zcu.typeToFunc(cg.fn_type).?; + var fn_param_index: usize = 0; + try cg.mir_local_types.ensureTotalCapacity(cg.gpa, fn_info.param_types.len); + var zir_param_index: usize = 0; + for (zir.getParamBody(func_zir_inst)) |zir_param_inst| { + const name = zir.nullTerminatedString(zir.getParamName(zir_param_inst) orelse continue); + defer zir_param_index += 1; + try cg.mir_local_name_bytes.appendSlice(cg.gpa, name[0 .. name.len + 1]); + + if (comptime_args.len > 0) switch (comptime_args.get(ip)[zir_param_index]) { + .none => {}, + else => |comptime_arg| { + _ = try cg.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_dbg_arg_val, + .data = .{ .ip_index = comptime_arg }, + }); + continue; + }, + }; + + const arg_ty: Type = .fromInterned(fn_info.param_types.get(ip)[fn_param_index]); + fn_param_index += 1; + cg.mir_local_types.appendAssumeCapacity(arg_ty.toIntern()); + + if (air_arg_index == air_args_body.len) { + try cg.asmPseudo(.pseudo_dbg_arg_none); + continue; + } + const air_arg_inst = air_args_body[air_arg_index]; + const air_arg_data = cg.air.instructions.items(.data)[air_arg_index].arg; + if (air_arg_data.zir_param_index != zir_param_index) { + try cg.asmPseudo(.pseudo_dbg_arg_none); + continue; + } + air_arg_index += 1; + try cg.genLocalDebugInfo(.arg, arg_ty, cg.getResolvedInstValue(air_arg_inst).short); + } + if (fn_info.is_var_args) try cg.asmPseudo(.pseudo_dbg_var_args_none); + } + + try cg.genBody(main_body[air_arg_count..]); +} + +fn checkInvariantsAfterAirInst(cg: *CodeGen) void { + assert(!cg.register_manager.lockedRegsExist()); if (std.debug.runtime_safety) { // check consistency of tracked registers - var it = self.register_manager.free_registers.iterator(.{ .kind = .unset }); + var it = cg.register_manager.free_registers.iterator(.{ .kind = .unset }); while (it.next()) |index| { - const tracked_inst = self.register_manager.registers[index]; - const tracking = self.getResolvedInstValue(tracked_inst); + const tracked_inst = cg.register_manager.registers[index]; + const tracking = cg.getResolvedInstValue(tracked_inst); for (tracking.getRegs()) |reg| { if (RegisterManager.indexOfRegIntoTracked(reg).? == index) break; } else unreachable; // tracked register not in use @@ -2459,10 +2405,10 @@ fn checkInvariantsAfterAirInst(self: *CodeGen) void { } } -fn genBodyBlock(self: *CodeGen, body: []const Air.Inst.Index) InnerError!void { - if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_enter_block_none); - try self.genBody(body); - if (!self.mod.strip) try self.asmPseudo(.pseudo_dbg_leave_block_none); +fn genBodyBlock(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { + if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_enter_block_none); + try cg.genBody(body); + if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_leave_block_none); } fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { @@ -2474,25 +2420,6 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { const air_datas = cg.air.instructions.items(.data); const use_old = cg.target.ofmt == .coff; - cg.arg_index = 0; - for (body) |inst| switch (air_tags[@intFromEnum(inst)]) { - .arg => { - wip_mir_log.debug("{}", .{cg.fmtAir(inst)}); - verbose_tracking_log.debug("{}", .{cg.fmtTracking()}); - - cg.reused_operands = .initEmpty(); - try cg.inst_tracking.ensureUnusedCapacity(cg.gpa, 1); - - try cg.airArg(inst); - - try cg.resetTemps(@enumFromInt(0)); - cg.checkInvariantsAfterAirInst(); - }, - else => break, - }; - - if (cg.arg_index == 0) try cg.airDbgVarArgs(); - cg.arg_index = 0; for (body) |inst| { if (cg.liveness.isUnused(inst) and !cg.air.mustLower(inst, ip)) continue; wip_mir_log.debug("{}", .{cg.fmtAir(inst)}); @@ -2506,20 +2433,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .shuffle_one, .shuffle_two => @panic("x86_64 TODO: shuffle_one/shuffle_two"), // zig fmt: on - .arg => if (!cg.mod.strip) { - // skip zero-bit arguments as they don't have a corresponding arg instruction - var arg_index = cg.arg_index; - while (cg.args[arg_index] == .none) arg_index += 1; - cg.arg_index = arg_index + 1; - - const name = air_datas[@intFromEnum(inst)].arg.name; - if (name != .none) try cg.genLocalDebugInfo(inst, cg.getResolvedInstValue(inst).short); - if (cg.liveness.isUnused(inst)) try cg.processDeath(inst); - - for (cg.args[arg_index + 1 ..]) |arg| { - if (arg != .none) break; - } else try cg.airDbgVarArgs(); - }, + .arg => try cg.airArg(inst), .add, .add_optimized, .add_wrap => |air_tag| if (use_old) try cg.airBinOp(inst, switch (air_tag) { else => unreachable, .add, .add_optimized => .add, @@ -85181,19 +85095,19 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { if (!cg.mod.strip) _ = try cg.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_enter_inline_func, - .data = .{ .func = dbg_inline_block.data.func }, + .data = .{ .ip_index = dbg_inline_block.data.func }, }); try cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[dbg_inline_block.end..][0..dbg_inline_block.data.body_len])); if (!cg.mod.strip) _ = try cg.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_leave_inline_func, - .data = .{ .func = old_inline_func }, + .data = .{ .ip_index = old_inline_func }, }); }, .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline, - => if (use_old) try cg.airDbgVar(inst) else if (!cg.mod.strip) { + => |air_tag| if (use_old) try cg.airDbgVar(inst) else if (!cg.mod.strip) { const pl_op = air_datas[@intFromEnum(inst)].pl_op; var ops = try cg.tempsFromOperands(inst, .{pl_op.operand}); var mcv = ops[0].tracking(cg).short; @@ -85209,7 +85123,16 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { }, }, } - try cg.genLocalDebugInfo(inst, ops[0].tracking(cg).short); + + const name_nts: Air.NullTerminatedString = @enumFromInt(pl_op.payload); + assert(name_nts != .none); + const name = name_nts.toSlice(cg.air); + try cg.mir_local_name_bytes.appendSlice(cg.gpa, name[0 .. name.len + 1]); + + const ty = cg.typeOf(pl_op.operand); + try cg.mir_local_types.append(cg.gpa, ty.toIntern()); + + try cg.genLocalDebugInfo(air_tag, ty, ops[0].tracking(cg).short); try ops[0].die(cg); }, .is_null => if (use_old) try cg.airIsNull(inst) else { @@ -173321,16 +173244,14 @@ fn genIntMulComplexOpMir(self: *CodeGen, dst_ty: Type, dst_mcv: MCValue, src_mcv } fn airArg(self: *CodeGen, inst: Air.Inst.Index) !void { - const pt = self.pt; - const zcu = pt.zcu; - // skip zero-bit arguments as they don't have a corresponding arg instruction - var arg_index = self.arg_index; - while (self.args[arg_index] == .none) arg_index += 1; - self.arg_index = arg_index + 1; - + const zcu = self.pt.zcu; + const arg_index = for (self.args, 0..) |arg, arg_index| { + if (arg != .none) break arg_index; + } else unreachable; + const src_mcv = self.args[arg_index]; + self.args = self.args[arg_index + 1 ..]; const result: MCValue = if (self.mod.strip and self.liveness.isUnused(inst)) .unreach else result: { const arg_ty = self.typeOfIndex(inst); - const src_mcv = self.args[arg_index]; switch (src_mcv) { .register, .register_pair, .load_frame => { for (src_mcv.getRegs()) |reg| self.register_manager.getRegAssumeFree(reg, inst); @@ -173429,68 +173350,108 @@ fn airArg(self: *CodeGen, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } -fn airDbgVarArgs(self: *CodeGen) !void { - if (self.mod.strip) return; - if (!self.pt.zcu.typeToFunc(self.fn_type).?.is_var_args) return; - try self.asmPseudo(.pseudo_dbg_var_args_none); -} - -fn genLocalDebugInfo( - self: *CodeGen, - inst: Air.Inst.Index, - mcv: MCValue, -) !void { - if (self.mod.strip) return; - switch (self.air.instructions.items(.tag)[@intFromEnum(inst)]) { +fn genLocalDebugInfo(cg: *CodeGen, air_tag: Air.Inst.Tag, ty: Type, mcv: MCValue) !void { + assert(!cg.mod.strip); + _ = switch (air_tag) { else => unreachable, - .arg, .dbg_arg_inline, .dbg_var_val => |tag| { - switch (mcv) { - .none => try self.asmAir(.dbg_local, inst), - .unreach, .dead, .elementwise_args, .reserved_frame, .air_ref => unreachable, - .immediate => |imm| try self.asmAirImmediate(.dbg_local, inst, .u(imm)), - .lea_frame => |frame_addr| try self.asmAirFrameAddress(.dbg_local, inst, frame_addr), - .lea_symbol => |sym_off| try self.asmAirImmediate(.dbg_local, inst, .rel(sym_off)), - else => { - const ty = switch (tag) { - else => unreachable, - .arg => self.typeOfIndex(inst), - .dbg_arg_inline, .dbg_var_val => self.typeOf( - self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op.operand, - ), - }; - const frame_index = try self.allocFrameIndex(.initSpill(ty, self.pt.zcu)); - try self.genSetMem(.{ .frame = frame_index }, 0, ty, mcv, .{}); - try self.asmAirMemory(.dbg_local, inst, .{ - .base = .{ .frame = frame_index }, - .mod = .{ .rm = .{ .size = .qword } }, - }); + .arg, .dbg_var_val, .dbg_arg_inline => switch (mcv) { + .none, .unreach, .dead, .elementwise_args, .reserved_frame, .air_ref => unreachable, + .immediate => |imm| if (std.math.cast(u32, imm)) |small| try cg.addInst(.{ + .tag = .pseudo, + .ops = switch (air_tag) { + else => unreachable, + .arg, .dbg_arg_inline => .pseudo_dbg_arg_i_u, + .dbg_var_val => .pseudo_dbg_var_i_u, }, - } + .data = .{ .i = .{ .i = small } }, + }) else try cg.addInst(.{ + .tag = .pseudo, + .ops = switch (air_tag) { + else => unreachable, + .arg, .dbg_arg_inline => .pseudo_dbg_arg_i_64, + .dbg_var_val => .pseudo_dbg_var_i_64, + }, + .data = .{ .i64 = imm }, + }), + .lea_frame => |frame_addr| try cg.addInst(.{ + .tag = .pseudo, + .ops = switch (air_tag) { + else => unreachable, + .arg, .dbg_arg_inline => .pseudo_dbg_arg_fa, + .dbg_var_val => .pseudo_dbg_var_fa, + }, + .data = .{ .fa = frame_addr }, + }), + .lea_symbol => |sym_off| try cg.addInst(.{ + .tag = .pseudo, + .ops = switch (air_tag) { + else => unreachable, + .arg, .dbg_arg_inline => .pseudo_dbg_arg_reloc, + .dbg_var_val => .pseudo_dbg_var_reloc, + }, + .data = .{ .reloc = sym_off }, + }), + else => { + const frame_index = try cg.allocFrameIndex(.initSpill(ty, cg.pt.zcu)); + try cg.genSetMem(.{ .frame = frame_index }, 0, ty, mcv, .{}); + _ = try cg.addInst(.{ + .tag = .pseudo, + .ops = switch (air_tag) { + else => unreachable, + .arg, .dbg_arg_inline => .pseudo_dbg_arg_m, + .dbg_var_val => .pseudo_dbg_var_m, + }, + .data = .{ .x = .{ + .payload = try cg.addExtra(Mir.Memory.encode(.{ + .base = .{ .frame = frame_index }, + .mod = .{ .rm = .{ .size = .qword } }, + })), + } }, + }); + }, }, .dbg_var_ptr => switch (mcv) { else => unreachable, - .unreach, .dead, .elementwise_args, .reserved_frame, .air_ref => unreachable, - .lea_frame => |frame_addr| try self.asmAirMemory(.dbg_local, inst, .{ - .base = .{ .frame = frame_addr.index }, - .mod = .{ .rm = .{ - .size = .qword, - .disp = frame_addr.off, + .none, .unreach, .dead, .elementwise_args, .reserved_frame, .air_ref => unreachable, + .lea_frame => |frame_addr| try cg.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_dbg_var_m, + .data = .{ .x = .{ + .payload = try cg.addExtra(Mir.Memory.encode(.{ + .base = .{ .frame = frame_addr.index }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = frame_addr.off, + } }, + })), } }, }), // debug info should explicitly ignore pcrel requirements - .lea_symbol, .lea_pcrel => |sym_off| try self.asmAirMemory(.dbg_local, inst, .{ - .base = .{ .reloc = sym_off.sym_index }, - .mod = .{ .rm = .{ - .size = .qword, - .disp = sym_off.off, + .lea_symbol, .lea_pcrel => |sym_off| try cg.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_dbg_var_m, + .data = .{ .x = .{ + .payload = try cg.addExtra(Mir.Memory.encode(.{ + .base = .{ .reloc = sym_off.sym_index }, + .mod = .{ .rm = .{ + .size = .qword, + .disp = sym_off.off, + } }, + })), } }, }), - .lea_direct, .lea_got => |sym_index| try self.asmAirMemory(.dbg_local, inst, .{ - .base = .{ .reloc = sym_index }, - .mod = .{ .rm = .{ .size = .qword } }, + .lea_direct, .lea_got => |sym_index| try cg.addInst(.{ + .tag = .pseudo, + .ops = .pseudo_dbg_var_m, + .data = .{ .x = .{ + .payload = try cg.addExtra(Mir.Memory.encode(.{ + .base = .{ .reloc = sym_index }, + .mod = .{ .rm = .{ .size = .qword } }, + })), + } }, }), }, - } + }; } fn airRetAddr(self: *CodeGen, inst: Air.Inst.Index) !void { @@ -173514,8 +173475,8 @@ fn airCall(self: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]); const ExpectedContents = extern struct { - tys: [16][@sizeOf(Type)]u8 align(@alignOf(Type)), - vals: [16][@sizeOf(MCValue)]u8 align(@alignOf(MCValue)), + tys: [32][@sizeOf(Type)]u8 align(@alignOf(Type)), + vals: [32][@sizeOf(MCValue)]u8 align(@alignOf(MCValue)), }; var stack align(@max(@alignOf(ExpectedContents), @alignOf(std.heap.StackFallbackAllocator(0)))) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); @@ -173570,9 +173531,9 @@ fn genCall(self: *CodeGen, info: union(enum) { const fn_info = zcu.typeToFunc(fn_ty).?; const ExpectedContents = extern struct { - var_args: [16][@sizeOf(Type)]u8 align(@alignOf(Type)), - frame_indices: [16]FrameIndex, - reg_locks: [16][@sizeOf(?RegisterLock)]u8 align(@alignOf(?RegisterLock)), + var_args: [32][@sizeOf(Type)]u8 align(@alignOf(Type)), + frame_indices: [32]FrameIndex, + reg_locks: [32][@sizeOf(?RegisterLock)]u8 align(@alignOf(?RegisterLock)), }; var stack align(@max(@alignOf(ExpectedContents), @alignOf(std.heap.StackFallbackAllocator(0)))) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); @@ -174488,10 +174449,21 @@ fn genTry( return result; } -fn airDbgVar(self: *CodeGen, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - try self.genLocalDebugInfo(inst, try self.resolveInst(pl_op.operand)); - return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); +fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void { + if (cg.mod.strip) return; + const air_tag = cg.air.instructions.items(.tag)[@intFromEnum(inst)]; + const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; + + const name_nts: Air.NullTerminatedString = @enumFromInt(pl_op.payload); + assert(name_nts != .none); + const name = name_nts.toSlice(cg.air); + try cg.mir_local_name_bytes.appendSlice(cg.gpa, name[0 .. name.len + 1]); + + const ty = cg.typeOf(pl_op.operand); + try cg.mir_local_types.append(cg.gpa, ty.toIntern()); + + try cg.genLocalDebugInfo(air_tag, ty, try cg.resolveInst(pl_op.operand)); + return cg.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); } fn genCondBrMir(self: *CodeGen, ty: Type, mcv: MCValue) !Mir.Inst.Index { @@ -181477,6 +181449,7 @@ fn lowerUav(self: *CodeGen, val: Value, alignment: InternPool.Alignment) InnerEr const CallMCValues = struct { args: []MCValue, + air_arg_count: u32, return_value: InstTracking, stack_byte_count: u31, stack_align: InternPool.Alignment, @@ -181512,13 +181485,14 @@ fn resolveCallingConventionValues( const param_types = try allocator.alloc(Type, fn_info.param_types.len + var_args.len); defer allocator.free(param_types); - for (param_types[0..fn_info.param_types.len], fn_info.param_types.get(ip)) |*dest, src| - dest.* = .fromInterned(src); + for (param_types[0..fn_info.param_types.len], fn_info.param_types.get(ip)) |*param_ty, arg_ty| + param_ty.* = .fromInterned(arg_ty); for (param_types[fn_info.param_types.len..], var_args) |*param_ty, arg_ty| param_ty.* = self.promoteVarArg(arg_ty); var result: CallMCValues = .{ .args = try self.gpa.alloc(MCValue, param_types.len), + .air_arg_count = 0, // These undefined values must be populated before returning from this function. .return_value = undefined, .stack_byte_count = 0, @@ -181640,6 +181614,7 @@ fn resolveCallingConventionValues( // Input params for (param_types, result.args) |ty, *arg| { assert(ty.hasRuntimeBitsIgnoreComptime(zcu)); + result.air_arg_count += 1; switch (cc) { .x86_64_sysv => {}, .x86_64_win => { @@ -181812,6 +181787,7 @@ fn resolveCallingConventionValues( arg.* = .none; continue; } + result.air_arg_count += 1; const param_size: u31 = @intCast(param_ty.abiSize(zcu)); if (abi.zigcc.params_in_regs) switch (self.regClassForType(param_ty)) { .general_purpose, .gphi => if (param_gpr.len >= 1 and param_size <= @as(u4, switch (self.target.cpu.arch) { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index d4116974cf..cbbfdab202 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1,6 +1,5 @@ //! This file contains the functionality for emitting x86_64 MIR as machine code -air: Air, lower: Lower, atom_index: u32, debug_output: link.File.DebugInfoOutput, @@ -22,6 +21,8 @@ pub fn emitMir(emit: *Emit) Error!void { defer relocs.deinit(emit.lower.allocator); var table_relocs: std.ArrayListUnmanaged(TableReloc) = .empty; defer table_relocs.deinit(emit.lower.allocator); + var local_name_index: usize = 0; + var local_index: usize = 0; for (0..emit.lower.mir.instructions.len) |mir_i| { const mir_index: Mir.Inst.Index = @intCast(mir_i); code_offset_mapping[mir_index] = @intCast(emit.code.items.len); @@ -338,7 +339,7 @@ pub fn emitMir(emit: *Emit) Error!void { log.debug("mirDbgEnterInline (line={d}, col={d})", .{ emit.prev_di_loc.line, emit.prev_di_loc.column, }); - try dwarf.enterInlineFunc(mir_inst.data.func, emit.code.items.len, emit.prev_di_loc.line, emit.prev_di_loc.column); + try dwarf.enterInlineFunc(mir_inst.data.ip_index, emit.code.items.len, emit.prev_di_loc.line, emit.prev_di_loc.column); }, .plan9 => {}, .none => {}, @@ -348,77 +349,61 @@ pub fn emitMir(emit: *Emit) Error!void { log.debug("mirDbgLeaveInline (line={d}, col={d})", .{ emit.prev_di_loc.line, emit.prev_di_loc.column, }); - try dwarf.leaveInlineFunc(mir_inst.data.func, emit.code.items.len); + try dwarf.leaveInlineFunc(mir_inst.data.ip_index, emit.code.items.len); }, .plan9 => {}, .none => {}, }, - .pseudo_dbg_local_a, - .pseudo_dbg_local_ai_s, - .pseudo_dbg_local_ai_u, - .pseudo_dbg_local_ai_64, - .pseudo_dbg_local_as, - .pseudo_dbg_local_aso, - .pseudo_dbg_local_aro, - .pseudo_dbg_local_af, - .pseudo_dbg_local_am, + .pseudo_dbg_arg_none, + .pseudo_dbg_arg_i_s, + .pseudo_dbg_arg_i_u, + .pseudo_dbg_arg_i_64, + .pseudo_dbg_arg_reloc, + .pseudo_dbg_arg_ro, + .pseudo_dbg_arg_fa, + .pseudo_dbg_arg_m, + .pseudo_dbg_var_none, + .pseudo_dbg_var_i_s, + .pseudo_dbg_var_i_u, + .pseudo_dbg_var_i_64, + .pseudo_dbg_var_reloc, + .pseudo_dbg_var_ro, + .pseudo_dbg_var_fa, + .pseudo_dbg_var_m, => switch (emit.debug_output) { .dwarf => |dwarf| { var loc_buf: [2]link.File.Dwarf.Loc = undefined; - const air_inst_index, const loc: link.File.Dwarf.Loc = switch (mir_inst.ops) { + const loc: link.File.Dwarf.Loc = loc: switch (mir_inst.ops) { else => unreachable, - .pseudo_dbg_local_a => .{ mir_inst.data.a.air_inst, .empty }, - .pseudo_dbg_local_ai_s, - .pseudo_dbg_local_ai_u, - .pseudo_dbg_local_ai_64, - => .{ mir_inst.data.ai.air_inst, .{ .stack_value = stack_value: { - loc_buf[0] = switch (emit.lower.imm(mir_inst.ops, mir_inst.data.ai.i)) { + .pseudo_dbg_arg_none, .pseudo_dbg_var_none => .empty, + .pseudo_dbg_arg_i_s, + .pseudo_dbg_arg_i_u, + .pseudo_dbg_var_i_s, + .pseudo_dbg_var_i_u, + => .{ .stack_value = stack_value: { + loc_buf[0] = switch (emit.lower.imm(mir_inst.ops, mir_inst.data.i.i)) { .signed => |s| .{ .consts = s }, .unsigned => |u| .{ .constu = u }, }; break :stack_value &loc_buf[0]; - } } }, - .pseudo_dbg_local_as => .{ mir_inst.data.as.air_inst, .{ - .addr_reloc = mir_inst.data.as.sym_index, } }, - .pseudo_dbg_local_aso => loc: { - const sym_off = emit.lower.mir.extraData( - bits.SymbolOffset, - mir_inst.data.ax.payload, - ).data; - break :loc .{ mir_inst.data.ax.air_inst, .{ .plus = .{ - sym: { - loc_buf[0] = .{ .addr_reloc = sym_off.sym_index }; - break :sym &loc_buf[0]; - }, - off: { - loc_buf[1] = .{ .consts = sym_off.off }; - break :off &loc_buf[1]; - }, - } } }; - }, - .pseudo_dbg_local_aro => loc: { - const air_off = emit.lower.mir.extraData( - Mir.AirOffset, - mir_inst.data.rx.payload, - ).data; - break :loc .{ air_off.air_inst, .{ .plus = .{ - reg: { - loc_buf[0] = .{ .breg = mir_inst.data.rx.r1.dwarfNum() }; - break :reg &loc_buf[0]; - }, - off: { - loc_buf[1] = .{ .consts = air_off.off }; - break :off &loc_buf[1]; - }, - } } }; - }, - .pseudo_dbg_local_af => loc: { - const reg_off = emit.lower.mir.resolveFrameAddr(emit.lower.mir.extraData( - bits.FrameAddr, - mir_inst.data.ax.payload, - ).data); - break :loc .{ mir_inst.data.ax.air_inst, .{ .plus = .{ + .pseudo_dbg_arg_i_64, .pseudo_dbg_var_i_64 => .{ .stack_value = stack_value: { + loc_buf[0] = .{ .constu = mir_inst.data.i64 }; + break :stack_value &loc_buf[0]; + } }, + .pseudo_dbg_arg_reloc, .pseudo_dbg_var_reloc => .{ .plus = .{ + sym: { + loc_buf[0] = .{ .addr_reloc = mir_inst.data.reloc.sym_index }; + break :sym &loc_buf[0]; + }, + off: { + loc_buf[1] = .{ .consts = mir_inst.data.reloc.off }; + break :off &loc_buf[1]; + }, + } }, + .pseudo_dbg_arg_fa, .pseudo_dbg_var_fa => { + const reg_off = emit.lower.mir.resolveFrameAddr(mir_inst.data.fa); + break :loc .{ .plus = .{ reg: { loc_buf[0] = .{ .breg = reg_off.reg.dwarfNum() }; break :reg &loc_buf[0]; @@ -427,11 +412,11 @@ pub fn emitMir(emit: *Emit) Error!void { loc_buf[1] = .{ .consts = reg_off.off }; break :off &loc_buf[1]; }, - } } }; + } }; }, - .pseudo_dbg_local_am => loc: { - const mem = emit.lower.mem(undefined, mir_inst.data.ax.payload); - break :loc .{ mir_inst.data.ax.air_inst, .{ .plus = .{ + .pseudo_dbg_arg_m, .pseudo_dbg_var_m => { + const mem = emit.lower.mem(undefined, mir_inst.data.x.payload); + break :loc .{ .plus = .{ base: { loc_buf[0] = switch (mem.base()) { .none => .{ .constu = 0 }, @@ -449,30 +434,64 @@ pub fn emitMir(emit: *Emit) Error!void { }; break :disp &loc_buf[1]; }, - } } }; + } }; }, }; - const ip = &emit.lower.bin_file.comp.zcu.?.intern_pool; - const air_inst = emit.air.instructions.get(@intFromEnum(air_inst_index)); - const name: Air.NullTerminatedString = switch (air_inst.tag) { - else => unreachable, - .arg => air_inst.data.arg.name, - .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => @enumFromInt(air_inst.data.pl_op.payload), - }; - try dwarf.genLocalDebugInfo( - switch (air_inst.tag) { + + const local_name_bytes = emit.lower.mir.local_name_bytes[local_name_index..]; + const local_name = local_name_bytes[0..std.mem.indexOfScalar(u8, local_name_bytes, 0).? :0]; + local_name_index += local_name.len + 1; + + const local_type = emit.lower.mir.local_types[local_index]; + local_index += 1; + + try dwarf.genLocalVarDebugInfo( + switch (mir_inst.ops) { else => unreachable, - .arg, .dbg_arg_inline => .local_arg, - .dbg_var_ptr, .dbg_var_val => .local_var, + .pseudo_dbg_arg_none, + .pseudo_dbg_arg_i_s, + .pseudo_dbg_arg_i_u, + .pseudo_dbg_arg_i_64, + .pseudo_dbg_arg_reloc, + .pseudo_dbg_arg_ro, + .pseudo_dbg_arg_fa, + .pseudo_dbg_arg_m, + .pseudo_dbg_arg_val, + => .arg, + .pseudo_dbg_var_none, + .pseudo_dbg_var_i_s, + .pseudo_dbg_var_i_u, + .pseudo_dbg_var_i_64, + .pseudo_dbg_var_reloc, + .pseudo_dbg_var_ro, + .pseudo_dbg_var_fa, + .pseudo_dbg_var_m, + .pseudo_dbg_var_val, + => .local_var, }, - name.toSlice(emit.air), - switch (air_inst.tag) { + local_name, + .fromInterned(local_type), + loc, + ); + }, + .plan9 => {}, + .none => {}, + }, + .pseudo_dbg_arg_val, .pseudo_dbg_var_val => switch (emit.debug_output) { + .dwarf => |dwarf| { + const local_name_bytes = emit.lower.mir.local_name_bytes[local_name_index..]; + const local_name = local_name_bytes[0..std.mem.indexOfScalar(u8, local_name_bytes, 0).? :0]; + local_name_index += local_name.len + 1; + + try dwarf.genLocalConstDebugInfo( + emit.lower.src_loc, + switch (mir_inst.ops) { else => unreachable, - .arg => emit.air.typeOfIndex(air_inst_index, ip), - .dbg_var_ptr => emit.air.typeOf(air_inst.data.pl_op.operand, ip).childTypeIp(ip), - .dbg_var_val, .dbg_arg_inline => emit.air.typeOf(air_inst.data.pl_op.operand, ip), + .pseudo_dbg_arg_val => .comptime_arg, + .pseudo_dbg_var_val => .local_const, }, - loc, + local_name, + .fromInterned(mir_inst.data.ip_index), ); }, .plan9 => {}, @@ -611,11 +630,10 @@ fn dbgAdvancePCAndLine(emit: *Emit, loc: Loc) Error!void { } const bits = @import("bits.zig"); +const Emit = @This(); +const InternPool = @import("../../InternPool.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.emit); -const std = @import("std"); - -const Air = @import("../../Air.zig"); -const Emit = @This(); const Lower = @import("Lower.zig"); const Mir = @import("Mir.zig"); +const std = @import("std"); diff --git a/src/arch/x86_64/Lower.zig b/src/arch/x86_64/Lower.zig index 838f155d10..54b419103f 100644 --- a/src/arch/x86_64/Lower.zig +++ b/src/arch/x86_64/Lower.zig @@ -327,16 +327,25 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index) Error!struct { .pseudo_dbg_leave_block_none, .pseudo_dbg_enter_inline_func, .pseudo_dbg_leave_inline_func, - .pseudo_dbg_local_a, - .pseudo_dbg_local_ai_s, - .pseudo_dbg_local_ai_u, - .pseudo_dbg_local_ai_64, - .pseudo_dbg_local_as, - .pseudo_dbg_local_aso, - .pseudo_dbg_local_aro, - .pseudo_dbg_local_af, - .pseudo_dbg_local_am, + .pseudo_dbg_arg_none, + .pseudo_dbg_arg_i_s, + .pseudo_dbg_arg_i_u, + .pseudo_dbg_arg_i_64, + .pseudo_dbg_arg_reloc, + .pseudo_dbg_arg_ro, + .pseudo_dbg_arg_fa, + .pseudo_dbg_arg_m, + .pseudo_dbg_arg_val, .pseudo_dbg_var_args_none, + .pseudo_dbg_var_none, + .pseudo_dbg_var_i_s, + .pseudo_dbg_var_i_u, + .pseudo_dbg_var_i_64, + .pseudo_dbg_var_reloc, + .pseudo_dbg_var_ro, + .pseudo_dbg_var_fa, + .pseudo_dbg_var_m, + .pseudo_dbg_var_val, .pseudo_dead_none, => {}, @@ -364,7 +373,8 @@ pub fn imm(lower: *const Lower, ops: Mir.Inst.Ops, i: u32) Immediate { .i_s, .mi_s, .rmi_s, - .pseudo_dbg_local_ai_s, + .pseudo_dbg_arg_i_s, + .pseudo_dbg_var_i_s, => .s(@bitCast(i)), .ii, @@ -379,13 +389,17 @@ pub fn imm(lower: *const Lower, ops: Mir.Inst.Ops, i: u32) Immediate { .mri, .rrm, .rrmi, - .pseudo_dbg_local_ai_u, + .pseudo_dbg_arg_i_u, + .pseudo_dbg_var_i_u, => .u(i), .ri_64, - .pseudo_dbg_local_ai_64, => .u(lower.mir.extraData(Mir.Imm64, i).data.decode()), + .pseudo_dbg_arg_i_64, + .pseudo_dbg_var_i_64, + => unreachable, + else => unreachable, }; } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 14468677af..24d5c6a3ed 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -9,6 +9,8 @@ instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. extra: []const u32, +local_name_bytes: []const u8, +local_types: []const InternPool.Index, table: []const Inst.Index, frame_locs: std.MultiArrayList(FrameLoc).Slice, @@ -1522,6 +1524,7 @@ pub const Inst = struct { pseudo_cfi_escape_bytes, /// End of prologue + /// Uses `none` payload. pseudo_dbg_prologue_end_none, /// Update debug line with is_stmt register set /// Uses `line_column` payload. @@ -1530,44 +1533,76 @@ pub const Inst = struct { /// Uses `line_column` payload. pseudo_dbg_line_line_column, /// Start of epilogue + /// Uses `none` payload. pseudo_dbg_epilogue_begin_none, /// Start of lexical block + /// Uses `none` payload. pseudo_dbg_enter_block_none, /// End of lexical block + /// Uses `none` payload. pseudo_dbg_leave_block_none, /// Start of inline function + /// Uses `ip_index` payload. pseudo_dbg_enter_inline_func, /// End of inline function + /// Uses `ip_index` payload. pseudo_dbg_leave_inline_func, - /// Local argument or variable. - /// Uses `a` payload. - pseudo_dbg_local_a, - /// Local argument or variable. - /// Uses `ai` payload. - pseudo_dbg_local_ai_s, - /// Local argument or variable. - /// Uses `ai` payload. - pseudo_dbg_local_ai_u, - /// Local argument or variable. - /// Uses `ai` payload with extra data of type `Imm64`. - pseudo_dbg_local_ai_64, - /// Local argument or variable. - /// Uses `as` payload. - pseudo_dbg_local_as, - /// Local argument or variable. - /// Uses `ax` payload with extra data of type `bits.SymbolOffset`. - pseudo_dbg_local_aso, - /// Local argument or variable. - /// Uses `rx` payload with extra data of type `AirOffset`. - pseudo_dbg_local_aro, - /// Local argument or variable. - /// Uses `ax` payload with extra data of type `bits.FrameAddr`. - pseudo_dbg_local_af, - /// Local argument or variable. - /// Uses `ax` payload with extra data of type `Memory`. - pseudo_dbg_local_am, + /// Local argument. + /// Uses `none` payload. + pseudo_dbg_arg_none, + /// Local argument. + /// Uses `i` payload. + pseudo_dbg_arg_i_s, + /// Local argument. + /// Uses `i` payload. + pseudo_dbg_arg_i_u, + /// Local argument. + /// Uses `i64` payload. + pseudo_dbg_arg_i_64, + /// Local argument. + /// Uses `reloc` payload. + pseudo_dbg_arg_reloc, + /// Local argument. + /// Uses `ro` payload. + pseudo_dbg_arg_ro, + /// Local argument. + /// Uses `fa` payload. + pseudo_dbg_arg_fa, + /// Local argument. + /// Uses `x` payload with extra data of type `Memory`. + pseudo_dbg_arg_m, + /// Local argument. + /// Uses `ip_index` payload. + pseudo_dbg_arg_val, /// Remaining arguments are varargs. pseudo_dbg_var_args_none, + /// Local variable. + /// Uses `none` payload. + pseudo_dbg_var_none, + /// Local variable. + /// Uses `i` payload. + pseudo_dbg_var_i_s, + /// Local variable. + /// Uses `i` payload. + pseudo_dbg_var_i_u, + /// Local variable. + /// Uses `i64` payload. + pseudo_dbg_var_i_64, + /// Local variable. + /// Uses `reloc` payload. + pseudo_dbg_var_reloc, + /// Local variable. + /// Uses `ro` payload. + pseudo_dbg_var_ro, + /// Local variable. + /// Uses `fa` payload. + pseudo_dbg_var_fa, + /// Local variable. + /// Uses `x` payload with extra data of type `Memory`. + pseudo_dbg_var_m, + /// Local variable. + /// Uses `ip_index` payload. + pseudo_dbg_var_val, /// Tombstone /// Emitter should skip this instruction. @@ -1584,6 +1619,7 @@ pub const Inst = struct { inst: Index, }, /// A 32-bit immediate value. + i64: u64, i: struct { fixes: Fixes = ._, i: u32, @@ -1683,31 +1719,18 @@ pub const Inst = struct { return std.mem.sliceAsBytes(mir.extra[bytes.payload..])[0..bytes.len]; } }, - a: struct { - air_inst: Air.Inst.Index, - }, - ai: struct { - air_inst: Air.Inst.Index, - i: u32, - }, - as: struct { - air_inst: Air.Inst.Index, - sym_index: u32, - }, - ax: struct { - air_inst: Air.Inst.Index, - payload: u32, - }, /// Relocation for the linker where: /// * `sym_index` is the index of the target /// * `off` is the offset from the target reloc: bits.SymbolOffset, + fa: bits.FrameAddr, + ro: bits.RegisterOffset, /// Debug line and column position line_column: struct { line: u32, column: u32, }, - func: InternPool.Index, + ip_index: InternPool.Index, /// Register list reg_list: RegisterList, }; @@ -1760,8 +1783,6 @@ pub const Inst = struct { } }; -pub const AirOffset = struct { air_inst: Air.Inst.Index, off: i32 }; - /// Used in conjunction with payload to transfer a list of used registers in a compact manner. pub const RegisterList = struct { bitset: BitSet, @@ -1924,6 +1945,8 @@ pub const Memory = struct { pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.instructions.deinit(gpa); gpa.free(mir.extra); + gpa.free(mir.local_name_bytes); + gpa.free(mir.local_types); gpa.free(mir.table); mir.frame_locs.deinit(gpa); mir.* = undefined; @@ -1937,8 +1960,6 @@ pub fn emit( func_index: InternPool.Index, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, - /// TODO: remove dependency on this argument. This blocks enabling `Zcu.Feature.separate_thread`. - air: *const Air, ) codegen.CodeGenError!void { const zcu = pt.zcu; const comp = zcu.comp; @@ -1948,7 +1969,6 @@ pub fn emit( const nav = func.owner_nav; const mod = zcu.navFileScope(nav).mod.?; var e: Emit = .{ - .air = air.*, .lower = .{ .bin_file = lf, .target = &mod.resolved_target.result, @@ -1998,7 +2018,7 @@ pub fn extraData(mir: Mir, comptime T: type, index: u32) struct { data: T, end: @field(result, field.name) = switch (field.type) { u32 => mir.extra[i], i32, Memory.Info => @bitCast(mir.extra[i]), - bits.FrameIndex, Air.Inst.Index => @enumFromInt(mir.extra[i]), + bits.FrameIndex => @enumFromInt(mir.extra[i]), else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)), }; i += 1; @@ -2043,7 +2063,6 @@ const builtin = @import("builtin"); const encoder = @import("encoder.zig"); const std = @import("std"); -const Air = @import("../../Air.zig"); const IntegerBitSet = std.bit_set.IntegerBitSet; const InternPool = @import("../../InternPool.zig"); const Mir = @This(); diff --git a/src/codegen.zig b/src/codegen.zig index 5a8f17735a..9199c27dc2 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -180,10 +180,6 @@ pub fn emitFunction( any_mir: *const AnyMir, code: *std.ArrayListUnmanaged(u8), debug_output: link.File.DebugInfoOutput, - /// TODO: this parameter needs to be removed. We should not still hold AIR this late - /// in the pipeline. Any information needed to call emit must be stored in MIR. - /// This is `undefined` if the backend supports the `separate_thread` feature. - air: *const Air, ) CodeGenError!void { const zcu = pt.zcu; const func = zcu.funcInfo(func_index); @@ -199,7 +195,7 @@ pub fn emitFunction( => |backend| { dev.check(devFeatureForBackend(backend)); const mir = &@field(any_mir, AnyMir.tag(backend)); - return mir.emit(lf, pt, src_loc, func_index, code, debug_output, air); + return mir.emit(lf, pt, src_loc, func_index, code, debug_output); }, } } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index e30e8f70a3..658764ba3c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -9509,15 +9509,21 @@ pub const FuncGen = struct { const inst_ty = self.typeOfIndex(inst); - const name = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.name; - if (name == .none) return arg_val; - const func = zcu.funcInfo(zcu.navValue(self.ng.nav_index).toIntern()); + const func_zir = func.zir_body_inst.resolveFull(&zcu.intern_pool).?; + const file = zcu.fileByIndex(func_zir.file); + + const mod = file.mod.?; + if (mod.strip) return arg_val; + const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg; + const zir = &file.zir.?; + const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?); + const lbrace_line = zcu.navSrcLine(func.owner_nav) + func.lbrace_line + 1; const lbrace_col = func.lbrace_column + 1; const debug_parameter = try o.builder.debugParameter( - try o.builder.metadataString(name.toSlice(self.air)), + try o.builder.metadataString(name), self.file, self.scope, lbrace_line, @@ -9535,7 +9541,6 @@ pub const FuncGen = struct { }, }; - const mod = self.ng.ownerModule(); if (isByRef(inst_ty, zcu)) { _ = try self.wip.callIntrinsic( .normal, diff --git a/src/link.zig b/src/link.zig index bbd0163d23..844ea7a85c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -8,7 +8,6 @@ const log = std.log.scoped(.link); const trace = @import("tracy.zig").trace; const wasi_libc = @import("libs/wasi_libc.zig"); -const Air = @import("Air.zig"); const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const Path = std.Build.Cache.Path; @@ -752,9 +751,6 @@ pub const File = struct { /// that `mir.deinit` remains legal for the caller. For instance, the callee can /// take ownership of an embedded slice and replace it with `&.{}` in `mir`. mir: *codegen.AnyMir, - /// This may be `undefined`; only pass it to `emitFunction`. - /// This parameter will eventually be removed. - maybe_undef_air: *const Air, ) UpdateNavError!void { assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { @@ -762,7 +758,7 @@ pub const File = struct { .spirv => unreachable, // see corresponding special case in `Zcu.PerThread.runCodegenInner` inline else => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, mir, maybe_undef_air); + return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, mir); }, } } @@ -1271,11 +1267,6 @@ pub const ZcuTask = union(enum) { /// the codegen job to ensure that the linker receives functions in a deterministic order, /// allowing reproducible builds. mir: *SharedMir, - /// This field exists only due to deficiencies in some codegen implementations; it should - /// be removed when the corresponding parameter of `CodeGen.emitFunction` can be removed. - /// This is `undefined` if `Zcu.Feature.separate_thread` is supported. - /// If this is defined, its memory is owned externally; do not `deinit` this `air`. - air: *const Air, pub const SharedMir = struct { /// This is initially `.pending`. When `value` is populated, the codegen thread will set @@ -1458,7 +1449,7 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { assert(zcu.llvm_object == null); // LLVM codegen doesn't produce MIR const mir = &func.mir.value; if (comp.bin_file) |lf| { - lf.updateFunc(pt, func.func, mir, func.air) catch |err| switch (err) { + lf.updateFunc(pt, func.func, mir) catch |err| switch (err) { error.OutOfMemory => return diags.setAllocFailure(), error.CodegenFail => return zcu.assertCodegenFailed(nav), error.Overflow, error.RelocationNotByteAligned => { diff --git a/src/link/C.zig b/src/link/C.zig index 417ebcdee6..f3465055b8 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -17,7 +17,6 @@ const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const Type = @import("../Type.zig"); const Value = @import("../Value.zig"); -const Air = @import("../Air.zig"); const AnyMir = @import("../codegen.zig").AnyMir; pub const zig_h = "#include \"zig.h\"\n"; @@ -182,12 +181,7 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *AnyMir, - /// This may be `undefined`; only pass it to `emitFunction`. - /// This parameter will eventually be removed. - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { - _ = maybe_undef_air; // It would be a bug to use this argument. - const zcu = pt.zcu; const gpa = zcu.gpa; const func = zcu.funcInfo(func_index); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 81376c45d8..c9234b335d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1053,9 +1053,6 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - /// This may be `undefined`; only pass it to `emitFunction`. - /// This parameter will eventually be removed. - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -1084,7 +1081,6 @@ pub fn updateFunc( mir, &code_buffer, .none, - maybe_undef_air, ); try coff.updateNavCode(pt, nav_index, code_buffer.items, .FUNCTION); @@ -3123,7 +3119,6 @@ const link = @import("../link.zig"); const target_util = @import("../target.zig"); const trace = @import("../tracy.zig").trace; -const Air = @import("../Air.zig"); const Compilation = @import("../Compilation.zig"); const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 393cd53774..42d0d74ec5 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -1474,17 +1474,18 @@ pub const WipNav = struct { try cfa.write(wip_nav); } - pub const LocalTag = enum { local_arg, local_var }; - pub fn genLocalDebugInfo( + pub const LocalVarTag = enum { arg, local_var }; + pub fn genLocalVarDebugInfo( wip_nav: *WipNav, - tag: LocalTag, + tag: LocalVarTag, name: []const u8, ty: Type, loc: Loc, ) UpdateError!void { assert(wip_nav.func != .none); try wip_nav.abbrevCode(switch (tag) { - inline else => |ct_tag| @field(AbbrevCode, @tagName(ct_tag)), + .arg => .arg, + .local_var => .local_var, }); try wip_nav.strp(name); try wip_nav.refType(ty); @@ -1492,6 +1493,40 @@ pub const WipNav = struct { wip_nav.any_children = true; } + pub const LocalConstTag = enum { comptime_arg, local_const }; + pub fn genLocalConstDebugInfo( + wip_nav: *WipNav, + src_loc: Zcu.LazySrcLoc, + tag: LocalConstTag, + name: []const u8, + val: Value, + ) UpdateError!void { + assert(wip_nav.func != .none); + const pt = wip_nav.pt; + const zcu = pt.zcu; + const ty = val.typeOf(zcu); + const has_runtime_bits = ty.hasRuntimeBits(zcu); + const has_comptime_state = ty.comptimeOnly(zcu) and try ty.onePossibleValue(pt) == null; + try wip_nav.abbrevCode(if (has_runtime_bits and has_comptime_state) switch (tag) { + .comptime_arg => .comptime_arg_runtime_bits_comptime_state, + .local_const => .local_const_runtime_bits_comptime_state, + } else if (has_comptime_state) switch (tag) { + .comptime_arg => .comptime_arg_comptime_state, + .local_const => .local_const_comptime_state, + } else if (has_runtime_bits) switch (tag) { + .comptime_arg => .comptime_arg_runtime_bits, + .local_const => .local_const_runtime_bits, + } else switch (tag) { + .comptime_arg => .comptime_arg, + .local_const => .local_const, + }); + try wip_nav.strp(name); + try wip_nav.refType(ty); + if (has_runtime_bits) try wip_nav.blockValue(src_loc, val); + if (has_comptime_state) try wip_nav.refValue(val); + wip_nav.any_children = true; + } + pub fn genVarArgsDebugInfo(wip_nav: *WipNav) UpdateError!void { assert(wip_nav.func != .none); try wip_nav.abbrevCode(.is_var_args); @@ -1825,7 +1860,8 @@ pub const WipNav = struct { fn getNavEntry(wip_nav: *WipNav, nav_index: InternPool.Nav.Index) UpdateError!struct { Unit.Index, Entry.Index } { const zcu = wip_nav.pt.zcu; const ip = &zcu.intern_pool; - const unit = try wip_nav.dwarf.getUnit(zcu.fileByIndex(ip.getNav(nav_index).srcInst(ip).resolveFile(ip)).mod.?); + const nav = ip.getNav(nav_index); + const unit = try wip_nav.dwarf.getUnit(zcu.fileByIndex(nav.srcInst(ip).resolveFile(ip)).mod.?); const gop = try wip_nav.dwarf.navs.getOrPut(wip_nav.dwarf.gpa, nav_index); if (gop.found_existing) return .{ unit, gop.value_ptr.* }; const entry = try wip_nav.dwarf.addCommonEntry(unit); @@ -1842,10 +1878,16 @@ pub const WipNav = struct { const zcu = wip_nav.pt.zcu; const ip = &zcu.intern_pool; const maybe_inst_index = ty.typeDeclInst(zcu); - const unit = if (maybe_inst_index) |inst_index| - try wip_nav.dwarf.getUnit(zcu.fileByIndex(inst_index.resolveFile(ip)).mod.?) - else - .main; + const unit = if (maybe_inst_index) |inst_index| switch (switch (ip.indexToKey(ty.toIntern())) { + else => unreachable, + .struct_type => ip.loadStructType(ty.toIntern()).name_nav, + .union_type => ip.loadUnionType(ty.toIntern()).name_nav, + .enum_type => ip.loadEnumType(ty.toIntern()).name_nav, + .opaque_type => ip.loadOpaqueType(ty.toIntern()).name_nav, + }) { + .none => try wip_nav.dwarf.getUnit(zcu.fileByIndex(inst_index.resolveFile(ip)).mod.?), + else => |name_nav| return wip_nav.getNavEntry(name_nav.unwrap().?), + } else .main; const gop = try wip_nav.dwarf.types.getOrPut(wip_nav.dwarf.gpa, ty.toIntern()); if (gop.found_existing) return .{ unit, gop.value_ptr.* }; const entry = try wip_nav.dwarf.addCommonEntry(unit); @@ -1864,10 +1906,8 @@ pub const WipNav = struct { const ip = &zcu.intern_pool; const ty = value.typeOf(zcu); if (std.debug.runtime_safety) assert(ty.comptimeOnly(zcu) and try ty.onePossibleValue(wip_nav.pt) == null); - if (!value.isUndef(zcu)) { - if (ty.toIntern() == .type_type) return wip_nav.getTypeEntry(value.toType()); - if (ip.isFunctionType(ty.toIntern())) return wip_nav.getNavEntry(zcu.funcInfo(value.toIntern()).owner_nav); - } + if (ty.toIntern() == .type_type) return wip_nav.getTypeEntry(value.toType()); + if (ip.isFunctionType(ty.toIntern()) and !value.isUndef(zcu)) return wip_nav.getNavEntry(zcu.funcInfo(value.toIntern()).owner_nav); const gop = try wip_nav.dwarf.values.getOrPut(wip_nav.dwarf.gpa, value.toIntern()); const unit: Unit.Index = .main; if (gop.found_existing) return .{ unit, gop.value_ptr.* }; @@ -1916,7 +1956,10 @@ pub const WipNav = struct { &wip_nav.debug_info, .{ .debug_output = .{ .dwarf = wip_nav } }, ); - assert(old_len + bytes == wip_nav.debug_info.items.len); + if (old_len + bytes != wip_nav.debug_info.items.len) { + std.debug.print("{} [{}]: {} != {}\n", .{ ty.fmt(wip_nav.pt), ty.toIntern(), bytes, wip_nav.debug_info.items.len - old_len }); + unreachable; + } } const AbbrevCodeForForm = struct { @@ -2788,6 +2831,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); if (type_gop.found_existing) { if (dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).len > 0) break :tag .decl_alias; + assert(!nav_gop.found_existing); nav_gop.value_ptr.* = type_gop.value_ptr.*; } else { if (nav_gop.found_existing) @@ -2890,6 +2934,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); if (type_gop.found_existing) { if (dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).len > 0) break :tag .decl_alias; + assert(!nav_gop.found_existing); nav_gop.value_ptr.* = type_gop.value_ptr.*; } else { if (nav_gop.found_existing) @@ -2928,6 +2973,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); if (type_gop.found_existing) { if (dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).len > 0) break :tag .decl_alias; + assert(!nav_gop.found_existing); nav_gop.value_ptr.* = type_gop.value_ptr.*; } else { if (nav_gop.found_existing) @@ -2998,6 +3044,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo const type_gop = try dwarf.types.getOrPut(dwarf.gpa, nav_val.toIntern()); if (type_gop.found_existing) { if (dwarf.debug_info.section.getUnit(wip_nav.unit).getEntry(type_gop.value_ptr.*).len > 0) break :tag .decl_alias; + assert(!nav_gop.found_existing); nav_gop.value_ptr.* = type_gop.value_ptr.*; } else { if (nav_gop.found_existing) @@ -3164,6 +3211,7 @@ fn updateLazyType( ) UpdateError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; + assert(ip.typeOf(type_index) == .type_type); const ty: Type = .fromInterned(type_index); switch (type_index) { .generic_poison_type => log.debug("updateLazyType({s})", .{"anytype"}), @@ -3200,6 +3248,10 @@ fn updateLazyType( defer dwarf.gpa.free(name); switch (ip.indexToKey(type_index)) { + .undef => { + try wip_nav.abbrevCode(.undefined_comptime_value); + try wip_nav.refType(.type); + }, .int_type => |int_type| { try wip_nav.abbrevCode(.numeric_type); try wip_nav.strp(name); @@ -3633,7 +3685,6 @@ fn updateLazyType( }, // values, not types - .undef, .simple_value, .variable, .@"extern", @@ -3666,7 +3717,11 @@ fn updateLazyValue( ) UpdateError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; - log.debug("updateLazyValue({})", .{Value.fromInterned(value_index).fmtValue(pt)}); + assert(ip.typeOf(value_index) != .type_type); + log.debug("updateLazyValue(@as({}, {}))", .{ + Value.fromInterned(value_index).typeOf(zcu).fmt(pt), + Value.fromInterned(value_index).fmtValue(pt), + }); var wip_nav: WipNav = .{ .dwarf = dwarf, .pt = pt, @@ -3710,9 +3765,8 @@ fn updateLazyValue( .inferred_error_set_type, => unreachable, // already handled .undef => |ty| { - try wip_nav.abbrevCode(.aggregate_comptime_value); + try wip_nav.abbrevCode(.undefined_comptime_value); try wip_nav.refType(.fromInterned(ty)); - try uleb128(diw, @intFromEnum(AbbrevCode.null)); }, .simple_value => unreachable, // opv state .variable, .@"extern" => unreachable, // not a value @@ -4890,8 +4944,17 @@ const AbbrevCode = enum { block, empty_inlined_func, inlined_func, - local_arg, + arg, + comptime_arg, + comptime_arg_runtime_bits, + comptime_arg_comptime_state, + comptime_arg_runtime_bits_comptime_state, local_var, + local_const, + local_const_runtime_bits, + local_const_comptime_state, + local_const_runtime_bits_comptime_state, + undefined_comptime_value, data2_comptime_value, data4_comptime_value, data8_comptime_value, @@ -5663,7 +5726,7 @@ const AbbrevCode = enum { .{ .high_pc, .data4 }, }, }, - .local_arg = .{ + .arg = .{ .tag = .formal_parameter, .attrs = &.{ .{ .name, .strp }, @@ -5671,6 +5734,42 @@ const AbbrevCode = enum { .{ .location, .exprloc }, }, }, + .comptime_arg = .{ + .tag = .formal_parameter, + .attrs = &.{ + .{ .const_expr, .flag_present }, + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .comptime_arg_runtime_bits = .{ + .tag = .formal_parameter, + .attrs = &.{ + .{ .const_expr, .flag_present }, + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .const_value, .block }, + }, + }, + .comptime_arg_comptime_state = .{ + .tag = .formal_parameter, + .attrs = &.{ + .{ .const_expr, .flag_present }, + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .ZIG_comptime_value, .ref_addr }, + }, + }, + .comptime_arg_runtime_bits_comptime_state = .{ + .tag = .formal_parameter, + .attrs = &.{ + .{ .const_expr, .flag_present }, + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .const_value, .block }, + .{ .ZIG_comptime_value, .ref_addr }, + }, + }, .local_var = .{ .tag = .variable, .attrs = &.{ @@ -5679,6 +5778,44 @@ const AbbrevCode = enum { .{ .location, .exprloc }, }, }, + .local_const = .{ + .tag = .constant, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + }, + }, + .local_const_runtime_bits = .{ + .tag = .constant, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .const_value, .block }, + }, + }, + .local_const_comptime_state = .{ + .tag = .constant, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .ZIG_comptime_value, .ref_addr }, + }, + }, + .local_const_runtime_bits_comptime_state = .{ + .tag = .constant, + .attrs = &.{ + .{ .name, .strp }, + .{ .type, .ref_addr }, + .{ .const_value, .block }, + .{ .ZIG_comptime_value, .ref_addr }, + }, + }, + .undefined_comptime_value = .{ + .tag = .ZIG_comptime_value, + .attrs = &.{ + .{ .type, .ref_addr }, + }, + }, .data2_comptime_value = .{ .tag = .ZIG_comptime_value, .attrs = &.{ diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 498bc734c3..0beea0d9e7 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1683,12 +1683,11 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - return self.zigObjectPtr().?.updateFunc(self, pt, func_index, mir, maybe_undef_air); + return self.zigObjectPtr().?.updateFunc(self, pt, func_index, mir); } pub fn updateNav( @@ -4516,7 +4515,6 @@ const trace = @import("../tracy.zig").trace; const synthetic_sections = @import("Elf/synthetic_sections.zig"); const Merge = @import("Elf/Merge.zig"); -const Air = @import("../Air.zig"); const Archive = @import("Elf/Archive.zig"); const AtomList = @import("Elf/AtomList.zig"); const Compilation = @import("../Compilation.zig"); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 1a5ef4b408..8478aad8c3 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -1417,9 +1417,6 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - /// This may be `undefined`; only pass it to `emitFunction`. - /// This parameter will eventually be removed. - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1448,7 +1445,6 @@ pub fn updateFunc( mir, &code_buffer, if (debug_wip_nav) |*dn| .{ .dwarf = dn } else .none, - maybe_undef_air, ); const code = code_buffer.items; @@ -2363,7 +2359,6 @@ const trace = @import("../../tracy.zig").trace; const std = @import("std"); const Allocator = std.mem.Allocator; -const Air = @import("../../Air.zig"); const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); const Dwarf = @import("../Dwarf.zig"); diff --git a/src/link/Goff.zig b/src/link/Goff.zig index c222ae029f..ec4cb1252b 100644 --- a/src/link/Goff.zig +++ b/src/link/Goff.zig @@ -17,7 +17,6 @@ const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); -const Air = @import("../Air.zig"); base: link.File, @@ -74,13 +73,11 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { _ = self; _ = pt; _ = func_index; _ = mir; - _ = maybe_undef_air; unreachable; // we always use llvm } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6c081653ea..3f3a94bee7 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3040,12 +3040,11 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - return self.getZigObject().?.updateFunc(self, pt, func_index, mir, maybe_undef_air); + return self.getZigObject().?.updateFunc(self, pt, func_index, mir); } pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { @@ -5431,7 +5430,6 @@ const target_util = @import("../target.zig"); const trace = @import("../tracy.zig").trace; const synthetic = @import("MachO/synthetic.zig"); -const Air = @import("../Air.zig"); const Alignment = Atom.Alignment; const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index f378a9c410..bd54be6caa 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -778,9 +778,6 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - /// This may be `undefined`; only pass it to `emitFunction`. - /// This parameter will eventually be removed. - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { const tracy = trace(@src()); defer tracy.end(); @@ -806,7 +803,6 @@ pub fn updateFunc( mir, &code_buffer, if (debug_wip_nav) |*wip_nav| .{ .dwarf = wip_nav } else .none, - maybe_undef_air, ); const code = code_buffer.items; @@ -1815,7 +1811,6 @@ const target_util = @import("../../target.zig"); const trace = @import("../../tracy.zig").trace; const std = @import("std"); -const Air = @import("../../Air.zig"); const Allocator = std.mem.Allocator; const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 0d0699f0f0..c99ebb81bb 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -387,9 +387,6 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - /// This may be `undefined`; only pass it to `emitFunction`. - /// This parameter will eventually be removed. - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .plan9) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -422,7 +419,6 @@ pub fn updateFunc( mir, &code_buffer, .{ .plan9 = &dbg_info_output }, - maybe_undef_air, ); const code = try code_buffer.toOwnedSlice(gpa); self.getAtomPtr(atom_idx).code = .{ diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 82293b9c45..eda7552986 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -29,7 +29,6 @@ const leb = std.leb; const log = std.log.scoped(.link); const mem = std.mem; -const Air = @import("../Air.zig"); const Mir = @import("../arch/wasm/Mir.zig"); const CodeGen = @import("../arch/wasm/CodeGen.zig"); const abi = @import("../arch/wasm/abi.zig"); @@ -3182,14 +3181,12 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, any_mir: *const codegen.AnyMir, - maybe_undef_air: *const Air, ) !void { if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } dev.check(.wasm_backend); - _ = maybe_undef_air; // we (correctly) do not need this // This linker implementation only works with codegen backend `.stage2_wasm`. const mir = &any_mir.wasm; diff --git a/src/link/Xcoff.zig b/src/link/Xcoff.zig index 93fda27f3f..bbd8a3fea4 100644 --- a/src/link/Xcoff.zig +++ b/src/link/Xcoff.zig @@ -17,7 +17,6 @@ const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); -const Air = @import("../Air.zig"); base: link.File, @@ -74,13 +73,11 @@ pub fn updateFunc( pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, - maybe_undef_air: *const Air, ) link.File.UpdateNavError!void { _ = self; _ = pt; _ = func_index; _ = mir; - _ = maybe_undef_air; unreachable; // we always use llvm } -- cgit v1.2.3 From db5d85b8c89b755bd8865def3bd7114d5d9d4867 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 8 Jun 2025 21:47:29 +0100 Subject: compiler: improve progress output * "Flush" nodes ("LLVM Emit Object", "ELF Flush") appear under "Linking" * "Code Generation" disappears when all analysis and codegen is done * We only show one node under "Semantic Analysis" to accurately convey that analysis isn't happening in parallel, but rather that we're pausing one task to do another --- lib/std/Progress.zig | 22 +++++++++++++++++++ src/Compilation.zig | 58 ++++++++++++++++++++++++++++++++------------------- src/Zcu.zig | 38 +++++++++++++++++++++++++++++++-- src/Zcu/PerThread.zig | 22 ++++++++++++------- src/link.zig | 22 ++++++++++++------- src/link/Lld.zig | 3 +++ src/link/Queue.zig | 2 +- 7 files changed, 126 insertions(+), 41 deletions(-) (limited to 'src/Compilation.zig') diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index d9ff03a3fe..030f3f0a28 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -234,6 +234,28 @@ pub const Node = struct { _ = @atomicRmw(u32, &storage.completed_count, .Add, 1, .monotonic); } + /// Thread-safe. Bytes after '0' in `new_name` are ignored. + pub fn setName(n: Node, new_name: []const u8) void { + const index = n.index.unwrap() orelse return; + const storage = storageByIndex(index); + + const name_len = @min(max_name_len, std.mem.indexOfScalar(u8, new_name, 0) orelse new_name.len); + + copyAtomicStore(storage.name[0..name_len], new_name[0..name_len]); + if (name_len < storage.name.len) + @atomicStore(u8, &storage.name[name_len], 0, .monotonic); + } + + /// Gets the name of this `Node`. + /// A pointer to this array can later be passed to `setName` to restore the name. + pub fn getName(n: Node) [max_name_len]u8 { + var dest: [max_name_len]u8 align(@alignOf(usize)) = undefined; + if (n.index.unwrap()) |index| { + copyAtomicLoad(&dest, &storageByIndex(index).name); + } + return dest; + } + /// Thread-safe. pub fn setCompletedItems(n: Node, completed_items: usize) void { const index = n.index.unwrap() orelse return; diff --git a/src/Compilation.zig b/src/Compilation.zig index fe4671848d..74f841723e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -255,7 +255,7 @@ test_filters: []const []const u8, test_name_prefix: ?[]const u8, link_task_wait_group: WaitGroup = .{}, -work_queue_progress_node: std.Progress.Node = .none, +link_prog_node: std.Progress.Node = std.Progress.Node.none, llvm_opt_bisect_limit: c_int, @@ -2795,6 +2795,17 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } + // The linker progress node is set up here instead of in `performAllTheWork`, because + // we also want it around during `flush`. + const have_link_node = comp.bin_file != null; + if (have_link_node) { + comp.link_prog_node = main_progress_node.start("Linking", 0); + } + defer if (have_link_node) { + comp.link_prog_node.end(); + comp.link_prog_node = .none; + }; + try comp.performAllTheWork(main_progress_node); if (comp.zcu) |zcu| { @@ -2843,7 +2854,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { switch (comp.cache_use) { .none, .incremental => { - try flush(comp, arena, .main, main_progress_node); + try flush(comp, arena, .main); }, .whole => |whole| { if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf); @@ -2919,7 +2930,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } - try flush(comp, arena, .main, main_progress_node); + try flush(comp, arena, .main); // Calling `flush` may have produced errors, in which case the // cache manifest must not be written. @@ -3009,13 +3020,12 @@ fn flush( comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id, - prog_node: std.Progress.Node, ) !void { if (comp.zcu) |zcu| { if (zcu.llvm_object) |llvm_object| { // Emit the ZCU object from LLVM now; it's required to flush the output file. // If there's an output file, it wants to decide where the LLVM object goes! - const sub_prog_node = prog_node.start("LLVM Emit Object", 0); + const sub_prog_node = comp.link_prog_node.start("LLVM Emit Object", 0); defer sub_prog_node.end(); try llvm_object.emit(.{ .pre_ir_path = comp.verbose_llvm_ir, @@ -3053,7 +3063,7 @@ fn flush( } if (comp.bin_file) |lf| { // This is needed before reading the error flags. - lf.flush(arena, tid, prog_node) catch |err| switch (err) { + lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) { error.LinkFailure => {}, // Already reported. error.OutOfMemory => return error.OutOfMemory, }; @@ -4172,28 +4182,15 @@ pub fn addWholeFileError( } } -pub fn performAllTheWork( +fn performAllTheWork( comp: *Compilation, main_progress_node: std.Progress.Node, ) JobError!void { - comp.work_queue_progress_node = main_progress_node; - defer comp.work_queue_progress_node = .none; - + // Regardless of errors, `comp.zcu` needs to update its generation number. defer if (comp.zcu) |zcu| { - zcu.sema_prog_node.end(); - zcu.sema_prog_node = .none; - zcu.codegen_prog_node.end(); - zcu.codegen_prog_node = .none; - zcu.generation += 1; }; - try comp.performAllTheWorkInner(main_progress_node); -} -fn performAllTheWorkInner( - comp: *Compilation, - main_progress_node: std.Progress.Node, -) JobError!void { // Here we queue up all the AstGen tasks first, followed by C object compilation. // We wait until the AstGen tasks are all completed before proceeding to the // (at least for now) single-threaded main work queue. However, C object compilation @@ -4513,8 +4510,24 @@ fn performAllTheWorkInner( } zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); - zcu.codegen_prog_node = if (comp.bin_file != null) main_progress_node.start("Code Generation", 0) else .none; + if (comp.bin_file != null) { + zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); + } + // We increment `pending_codegen_jobs` so that it doesn't reach 0 until after analysis finishes. + // That prevents the "Code Generation" node from constantly disappearing and reappearing when + // we're probably going to analyze more functions at some point. + assert(zcu.pending_codegen_jobs.swap(1, .monotonic) == 0); // don't let this become 0 until analysis finishes } + // When analysis ends, delete the progress nodes for "Semantic Analysis" and possibly "Code Generation". + defer if (comp.zcu) |zcu| { + zcu.sema_prog_node.end(); + zcu.sema_prog_node = .none; + if (zcu.pending_codegen_jobs.rmw(.Sub, 1, .monotonic) == 1) { + // Decremented to 0, so all done. + zcu.codegen_prog_node.end(); + zcu.codegen_prog_node = .none; + } + }; if (!comp.separateCodegenThreadOk()) { // Waits until all input files have been parsed. @@ -4583,6 +4596,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { .status = .init(.pending), .value = undefined, }; + assert(zcu.pending_codegen_jobs.rmw(.Add, 1, .monotonic) > 0); // the "Code Generation" node hasn't been ended if (comp.separateCodegenThreadOk()) { // `workerZcuCodegen` takes ownership of `air`. comp.thread_pool.spawnWgId(&comp.link_task_wait_group, workerZcuCodegen, .{ comp, func.func, air, shared_mir }); diff --git a/src/Zcu.zig b/src/Zcu.zig index 91d2c0ffff..513492e818 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -66,8 +66,18 @@ root_mod: *Package.Module, /// `root_mod` is the test runner, and `main_mod` is the user's source file which has the tests. main_mod: *Package.Module, std_mod: *Package.Module, -sema_prog_node: std.Progress.Node = std.Progress.Node.none, -codegen_prog_node: std.Progress.Node = std.Progress.Node.none, +sema_prog_node: std.Progress.Node = .none, +codegen_prog_node: std.Progress.Node = .none, +/// The number of codegen jobs which are pending or in-progress. Whichever thread drops this value +/// to 0 is responsible for ending `codegen_prog_node`. While semantic analysis is happening, this +/// value bottoms out at 1 instead of 0, to ensure that it can only drop to 0 after analysis is +/// completed (since semantic analysis could trigger more codegen work). +pending_codegen_jobs: std.atomic.Value(u32) = .init(0), + +/// This is the progress node *under* `sema_prog_node` which is currently running. +/// When we have to pause to analyze something else, we just temporarily rename this node. +/// Eventually, when we thread semantic analysis, we will want one of these per thread. +cur_sema_prog_node: std.Progress.Node = .none, /// Used by AstGen worker to load and store ZIR cache. global_zir_cache: Cache.Directory, @@ -4753,3 +4763,27 @@ fn explainWhyFileIsInModule( import = importer_ref.import; } } + +const SemaProgNode = struct { + /// `null` means we created the node, so should end it. + old_name: ?[std.Progress.Node.max_name_len]u8, + pub fn end(spn: SemaProgNode, zcu: *Zcu) void { + if (spn.old_name) |old_name| { + zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion + zcu.cur_sema_prog_node.setName(&old_name); + } else { + zcu.cur_sema_prog_node.end(); + zcu.cur_sema_prog_node = .none; + } + } +}; +pub fn startSemaProgNode(zcu: *Zcu, name: []const u8) SemaProgNode { + if (zcu.cur_sema_prog_node.index != .none) { + const old_name = zcu.cur_sema_prog_node.getName(); + zcu.cur_sema_prog_node.setName(name); + return .{ .old_name = old_name }; + } else { + zcu.cur_sema_prog_node = zcu.sema_prog_node.start(name, 0); + return .{ .old_name = null }; + } +} diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index f8efa40dc0..8bc723f2e8 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -796,8 +796,8 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU info.deps.clearRetainingCapacity(); } - const unit_prog_node = zcu.sema_prog_node.start("comptime", 0); - defer unit_prog_node.end(); + const unit_prog_node = zcu.startSemaProgNode("comptime"); + defer unit_prog_node.end(zcu); return pt.analyzeComptimeUnit(cu_id) catch |err| switch (err) { error.AnalysisFail => { @@ -976,8 +976,8 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu info.deps.clearRetainingCapacity(); } - const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); - defer unit_prog_node.end(); + const unit_prog_node = zcu.startSemaProgNode(nav.fqn.toSlice(ip)); + defer unit_prog_node.end(zcu); const invalidate_value: bool, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: { break :res .{ @@ -1396,8 +1396,8 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc info.deps.clearRetainingCapacity(); } - const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); - defer unit_prog_node.end(); + const unit_prog_node = zcu.startSemaProgNode(nav.fqn.toSlice(ip)); + defer unit_prog_node.end(zcu); const invalidate_type: bool, const new_failed: bool = if (pt.analyzeNavType(nav_id)) |result| res: { break :res .{ @@ -1617,8 +1617,8 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter info.deps.clearRetainingCapacity(); } - const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); - defer func_prog_node.end(); + const func_prog_node = zcu.startSemaProgNode(ip.getNav(func.owner_nav).fqn.toSlice(ip)); + defer func_prog_node.end(zcu); const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index)) |result| .{ prev_failed or result.ies_outdated, false } @@ -3360,6 +3360,7 @@ pub fn populateTestFunctions( ip.mutateVarInit(test_fns_val.toIntern(), new_init); } { + assert(zcu.codegen_prog_node.index == .none); zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); defer { zcu.codegen_prog_node.end(); @@ -4393,6 +4394,11 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, ou }, } zcu.comp.link_task_queue.mirReady(zcu.comp, out); + if (zcu.pending_codegen_jobs.rmw(.Sub, 1, .monotonic) == 1) { + // Decremented to 0, so all done. + zcu.codegen_prog_node.end(); + zcu.codegen_prog_node = .none; + } } fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{ OutOfMemory, diff --git a/src/link.zig b/src/link.zig index 844ea7a85c..7d522b94d3 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1074,7 +1074,7 @@ pub const File = struct { /// Called when all linker inputs have been sent via `loadInput`. After /// this, `loadInput` will not be called anymore. - pub fn prelink(base: *File, prog_node: std.Progress.Node) FlushError!void { + pub fn prelink(base: *File) FlushError!void { assert(!base.post_prelink); // In this case, an object file is created by the LLVM backend, so @@ -1085,7 +1085,7 @@ pub const File = struct { switch (base.tag) { inline .wasm => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(prog_node); + return @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(base.comp.link_prog_node); }, else => {}, } @@ -1293,7 +1293,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { const base = comp.bin_file orelse return; switch (task) { .load_explicitly_provided => { - const prog_node = comp.work_queue_progress_node.start("Parse Linker Inputs", comp.link_inputs.len); + const prog_node = comp.link_prog_node.start("Parse Inputs", comp.link_inputs.len); defer prog_node.end(); for (comp.link_inputs) |input| { base.loadInput(input) catch |err| switch (err) { @@ -1310,7 +1310,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { } }, .load_host_libc => { - const prog_node = comp.work_queue_progress_node.start("Linker Parse Host libc", 0); + const prog_node = comp.link_prog_node.start("Parse Host libc", 0); defer prog_node.end(); const target = comp.root_mod.resolved_target.result; @@ -1369,7 +1369,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { } }, .load_object => |path| { - const prog_node = comp.work_queue_progress_node.start("Linker Parse Object", 0); + const prog_node = comp.link_prog_node.start("Parse Object", 0); defer prog_node.end(); base.openLoadObject(path) catch |err| switch (err) { error.LinkFailure => return, // error reported via diags @@ -1377,7 +1377,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }; }, .load_archive => |path| { - const prog_node = comp.work_queue_progress_node.start("Linker Parse Archive", 0); + const prog_node = comp.link_prog_node.start("Parse Archive", 0); defer prog_node.end(); base.openLoadArchive(path, null) catch |err| switch (err) { error.LinkFailure => return, // error reported via link_diags @@ -1385,7 +1385,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }; }, .load_dso => |path| { - const prog_node = comp.work_queue_progress_node.start("Linker Parse Shared Library", 0); + const prog_node = comp.link_prog_node.start("Parse Shared Library", 0); defer prog_node.end(); base.openLoadDso(path, .{ .preferred_mode = .dynamic, @@ -1396,7 +1396,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }; }, .load_input => |input| { - const prog_node = comp.work_queue_progress_node.start("Linker Parse Input", 0); + const prog_node = comp.link_prog_node.start("Parse Input", 0); defer prog_node.end(); base.loadInput(input) catch |err| switch (err) { error.LinkFailure => return, // error reported via link_diags @@ -1418,6 +1418,9 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { const zcu = comp.zcu.?; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); + const fqn_slice = zcu.intern_pool.getNav(nav_index).fqn.toSlice(&zcu.intern_pool); + const nav_prog_node = comp.link_prog_node.start(fqn_slice, 0); + defer nav_prog_node.end(); if (zcu.llvm_object) |llvm_object| { llvm_object.updateNav(pt, nav_index) catch |err| switch (err) { error.OutOfMemory => diags.setAllocFailure(), @@ -1441,6 +1444,9 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { const nav = zcu.funcInfo(func.func).owner_nav; const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); defer pt.deactivate(); + const fqn_slice = zcu.intern_pool.getNav(nav).fqn.toSlice(&zcu.intern_pool); + const nav_prog_node = comp.link_prog_node.start(fqn_slice, 0); + defer nav_prog_node.end(); switch (func.mir.status.load(.monotonic)) { .pending => unreachable, .ready => {}, diff --git a/src/link/Lld.zig b/src/link/Lld.zig index dd50bd2a2f..4ea809428e 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -267,6 +267,9 @@ pub fn flush( const comp = lld.base.comp; const result = if (comp.config.output_mode == .Lib and comp.config.link_mode == .static) r: { + if (!@import("build_options").have_llvm or !comp.config.use_lib_llvm) { + return lld.base.comp.link_diags.fail("using lld without libllvm not implemented", .{}); + } break :r linkAsArchive(lld, arena); } else switch (lld.ofmt) { .coff => coffLink(lld, arena), diff --git a/src/link/Queue.zig b/src/link/Queue.zig index 3436be5921..ab5fd89699 100644 --- a/src/link/Queue.zig +++ b/src/link/Queue.zig @@ -180,7 +180,7 @@ fn flushTaskQueue(tid: usize, q: *Queue, comp: *Compilation) void { // We've finished the prelink tasks, so run prelink if necessary. if (comp.bin_file) |lf| { if (!lf.post_prelink) { - if (lf.prelink(comp.work_queue_progress_node)) |_| { + if (lf.prelink()) |_| { lf.post_prelink = true; } else |err| switch (err) { error.OutOfMemory => comp.link_diags.setAllocFailure(), -- cgit v1.2.3 From ac745edbbd6687c5898bb3a50bf9d31d86e57b9e Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 8 Jun 2025 16:25:28 +0100 Subject: compiler: estimate totals for "Code Generation" and "Linking" progress nodes --- src/Compilation.zig | 6 ++++++ src/link.zig | 29 +++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 74f841723e..04cd03c3d8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4202,6 +4202,10 @@ fn performAllTheWork( comp.link_task_wait_group.reset(); defer comp.link_task_wait_group.wait(); + comp.link_prog_node.increaseEstimatedTotalItems( + comp.link_task_queue.queued_prelink.items.len + // already queued prelink tasks + comp.link_task_queue.pending_prelink_tasks, // prelink tasks which will be queued + ); comp.link_task_queue.start(comp); if (comp.emit_docs != null) { @@ -4597,6 +4601,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { .value = undefined, }; assert(zcu.pending_codegen_jobs.rmw(.Add, 1, .monotonic) > 0); // the "Code Generation" node hasn't been ended + zcu.codegen_prog_node.increaseEstimatedTotalItems(1); if (comp.separateCodegenThreadOk()) { // `workerZcuCodegen` takes ownership of `air`. comp.thread_pool.spawnWgId(&comp.link_task_wait_group, workerZcuCodegen, .{ comp, func.func, air, shared_mir }); @@ -7444,6 +7449,7 @@ pub fn queuePrelinkTasks(comp: *Compilation, tasks: []const link.PrelinkTask) vo /// The reason for the double-queue here is that the first queue ensures any /// resolve_type_fully tasks are complete before this dispatch function is called. fn dispatchZcuLinkTask(comp: *Compilation, tid: usize, task: link.ZcuTask) void { + comp.link_prog_node.increaseEstimatedTotalItems(1); if (!comp.separateCodegenThreadOk()) { assert(tid == 0); if (task == .link_func) { diff --git a/src/link.zig b/src/link.zig index 7d522b94d3..ce98ac8929 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1290,7 +1290,10 @@ pub const ZcuTask = union(enum) { pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { const diags = &comp.link_diags; - const base = comp.bin_file orelse return; + const base = comp.bin_file orelse { + comp.link_prog_node.completeOne(); + return; + }; switch (task) { .load_explicitly_provided => { const prog_node = comp.link_prog_node.start("Parse Inputs", comp.link_inputs.len); @@ -1413,12 +1416,13 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { } pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { const diags = &comp.link_diags; + const zcu = comp.zcu.?; + const ip = &zcu.intern_pool; + const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); + defer pt.deactivate(); switch (task) { .link_nav => |nav_index| { - const zcu = comp.zcu.?; - const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); - defer pt.deactivate(); - const fqn_slice = zcu.intern_pool.getNav(nav_index).fqn.toSlice(&zcu.intern_pool); + const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip); const nav_prog_node = comp.link_prog_node.start(fqn_slice, 0); defer nav_prog_node.end(); if (zcu.llvm_object) |llvm_object| { @@ -1440,11 +1444,8 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { } }, .link_func => |func| { - const zcu = comp.zcu.?; const nav = zcu.funcInfo(func.func).owner_nav; - const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); - defer pt.deactivate(); - const fqn_slice = zcu.intern_pool.getNav(nav).fqn.toSlice(&zcu.intern_pool); + const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); const nav_prog_node = comp.link_prog_node.start(fqn_slice, 0); defer nav_prog_node.end(); switch (func.mir.status.load(.monotonic)) { @@ -1468,9 +1469,9 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { } }, .link_type => |ty| { - const zcu = comp.zcu.?; - const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid)); - defer pt.deactivate(); + const name = Type.fromInterned(ty).containerTypeName(ip).toSlice(ip); + const nav_prog_node = comp.link_prog_node.start(name, 0); + defer nav_prog_node.end(); if (zcu.llvm_object == null) { if (comp.bin_file) |lf| { lf.updateContainerType(pt, ty) catch |err| switch (err) { @@ -1481,8 +1482,8 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { } }, .update_line_number => |ti| { - const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); - defer pt.deactivate(); + const nav_prog_node = comp.link_prog_node.start("Update line number", 0); + defer nav_prog_node.end(); if (pt.zcu.llvm_object == null) { if (comp.bin_file) |lf| { lf.updateLineNumber(pt, ti) catch |err| switch (err) { -- cgit v1.2.3 From d7afd797ccdeeab74946f047c3e755f33b5ea9b9 Mon Sep 17 00:00:00 2001 From: mlugg Date: Wed, 11 Jun 2025 14:27:21 +0100 Subject: Zcu: handle unreferenced `test_functions` correctly Previously, `PerThread.populateTestFunctions` was analyzing the `test_functions` declaration if it hadn't already been analyzed, so that it could then populate it. However, the logic for doing this wasn't actually correct, because it didn't trigger the necessary type resolution. I could have tried to fix this, but there's actually a simpler solution! If the `test_functions` declaration isn't referenced or has a compile error, then we simply don't need to update it; either it's unreferenced so its value doesn't matter, or we're going to get a compile error anyway. Either way, we can just give up early. This avoids doing semantic analysis after `performAllTheWork` finishes. Also, get rid of the "Code Generation" progress node while updating the test decl: this is a linking task. --- src/Compilation.zig | 2 +- src/Zcu/PerThread.zig | 58 +++++++++++++++++++++++---------------------------- 2 files changed, 27 insertions(+), 33 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 04cd03c3d8..49b2a6b8b6 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2817,7 +2817,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { // The `test_functions` decl has been intentionally postponed until now, // at which point we must populate it with the list of test functions that // have been discovered and not filtered out. - try pt.populateTestFunctions(main_progress_node); + try pt.populateTestFunctions(); } try pt.processExports(); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 7416ebbaab..4ec6eebc46 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -3229,39 +3229,42 @@ fn processExportsInner( } } -pub fn populateTestFunctions( - pt: Zcu.PerThread, - main_progress_node: std.Progress.Node, -) Allocator.Error!void { +pub fn populateTestFunctions(pt: Zcu.PerThread) Allocator.Error!void { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; + + // Our job is to correctly set the value of the `test_functions` declaration if it has been + // analyzed and sent to codegen, It usually will have been, because the test runner will + // reference it, and `std.builtin` shouldn't have type errors. However, if it hasn't been + // analyzed, we will just terminate early, since clearly the test runner hasn't referenced + // `test_functions` so there's no point populating it. More to the the point, we potentially + // *can't* populate it without doing some type resolution, and... let's try to leave Sema in + // the past here. + const builtin_mod = zcu.builtin_modules.get(zcu.root_mod.getBuiltinOptions(zcu.comp.config).hash()).?; const builtin_file_index = zcu.module_roots.get(builtin_mod).?.unwrap().?; - pt.ensureFileAnalyzed(builtin_file_index) catch |err| switch (err) { - error.AnalysisFail => unreachable, // builtin module is generated so cannot be corrupt - error.OutOfMemory => |e| return e, - }; - const builtin_root_type = Type.fromInterned(zcu.fileRootType(builtin_file_index)); - const builtin_namespace = builtin_root_type.getNamespace(zcu).unwrap().?; + const builtin_root_type = zcu.fileRootType(builtin_file_index); + if (builtin_root_type == .none) return; // `@import("builtin")` never analyzed + const builtin_namespace = Type.fromInterned(builtin_root_type).getNamespace(zcu).unwrap().?; + // We know that the namespace has a `test_functions`... const nav_index = zcu.namespacePtr(builtin_namespace).pub_decls.getKeyAdapted( try ip.getOrPutString(gpa, pt.tid, "test_functions", .no_embedded_nulls), Zcu.Namespace.NameAdapter{ .zcu = zcu }, ).?; + // ...but it might not be populated, so let's check that! + if (zcu.failed_analysis.contains(.wrap(.{ .nav_val = nav_index })) or + zcu.transitive_failed_analysis.contains(.wrap(.{ .nav_val = nav_index })) or + ip.getNav(nav_index).status != .fully_resolved) { - // We have to call `ensureNavValUpToDate` here in case `builtin.test_functions` - // was not referenced by start code. - zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); - defer { - zcu.sema_prog_node.end(); - zcu.sema_prog_node = std.Progress.Node.none; - } - pt.ensureNavValUpToDate(nav_index) catch |err| switch (err) { - error.AnalysisFail => return, - error.OutOfMemory => return error.OutOfMemory, - }; + // The value of `builtin.test_functions` was either never referenced, or failed analysis. + // Either way, we don't need to do anything. + return; } + // Okay, `builtin.test_functions` is (potentially) referenced and valid. Our job now is to swap + // its placeholder `&.{}` value for the actual list of all test functions. + const test_fns_val = zcu.navValue(nav_index); const test_fn_ty = test_fns_val.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu); @@ -3363,17 +3366,8 @@ pub fn populateTestFunctions( } }); ip.mutateVarInit(test_fns_val.toIntern(), new_init); } - { - assert(zcu.codegen_prog_node.index == .none); - zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); - defer { - zcu.codegen_prog_node.end(); - zcu.codegen_prog_node = std.Progress.Node.none; - } - - // The linker thread is not running, so we actually need to dispatch this task directly. - @import("../link.zig").linkTestFunctionsNav(pt, nav_index); - } + // The linker thread is not running, so we actually need to dispatch this task directly. + @import("../link.zig").linkTestFunctionsNav(pt, nav_index); } /// Stores an error in `pt.zcu.failed_files` for this file, and sets the file -- cgit v1.2.3 From f9a670d46de3c62be16202f186eacfee6ec096d4 Mon Sep 17 00:00:00 2001 From: mlugg Date: Wed, 11 Jun 2025 22:00:41 +0100 Subject: Compilation: prevent zig1 depending on fd_readdir This isn't really coherent to model as a `Feature`; this makes sense because of zig1's specific environment. As such, I opted to check `dev.env` directly. --- src/Compilation.zig | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 49b2a6b8b6..9f851cf135 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2589,6 +2589,11 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { if (none.tmp_artifact_directory) |*tmp_dir| { tmp_dir.handle.close(); none.tmp_artifact_directory = null; + if (dev.env == .bootstrap) { + // zig1 uses `CacheMode.none`, but it doesn't need to know how to delete + // temporary directories; it doesn't have a real cache directory anyway. + return; + } const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ -- cgit v1.2.3 From 5bb5aaf932b8ed30aebfbb0036e1532abfc6af46 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 12 Jun 2025 09:56:37 +0100 Subject: compiler: don't queue too much AIR/MIR Without this cap, unlucky scheduling and/or details of what pipeline stages perform best on the host machine could cause many gigabytes of MIR to be stuck in the queue. At a certain point, pause the main thread until some of the functions in flight have been processed. --- src/Compilation.zig | 6 ++++++ src/link.zig | 5 +++++ src/link/Queue.zig | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 9f851cf135..ad184b2bc9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4607,12 +4607,17 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { }; assert(zcu.pending_codegen_jobs.rmw(.Add, 1, .monotonic) > 0); // the "Code Generation" node hasn't been ended zcu.codegen_prog_node.increaseEstimatedTotalItems(1); + // This value is used as a heuristic to avoid queueing too much AIR/MIR at once (hence + // using a lot of memory). If this would cause too many AIR bytes to be in-flight, we + // will block on the `dispatchZcuLinkTask` call below. + const air_bytes: u32 = @intCast(air.instructions.len * 5 + air.extra.items.len * 4); if (comp.separateCodegenThreadOk()) { // `workerZcuCodegen` takes ownership of `air`. comp.thread_pool.spawnWgId(&comp.link_task_wait_group, workerZcuCodegen, .{ comp, func.func, air, shared_mir }); comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ .func = func.func, .mir = shared_mir, + .air_bytes = air_bytes, } }); } else { { @@ -4624,6 +4629,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { comp.dispatchZcuLinkTask(tid, .{ .link_func = .{ .func = func.func, .mir = shared_mir, + .air_bytes = air_bytes, } }); air.deinit(gpa); } diff --git a/src/link.zig b/src/link.zig index ce98ac8929..9bed6b4131 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1267,6 +1267,11 @@ pub const ZcuTask = union(enum) { /// the codegen job to ensure that the linker receives functions in a deterministic order, /// allowing reproducible builds. mir: *SharedMir, + /// This is not actually used by `doZcuTask`. Instead, `Queue` uses this value as a heuristic + /// to avoid queueing too much AIR/MIR for codegen/link at a time. Essentially, we cap the + /// total number of AIR bytes which are being processed at once, preventing unbounded memory + /// usage when AIR is produced faster than it is processed. + air_bytes: u32, pub const SharedMir = struct { /// This is initially `.pending`. When `value` is populated, the codegen thread will set diff --git a/src/link/Queue.zig b/src/link/Queue.zig index 16ee701771..d197edab02 100644 --- a/src/link/Queue.zig +++ b/src/link/Queue.zig @@ -39,6 +39,21 @@ wip_zcu: std.ArrayListUnmanaged(ZcuTask), /// index into `wip_zcu` which we have reached. wip_zcu_idx: usize, +/// The sum of all `air_bytes` for all currently-queued `ZcuTask.link_func` tasks. Because +/// MIR bytes are approximately proportional to AIR bytes, this acts to limit the amount of +/// AIR and MIR which is queued for codegen and link respectively, to prevent excessive +/// memory usage if analysis produces AIR faster than it can be processed by codegen/link. +/// The cap is `max_air_bytes_in_flight`. +/// Guarded by `mutex`. +air_bytes_in_flight: u32, +/// If nonzero, then a call to `enqueueZcu` is blocked waiting to add a `link_func` task, but +/// cannot until `air_bytes_in_flight` is no greater than this value. +/// Guarded by `mutex`. +air_bytes_waiting: u32, +/// After setting `air_bytes_waiting`, `enqueueZcu` will wait on this condition (with `mutex`). +/// When `air_bytes_waiting` many bytes can be queued, this condition should be signaled. +air_bytes_cond: std.Thread.Condition, + /// Guarded by `mutex`. state: union(enum) { /// The link thread is currently running or queued to run. @@ -52,6 +67,11 @@ state: union(enum) { wait_for_mir: *ZcuTask.LinkFunc.SharedMir, }, +/// In the worst observed case, MIR is around 50 times as large as AIR. More typically, the ratio is +/// around 20. Going by that 50x multiplier, and assuming we want to consume no more than 500 MiB of +/// memory on AIR/MIR, we see a limit of around 10 MiB of AIR in-flight. +const max_air_bytes_in_flight = 10 * 1024 * 1024; + /// The initial `Queue` state, containing no tasks, expecting no prelink tasks, and with no running worker thread. /// The `pending_prelink_tasks` and `queued_prelink` fields may be modified as needed before calling `start`. pub const empty: Queue = .{ @@ -64,6 +84,9 @@ pub const empty: Queue = .{ .wip_zcu = .empty, .wip_zcu_idx = 0, .state = .finished, + .air_bytes_in_flight = 0, + .air_bytes_waiting = 0, + .air_bytes_cond = .{}, }; /// `lf` is needed to correctly deinit any pending `ZcuTask`s. pub fn deinit(q: *Queue, comp: *Compilation) void { @@ -131,6 +154,16 @@ pub fn enqueueZcu(q: *Queue, comp: *Compilation, task: ZcuTask) Allocator.Error! { q.mutex.lock(); defer q.mutex.unlock(); + // If this is a `link_func` task, we might need to wait for `air_bytes_in_flight` to fall. + if (task == .link_func) { + const max_in_flight = max_air_bytes_in_flight -| task.link_func.air_bytes; + while (q.air_bytes_in_flight > max_in_flight) { + q.air_bytes_waiting = task.link_func.air_bytes; + q.air_bytes_cond.wait(&q.mutex); + q.air_bytes_waiting = 0; + } + q.air_bytes_in_flight += task.link_func.air_bytes; + } try q.queued_zcu.append(comp.gpa, task); switch (q.state) { .running, .wait_for_mir => return, @@ -221,6 +254,17 @@ fn flushTaskQueue(tid: usize, q: *Queue, comp: *Compilation) void { } link.doZcuTask(comp, tid, task); task.deinit(comp.zcu.?); + if (task == .link_func) { + // Decrease `air_bytes_in_flight`, since we've finished processing this MIR. + q.mutex.lock(); + defer q.mutex.unlock(); + q.air_bytes_in_flight -= task.link_func.air_bytes; + if (q.air_bytes_waiting != 0 and + q.air_bytes_in_flight <= max_air_bytes_in_flight -| q.air_bytes_waiting) + { + q.air_bytes_cond.signal(); + } + } q.wip_zcu_idx += 1; } } -- cgit v1.2.3 From 71baa5e769b3b82468736a60e0725a94da9be4e9 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 12 Jun 2025 13:53:41 +0100 Subject: compiler: improve progress output Update the estimated total items for the codegen and link progress nodes earlier. Rather than waiting for the main thread to dispatch the tasks, we can add the item to the estimated total as soon as we queue the main task. The only difference is we need to complete it even in error cases. --- src/Compilation.zig | 18 +++++++++++++++--- src/Sema.zig | 8 ++++++++ src/Sema/LowerZon.zig | 1 + src/Zcu/PerThread.zig | 7 +++++++ 4 files changed, 31 insertions(+), 3 deletions(-) (limited to 'src/Compilation.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index ad184b2bc9..065b717931 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -848,6 +848,8 @@ const Job = union(enum) { /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that /// all types are resolved before the linker task is queued. /// If the backend does not support `Zcu.Feature.separate_thread`, codegen and linking happen immediately. + /// Before queueing this `Job`, increase the estimated total item count for both + /// `comp.zcu.?.codegen_prog_node` and `comp.link_prog_node`. codegen_func: struct { func: InternPool.Index, /// The AIR emitted from analyzing `func`; owned by this `Job` in `gpa`. @@ -857,12 +859,15 @@ const Job = union(enum) { /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that /// all types are resolved before the linker task is queued. /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately. + /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`. link_nav: InternPool.Nav.Index, /// Queue a `link.ZcuTask` to emit debug information for this container type. /// This `Job` exists (instead of the `link.ZcuTask` being directly queued) to ensure that /// all types are resolved before the linker task is queued. /// If the backend does not support `Zcu.Feature.separate_thread`, the task is run immediately. + /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`. link_type: InternPool.Index, + /// Before queueing this `Job`, increase the estimated total item count for `comp.link_prog_node`. update_line_number: InternPool.TrackedInst.Index, /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. @@ -4592,11 +4597,17 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { const zcu = comp.zcu.?; const gpa = zcu.gpa; var air = func.air; - errdefer air.deinit(gpa); + errdefer { + zcu.codegen_prog_node.completeOne(); + comp.link_prog_node.completeOne(); + air.deinit(gpa); + } if (!air.typesFullyResolved(zcu)) { // Type resolution failed in a way which affects this function. This is a transitive // failure, but it doesn't need recording, because this function semantically depends // on the failed type, so when it is changed the function is updated. + zcu.codegen_prog_node.completeOne(); + comp.link_prog_node.completeOne(); air.deinit(gpa); return; } @@ -4606,7 +4617,6 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { .value = undefined, }; assert(zcu.pending_codegen_jobs.rmw(.Add, 1, .monotonic) > 0); // the "Code Generation" node hasn't been ended - zcu.codegen_prog_node.increaseEstimatedTotalItems(1); // This value is used as a heuristic to avoid queueing too much AIR/MIR at once (hence // using a lot of memory). If this would cause too many AIR bytes to be in-flight, we // will block on the `dispatchZcuLinkTask` call below. @@ -4640,6 +4650,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { if (nav.analysis != null) { const unit: InternPool.AnalUnit = .wrap(.{ .nav_val = nav_index }); if (zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit)) { + comp.link_prog_node.completeOne(); return; } } @@ -4648,6 +4659,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { // Type resolution failed in a way which affects this `Nav`. This is a transitive // failure, but it doesn't need recording, because this `Nav` semantically depends // on the failed type, so when it is changed the `Nav` will be updated. + comp.link_prog_node.completeOne(); return; } comp.dispatchZcuLinkTask(tid, .{ .link_nav = nav_index }); @@ -4659,6 +4671,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { // Type resolution failed in a way which affects this type. This is a transitive // failure, but it doesn't need recording, because this type semantically depends // on the failed type, so when that is changed, this type will be updated. + comp.link_prog_node.completeOne(); return; } comp.dispatchZcuLinkTask(tid, .{ .link_type = ty }); @@ -7460,7 +7473,6 @@ pub fn queuePrelinkTasks(comp: *Compilation, tasks: []const link.PrelinkTask) vo /// The reason for the double-queue here is that the first queue ensures any /// resolve_type_fully tasks are complete before this dispatch function is called. fn dispatchZcuLinkTask(comp: *Compilation, tid: usize, task: link.ZcuTask) void { - comp.link_prog_node.increaseEstimatedTotalItems(1); if (!comp.separateCodegenThreadOk()) { assert(tid == 0); if (task == .link_func) { diff --git a/src/Sema.zig b/src/Sema.zig index 310058a421..5b3e6419a6 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2992,6 +2992,7 @@ fn zirStructDecl( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } try sema.declareDependency(.{ .interned = wip_ty.index }); @@ -3266,6 +3267,7 @@ fn zirEnumDecl( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } return Air.internedToRef(wip_ty.index); @@ -3385,6 +3387,7 @@ fn zirUnionDecl( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } try sema.declareDependency(.{ .interned = wip_ty.index }); @@ -3473,6 +3476,7 @@ fn zirOpaqueDecl( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } try sema.addTypeReferenceEntry(src, wip_ty.index); @@ -20105,6 +20109,7 @@ fn structInitAnon( codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip.index }); } if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index); @@ -21417,6 +21422,7 @@ fn reifyEnum( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } return Air.internedToRef(wip_ty.index); @@ -21671,6 +21677,7 @@ fn reifyUnion( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } try sema.declareDependency(.{ .interned = wip_ty.index }); @@ -22026,6 +22033,7 @@ fn reifyStruct( if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } try sema.declareDependency(.{ .interned = wip_ty.index }); diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index b8064cefbf..8dfb710ac0 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -195,6 +195,7 @@ fn lowerExprAnonResTy(self: *LowerZon, node: Zoir.Node.Index) CompileError!Inter codegen_type: { if (pt.zcu.comp.config.use_llvm) break :codegen_type; if (self.block.ownerModule().strip) break :codegen_type; + pt.zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try pt.zcu.comp.queueJob(.{ .link_type = wip.index }); } break :ty wip.finish(ip, new_namespace_index); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 4ec6eebc46..4d90878420 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1321,6 +1321,7 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr } // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_nav = nav_id }); } @@ -1717,6 +1718,8 @@ fn analyzeFuncBody( } // This job depends on any resolve_type_fully jobs queued up before it. + zcu.codegen_prog_node.increaseEstimatedTotalItems(1); + comp.link_prog_node.increaseEstimatedTotalItems(1); try comp.queueJob(.{ .codegen_func = .{ .func = func_index, .air = air, @@ -1799,6 +1802,7 @@ fn createFileRootStruct( codegen_type: { if (file.mod.?.strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } zcu.setFileRootType(file_index, wip_ty.index); @@ -3827,6 +3831,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error! const result = try pt.zcu.intern_pool.getExtern(pt.zcu.gpa, pt.tid, key); if (result.new_nav.unwrap()) |nav| { // This job depends on any resolve_type_fully jobs queued up before it. + pt.zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try pt.zcu.comp.queueJob(.{ .link_nav = nav }); if (pt.zcu.comp.debugIncremental()) try pt.zcu.incremental_debug_state.newNav(pt.zcu, nav); } @@ -3974,6 +3979,7 @@ fn recreateStructType( codegen_type: { if (file.mod.?.strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } @@ -4066,6 +4072,7 @@ fn recreateUnionType( codegen_type: { if (file.mod.?.strip) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. + zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); try zcu.comp.queueJob(.{ .link_type = wip_ty.index }); } -- cgit v1.2.3