aboutsummaryrefslogtreecommitdiff
path: root/src/link
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2025-06-12 20:46:36 -0400
committerGitHub <noreply@github.com>2025-06-12 20:46:36 -0400
commitdcdb4422b801f2d184107fdd7b9493f7840a0244 (patch)
treeca7a37c544382c10e45fbad68ea7701a05d0543c /src/link
parent5e3c0b7af7cd866f5464c244b9775e488b93ae48 (diff)
parent43d01ff69f6c6c46bef81dd4de2c78fb0a942b65 (diff)
downloadzig-dcdb4422b801f2d184107fdd7b9493f7840a0244.tar.gz
zig-dcdb4422b801f2d184107fdd7b9493f7840a0244.zip
Merge pull request #24124 from mlugg/better-backend-pipeline-2
compiler: threaded codegen (and more goodies)
Diffstat (limited to 'src/link')
-rw-r--r--src/link/C.zig146
-rw-r--r--src/link/Coff.zig721
-rw-r--r--src/link/Dwarf.zig227
-rw-r--r--src/link/Elf.zig828
-rw-r--r--src/link/Elf/Symbol.zig3
-rw-r--r--src/link/Elf/ZigObject.zig26
-rw-r--r--src/link/Goff.zig49
-rw-r--r--src/link/Lld.zig1757
-rw-r--r--src/link/MachO.zig83
-rw-r--r--src/link/MachO/DebugSymbols.zig2
-rw-r--r--src/link/MachO/Symbol.zig3
-rw-r--r--src/link/MachO/ZigObject.zig26
-rw-r--r--src/link/Plan9.zig48
-rw-r--r--src/link/Queue.zig279
-rw-r--r--src/link/SpirV.zig29
-rw-r--r--src/link/Wasm.zig702
-rw-r--r--src/link/Wasm/Flush.zig17
-rw-r--r--src/link/Xcoff.zig49
18 files changed, 2583 insertions, 2412 deletions
diff --git a/src/link/C.zig b/src/link/C.zig
index c32d8ba80b..f3465055b8 100644
--- a/src/link/C.zig
+++ b/src/link/C.zig
@@ -17,7 +17,7 @@ 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";
@@ -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,
},
};
@@ -167,6 +166,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);
@@ -178,73 +180,23 @@ pub fn updateFunc(
self: *C,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *AnyMir,
) link.File.UpdateNavError!void {
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 {
@@ -268,16 +220,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);
@@ -296,8 +246,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(),
@@ -344,16 +296,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);
@@ -361,16 +311,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 {
@@ -381,10 +331,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.flushModule(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 +346,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 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());
@@ -676,16 +622,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);
@@ -697,6 +641,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(
@@ -724,8 +670,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
@@ -734,8 +679,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);
@@ -871,12 +815,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);
@@ -896,3 +838,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 5100406030..0e00229b78 100644
--- a/src/link/Coff.zig
+++ b/src/link/Coff.zig
@@ -1,26 +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.
-
-/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path.
-llvm_object: ?LlvmObject.Ptr = null,
+//! 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,
@@ -226,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,
@@ -237,29 +224,21 @@ 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)
- 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,
.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,
@@ -284,45 +263,23 @@ 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,
};
- 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)) {
- // 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),
});
- assert(coff.llvm_object == null);
const gpa = comp.gpa;
try coff.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32));
@@ -428,8 +385,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);
}
@@ -1097,15 +1052,11 @@ pub fn updateFunc(
coff: *Coff,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) 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");
}
- if (coff.llvm_object) |llvm_object| {
- return llvm_object.updateFunc(pt, func_index, air, liveness);
- }
const tracy = trace(@src());
defer tracy.end();
@@ -1122,29 +1073,15 @@ pub fn updateFunc(
var code_buffer: std.ArrayListUnmanaged(u8) = .empty;
defer code_buffer.deinit(gpa);
- codegen.generateFunction(
+ try codegen.emitFunction(
&coff.base,
pt,
zcu.navSrcLoc(nav_index),
func_index,
- air,
- liveness,
+ mir,
&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);
@@ -1205,7 +1142,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 +1266,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 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.?,
@@ -1463,8 +1399,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 +1419,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 +1512,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),
@@ -1680,571 +1570,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.flushModule(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 != null) blk: {
- try coff.flushModule(arena, tid, prog_node);
-
- 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) |module| {
- if (module.stage1_flags.have_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)
- {
- break :blk .Console;
- }
- if (module.stage1_flags.have_winmain or module.stage1_flags.have_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) |module| {
- if (module.stage1_flags.have_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 flushModule(
+pub fn flush(
coff: *Coff,
arena: Allocator,
tid: Zcu.PerThread.Id,
@@ -2256,22 +1582,22 @@ 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;
+ 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 flushModuleInner(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 flushModuleInner(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;
@@ -2397,7 +1723,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);
@@ -2442,7 +1767,7 @@ pub fn lowerUav(
const atom = coff.getAtom(metadata.atom);
const existing_addr = atom.getSymbol(coff).value;
if (uav_alignment.check(existing_addr))
- return .{ .mcv = .{ .load_direct = atom.getSymbolIndex().? } };
+ return .{ .mcv = .{ .load_symbol = atom.getSymbolIndex().? } };
}
var name_buf: [32]u8 = undefined;
@@ -2474,7 +1799,7 @@ pub fn lowerUav(
.section = coff.rdata_section_index.?,
});
return .{ .mcv = .{
- .load_direct = coff.getAtom(atom_index).getSymbolIndex().?,
+ .load_symbol = coff.getAtom(atom_index).getSymbolIndex().?,
} };
}
@@ -2483,8 +1808,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(.{
@@ -3796,9 +3119,7 @@ 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 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..0afe10ef03 100644
--- a/src/link/Dwarf.zig
+++ b/src/link/Dwarf.zig
@@ -1474,24 +1474,59 @@ 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,
- name: []const u8,
+ tag: LocalVarTag,
+ opt_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 => if (opt_name) |_| .arg else .unnamed_arg,
+ .local_var => if (opt_name) |_| .local_var else unreachable,
});
- try wip_nav.strp(name);
+ if (opt_name) |name| try wip_nav.strp(name);
try wip_nav.refType(ty);
try wip_nav.infoExprLoc(loc);
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,
+ opt_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 => if (opt_name) |_| .comptime_arg_runtime_bits_comptime_state else .unnamed_comptime_arg_runtime_bits_comptime_state,
+ .local_const => if (opt_name) |_| .local_const_runtime_bits_comptime_state else unreachable,
+ } else if (has_comptime_state) switch (tag) {
+ .comptime_arg => if (opt_name) |_| .comptime_arg_comptime_state else .unnamed_comptime_arg_comptime_state,
+ .local_const => if (opt_name) |_| .local_const_comptime_state else unreachable,
+ } else if (has_runtime_bits) switch (tag) {
+ .comptime_arg => if (opt_name) |_| .comptime_arg_runtime_bits else .unnamed_comptime_arg_runtime_bits,
+ .local_const => if (opt_name) |_| .local_const_runtime_bits else unreachable,
+ } else switch (tag) {
+ .comptime_arg => if (opt_name) |_| .comptime_arg else .unnamed_comptime_arg,
+ .local_const => if (opt_name) |_| .local_const else unreachable,
+ });
+ if (opt_name) |name| 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
@@ -4391,7 +4445,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 flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
@@ -4890,8 +4944,22 @@ const AbbrevCode = enum {
block,
empty_inlined_func,
inlined_func,
- local_arg,
+ arg,
+ unnamed_arg,
+ comptime_arg,
+ unnamed_comptime_arg,
+ comptime_arg_runtime_bits,
+ unnamed_comptime_arg_runtime_bits,
+ comptime_arg_comptime_state,
+ unnamed_comptime_arg_comptime_state,
+ comptime_arg_runtime_bits_comptime_state,
+ unnamed_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 +5731,7 @@ const AbbrevCode = enum {
.{ .high_pc, .data4 },
},
},
- .local_arg = .{
+ .arg = .{
.tag = .formal_parameter,
.attrs = &.{
.{ .name, .strp },
@@ -5671,6 +5739,81 @@ const AbbrevCode = enum {
.{ .location, .exprloc },
},
},
+ .unnamed_arg = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .type, .ref_addr },
+ .{ .location, .exprloc },
+ },
+ },
+ .comptime_arg = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .const_expr, .flag_present },
+ .{ .name, .strp },
+ .{ .type, .ref_addr },
+ },
+ },
+ .unnamed_comptime_arg = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .const_expr, .flag_present },
+ .{ .type, .ref_addr },
+ },
+ },
+ .comptime_arg_runtime_bits = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .const_expr, .flag_present },
+ .{ .name, .strp },
+ .{ .type, .ref_addr },
+ .{ .const_value, .block },
+ },
+ },
+ .unnamed_comptime_arg_runtime_bits = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .const_expr, .flag_present },
+ .{ .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 },
+ },
+ },
+ .unnamed_comptime_arg_comptime_state = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .const_expr, .flag_present },
+ .{ .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 },
+ },
+ },
+ .unnamed_comptime_arg_runtime_bits_comptime_state = .{
+ .tag = .formal_parameter,
+ .attrs = &.{
+ .{ .const_expr, .flag_present },
+ .{ .type, .ref_addr },
+ .{ .const_value, .block },
+ .{ .ZIG_comptime_value, .ref_addr },
+ },
+ },
.local_var = .{
.tag = .variable,
.attrs = &.{
@@ -5679,6 +5822,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 1516993c74..0beea0d9e7 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,25 +15,11 @@ 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,
-/// 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) = .{},
@@ -204,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,
@@ -217,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;
@@ -268,16 +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 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)
- 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);
@@ -289,13 +260,15 @@ 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,
.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,
@@ -320,7 +293,6 @@ pub fn createEmpty(
};
},
- .emit_relocs = options.emit_relocs,
.z_nodelete = options.z_nodelete,
.z_notext = options.z_notext,
.z_defs = options.z_defs,
@@ -330,30 +302,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,
};
- 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)) {
- // 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);
@@ -361,13 +314,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;
@@ -457,8 +408,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 +464,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 +478,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);
}
@@ -795,60 +742,36 @@ 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.flushModule(arena, tid, prog_node);
-}
-
-pub fn flushModule(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 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 flushModuleInner(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;
- 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),
@@ -1508,639 +1431,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 != null) blk: {
- try self.flushModule(arena, tid, prog_node);
-
- 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();
@@ -2385,7 +1675,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);
}
@@ -2393,14 +1682,12 @@ pub fn updateFunc(
self: *Elf,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) 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");
}
- 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);
+ return self.zigObjectPtr().?.updateFunc(self, pt, func_index, mir);
}
pub fn updateNav(
@@ -2411,7 +1698,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 +1709,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 +1734,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 +1746,6 @@ pub fn deleteExport(
exported: Zcu.Exported,
name: InternPool.NullTerminatedString,
) void {
- if (self.llvm_object) |_| return;
return self.zigObjectPtr().?.deleteExport(self, exported, name);
}
@@ -4140,85 +3422,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);
}
@@ -5303,10 +4506,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");
@@ -5315,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");
@@ -5332,7 +4531,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/Symbol.zig b/src/link/Elf/Symbol.zig
index 31584ca406..843c23dca4 100644
--- a/src/link/Elf/Symbol.zig
+++ b/src/link/Elf/Symbol.zig
@@ -462,9 +462,6 @@ pub const Flags = packed struct {
/// Whether the symbol is a TLS variable.
is_tls: bool = false,
-
- /// Whether the symbol is an extern pointer (as opposed to function).
- is_extern_ptr: bool = false,
};
pub const Extra = struct {
diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig
index 13816940fe..71b42819e2 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.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 flushModule() 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 flushModule 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 flushModule
+ // 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;
}
@@ -1142,7 +1142,6 @@ fn getNavShdrIndex(
const gpa = elf_file.base.comp.gpa;
const ptr_size = elf_file.ptrWidthBytes();
const ip = &zcu.intern_pool;
- const any_non_single_threaded = elf_file.base.comp.config.any_non_single_threaded;
const nav_val = zcu.navValue(nav_index);
if (ip.isFunctionType(nav_val.typeOf(zcu).toIntern())) {
if (self.text_index) |symbol_index|
@@ -1162,7 +1161,7 @@ fn getNavShdrIndex(
else => .{ true, false, nav_val.toIntern() },
};
const has_relocs = self.symbol(sym_index).atom(elf_file).?.relocs(elf_file).len > 0;
- if (any_non_single_threaded and is_threadlocal) {
+ if (is_threadlocal and elf_file.base.comp.config.any_non_single_threaded) {
const is_bss = !has_relocs and for (code) |byte| {
if (byte != 0) break false;
} else true;
@@ -1416,8 +1415,7 @@ pub fn updateFunc(
elf_file: *Elf,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) link.File.UpdateNavError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -1438,13 +1436,12 @@ 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,
);
@@ -1544,11 +1541,7 @@ pub fn updateNav(
nav.name.toSlice(ip),
@"extern".lib_name.toSlice(ip),
);
- if (!ip.isFunctionType(@"extern".ty)) {
- const sym = self.symbol(sym_index);
- sym.flags.is_extern_ptr = true;
- if (@"extern".is_threadlocal) sym.flags.is_tls = true;
- }
+ if (@"extern".is_threadlocal and elf_file.base.comp.config.any_non_single_threaded) self.symbol(sym_index).flags.is_tls = true;
if (self.dwarf) |*dwarf| dwarf: {
var debug_wip_nav = try dwarf.initWipNav(pt, nav_index, sym_index) orelse break :dwarf;
defer debug_wip_nav.deinit();
@@ -2361,7 +2354,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 6ed360be25..1f4a7a4d30 100644
--- a/src/link/Goff.zig
+++ b/src/link/Goff.zig
@@ -13,14 +13,12 @@ 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");
-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,23 +34,20 @@ 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 = .{
.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,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
- .llvm_object = llvm_object,
};
return goff;
@@ -70,27 +65,27 @@ pub fn open(
}
pub fn deinit(self: *Goff) void {
- self.llvm_object.deinit();
+ _ = self;
}
pub fn updateFunc(
self: *Goff,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) 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;
+ _ = mir;
+ 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 +94,19 @@ 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);
-}
-
-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");
+ _ = self;
+ _ = arena;
_ = tid;
-
- try self.base.emitLlvmObject(arena, self.llvm_object, prog_node);
+ _ = prog_node;
}
diff --git a/src/link/Lld.zig b/src/link/Lld.zig
new file mode 100644
index 0000000000..4ea809428e
--- /dev/null
+++ b/src/link/Lld.zig
@@ -0,0 +1,1757 @@
+base: link.File,
+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_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,
+ .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os,
+ .file = null,
+ .build_id = options.build_id,
+ },
+ .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: {
+ 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),
+ .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;
+
+ 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={?}", .{zcu_obj_path});
+
+ 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 link_inputs = comp.link_inputs;
+
+ 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 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));
+
+ 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;
+}
+
+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});
+
+ 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;
+ 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,
+ };
+
+ 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 (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.
+ 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.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) {
+ 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 (zcu_obj_path) |p| {
+ try argv.append(try p.toString(arena));
+ }
+
+ 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);
+ }
+}
+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});
+
+ 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;
+ 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;
+ };
+
+ // 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 (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.
+ 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 (zcu_obj_path) |p| {
+ try argv.append(try p.toString(arena));
+ }
+
+ 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);
+ }
+}
+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});
+
+ 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;
+ 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;
+ };
+
+ 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 (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.
+ 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 (zcu_obj_path) |p| {
+ try argv.append(try p.toString(arena));
+ }
+
+ 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,
+ };
+ }
+ }
+}
+
+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 3ddc12a5b0..3f3a94bee7 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,
@@ -176,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);
@@ -191,13 +181,15 @@ 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,
.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,
@@ -225,15 +217,12 @@ 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, .{
.truncate = true,
.read = true,
- .mode = link.File.determineMode(false, output_mode, link_mode),
+ .mode = link.File.determineMode(output_mode, link_mode),
});
// Append null file
@@ -280,8 +269,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,15 +337,6 @@ pub fn flush(
tid: Zcu.PerThread.Id,
prog_node: std.Progress.Node,
) link.File.FlushError!void {
- try self.flushModule(arena, tid, prog_node);
-}
-
-pub fn flushModule(
- self: *MachO,
- arena: Allocator,
- tid: Zcu.PerThread.Id,
- prog_node: std.Progress.Node,
-) link.File.FlushError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -366,28 +344,19 @@ 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();
- 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.flushModule(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.getZigObject()) |zo| try zo.flush(self, tid);
+ 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();
@@ -409,7 +378,7 @@ pub fn flushModule(
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));
@@ -629,7 +598,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.flush(self) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}),
};
@@ -658,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);
@@ -692,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 {
@@ -784,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);
}
@@ -3073,26 +3039,22 @@ pub fn updateFunc(
self: *MachO,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) 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");
}
- 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);
+ 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 {
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 +3067,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 +3075,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 +3097,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);
}
@@ -5473,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");
@@ -5496,7 +5452,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..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 flushModule(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/Symbol.zig b/src/link/MachO/Symbol.zig
index 7493d3ceab..be126b0963 100644
--- a/src/link/MachO/Symbol.zig
+++ b/src/link/MachO/Symbol.zig
@@ -389,9 +389,6 @@ pub const Flags = packed struct {
/// ZigObject specific flags
/// Whether the symbol has a trampoline
trampoline: bool = false,
-
- /// Whether the symbol is an extern pointer (as opposed to function).
- is_extern_ptr: bool = false,
};
pub const SectionFlags = packed struct(u8) {
diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig
index a0de866544..f9ecdc6fb5 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 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 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.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 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 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.
@@ -777,8 +777,7 @@ pub fn updateFunc(
macho_file: *MachO,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) link.File.UpdateNavError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -796,13 +795,12 @@ 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,
);
@@ -883,11 +881,7 @@ pub fn updateNav(
const name = @"extern".name.toSlice(ip);
const lib_name = @"extern".lib_name.toSlice(ip);
const sym_index = try self.getGlobalSymbol(macho_file, name, lib_name);
- if (!ip.isFunctionType(@"extern".ty)) {
- const sym = &self.symbols.items[sym_index];
- sym.flags.is_extern_ptr = true;
- if (@"extern".is_threadlocal) sym.flags.tlv = true;
- }
+ if (@"extern".is_threadlocal and macho_file.base.comp.config.any_non_single_threaded) self.symbols.items[sym_index].flags.tlv = true;
if (self.dwarf) |*dwarf| dwarf: {
var debug_wip_nav = try dwarf.initWipNav(pt, nav_index, sym_index) orelse break :dwarf;
defer debug_wip_nav.deinit();
@@ -1160,7 +1154,6 @@ fn getNavOutputSection(
) error{OutOfMemory}!u8 {
_ = self;
const ip = &zcu.intern_pool;
- const any_non_single_threaded = macho_file.base.comp.config.any_non_single_threaded;
const nav_val = zcu.navValue(nav_index);
if (ip.isFunctionType(nav_val.typeOf(zcu).toIntern())) return macho_file.zig_text_sect_index.?;
const is_const, const is_threadlocal, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) {
@@ -1168,7 +1161,7 @@ fn getNavOutputSection(
.@"extern" => |@"extern"| .{ @"extern".is_const, @"extern".is_threadlocal, .none },
else => .{ true, false, nav_val.toIntern() },
};
- if (any_non_single_threaded and is_threadlocal) {
+ if (is_threadlocal and macho_file.base.comp.config.any_non_single_threaded) {
for (code) |byte| {
if (byte != 0) break;
} else return macho_file.getSectionByName("__DATA", "__thread_bss") orelse try macho_file.addSection(
@@ -1537,7 +1530,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 flush
if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(macho_file, pt, lazy_sym, symbol_index);
return symbol_index;
}
@@ -1813,7 +1806,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 5cbb9287d7..c99ebb81bb 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,
@@ -387,8 +386,7 @@ pub fn updateFunc(
self: *Plan9,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) 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");
@@ -413,13 +411,12 @@ 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 },
);
@@ -494,7 +491,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 flush
.type = atom.type,
.name = try gpa.dupe(u8, nav.name.toSlice(ip)),
};
@@ -527,25 +524,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.flushModule(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 +564,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 flush(
self: *Plan9,
arena: Allocator,
/// TODO: stop using this
@@ -607,10 +585,16 @@ pub fn flushModule(
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("flushModule", .{});
+ log.debug("flush", .{});
defer assert(self.hdr.entry != 0x0);
@@ -1039,7 +1023,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 flush
if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbolAtom(pt, lazy_sym, atom);
return atom;
}
@@ -1182,11 +1166,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/Queue.zig b/src/link/Queue.zig
new file mode 100644
index 0000000000..d197edab02
--- /dev/null
+++ b/src/link/Queue.zig
@@ -0,0 +1,279 @@
+//! 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,
+
+/// 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.
+ 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,
+},
+
+/// 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 = .{
+ .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,
+ .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 {
+ 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, .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();
+ // 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,
+ .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(); // every `return` site should unlock this before unlocking `q.mutex`
+
+ 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;
+ q.flush_safety.unlock();
+ 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()) |_| {
+ 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;
+ q.flush_safety.unlock();
+ 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 };
+ q.flush_safety.unlock();
+ return;
+ }
+ 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;
+ }
+}
+
+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/link/SpirV.zig b/src/link/SpirV.zig
index a49771c3e2..bafefccfc0 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 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()),
@@ -112,24 +111,6 @@ pub fn deinit(self: *SpirV) void {
self.object.deinit();
}
-pub fn updateFunc(
- self: *SpirV,
- pt: Zcu.PerThread,
- func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
-) link.File.UpdateNavError!void {
- if (build_options.skip_non_native) {
- @panic("Attempted to compile for architecture that was disabled by build configuration");
- }
-
- const ip = &pt.zcu.intern_pool;
- const func = pt.zcu.funcInfo(func_index);
- log.debug("lowering function {}", .{ip.getNav(func.owner_nav).name.fmt(ip)});
-
- try self.object.updateFunc(pt, func_index, air, liveness);
-}
-
pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
if (build_options.skip_non_native) {
@panic("Attempted to compile for architecture that was disabled by build configuration");
@@ -193,18 +174,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.flushModule(arena, tid, prog_node);
-}
-
-pub fn flushModule(
+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 flushModule 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 69684724a5..eda7552986 100644
--- a/src/link/Wasm.zig
+++ b/src/link/Wasm.zig
@@ -29,19 +29,16 @@ 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");
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");
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");
@@ -75,14 +72,10 @@ 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
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) = .{},
@@ -288,7 +281,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,
@@ -872,9 +865,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,
@@ -2938,28 +2946,20 @@ 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)
- 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"
@@ -2973,13 +2973,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,
@@ -2992,9 +2990,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();
@@ -3010,17 +3005,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)
@@ -3031,7 +3016,7 @@ pub fn createEmpty(
else
0,
});
- wasm.name = sub_path;
+ wasm.name = emit.sub_path;
return wasm;
}
@@ -3116,7 +3101,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);
@@ -3132,7 +3116,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();
@@ -3192,34 +3176,94 @@ 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,
+) !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");
}
- if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness);
dev.check(.wasm_backend);
+ // 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
@@ -3228,7 +3272,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 +3351,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 +3373,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;
@@ -3379,21 +3419,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.flushModule(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();
@@ -3785,37 +3810,25 @@ 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 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 flushModule 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;
- 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| {
- 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);
}
@@ -3850,432 +3863,6 @@ pub fn flushModule(
};
}
-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 != null) blk: {
- try wasm.flushModule(arena, tid, prog_node);
-
- 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,
@@ -4465,58 +4052,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 {
@@ -4550,10 +4133,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/link/Xcoff.zig b/src/link/Xcoff.zig
index 525d99d391..fd143713ff 100644
--- a/src/link/Xcoff.zig
+++ b/src/link/Xcoff.zig
@@ -13,14 +13,12 @@ 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");
-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,23 +34,20 @@ 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 = .{
.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,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
- .llvm_object = llvm_object,
};
return xcoff;
@@ -70,27 +65,27 @@ pub fn open(
}
pub fn deinit(self: *Xcoff) void {
- self.llvm_object.deinit();
+ _ = self;
}
pub fn updateFunc(
self: *Xcoff,
pt: Zcu.PerThread,
func_index: InternPool.Index,
- air: Air,
- liveness: Air.Liveness,
+ mir: *const codegen.AnyMir,
) 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;
+ _ = mir;
+ 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 +94,19 @@ 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);
-}
-
-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");
+ _ = self;
+ _ = arena;
_ = tid;
-
- try self.base.emitLlvmObject(arena, self.llvm_object, prog_node);
+ _ = prog_node;
}