| 1 | base: link.File, |
| 2 | ofmt: union(enum) { |
| 3 | elf: Elf, |
| 4 | coff: Coff, |
| 5 | wasm: Wasm, |
| 6 | }, |
| 7 | |
| 8 | const Coff = struct { |
| 9 | image_base: u64, |
| 10 | entry: link.File.OpenOptions.Entry, |
| 11 | pdb_out_path: ?[]const u8, |
| 12 | repro: bool, |
| 13 | tsaware: bool, |
| 14 | nxcompat: bool, |
| 15 | dynamicbase: bool, |
| 16 | /// TODO this and minor_subsystem_version should be combined into one property and left as |
| 17 | /// default or populated together. They should not be separate fields. |
| 18 | major_subsystem_version: u16, |
| 19 | minor_subsystem_version: u16, |
| 20 | lib_directories: []const Cache.Directory, |
| 21 | module_definition_file: ?[]const u8, |
| 22 | subsystem: ?std.zig.Subsystem, |
| 23 | /// These flags are populated by `codegen.llvm.updateExports` to allow us to guess the subsystem. |
| 24 | lld_export_flags: struct { |
| 25 | c_main: bool, |
| 26 | winmain: bool, |
| 27 | wwinmain: bool, |
| 28 | winmain_crt_startup: bool, |
| 29 | wwinmain_crt_startup: bool, |
| 30 | dllmain_crt_startup: bool, |
| 31 | }, |
| 32 | fn init(comp: *Compilation, options: link.File.OpenOptions) !Coff { |
| 33 | const target = &comp.root_mod.resolved_target.result; |
| 34 | const output_mode = comp.config.output_mode; |
| 35 | return .{ |
| 36 | .image_base = options.image_base orelse switch (output_mode) { |
| 37 | .Exe => switch (target.cpu.arch) { |
| 38 | .aarch64, .x86_64 => 0x140000000, |
| 39 | .thumb, .x86 => 0x400000, |
| 40 | else => return error.UnsupportedCoffArchitecture, |
| 41 | }, |
| 42 | .Lib => switch (target.cpu.arch) { |
| 43 | .aarch64, .x86_64 => 0x180000000, |
| 44 | .thumb, .x86 => 0x10000000, |
| 45 | else => return error.UnsupportedCoffArchitecture, |
| 46 | }, |
| 47 | .Obj => 0, |
| 48 | }, |
| 49 | .entry = options.entry, |
| 50 | .pdb_out_path = options.pdb_out_path, |
| 51 | .repro = options.repro, |
| 52 | .tsaware = options.tsaware, |
| 53 | .nxcompat = options.nxcompat, |
| 54 | .dynamicbase = options.dynamicbase, |
| 55 | .major_subsystem_version = options.major_subsystem_version orelse 6, |
| 56 | .minor_subsystem_version = options.minor_subsystem_version orelse 0, |
| 57 | .lib_directories = options.lib_directories, |
| 58 | .module_definition_file = options.module_definition_file, |
| 59 | // Subsystem depends on the set of public symbol names from linked objects. |
| 60 | // See LinkerDriver::inferSubsystem from the LLD project for the flow chart. |
| 61 | .subsystem = options.subsystem, |
| 62 | // These flags are initially all `false`; the LLVM backend populates them when it learns about exports. |
| 63 | .lld_export_flags = .{ |
| 64 | .c_main = false, |
| 65 | .winmain = false, |
| 66 | .wwinmain = false, |
| 67 | .winmain_crt_startup = false, |
| 68 | .wwinmain_crt_startup = false, |
| 69 | .dllmain_crt_startup = false, |
| 70 | }, |
| 71 | }; |
| 72 | } |
| 73 | }; |
| 74 | pub const Elf = struct { |
| 75 | entry_name: ?[]const u8, |
| 76 | hash_style: HashStyle, |
| 77 | image_base: u64, |
| 78 | linker_script: ?Cache.Path, |
| 79 | version_script: ?Cache.Path, |
| 80 | sort_section: ?SortSection, |
| 81 | print_icf_sections: bool, |
| 82 | print_map: bool, |
| 83 | nmagic: bool, |
| 84 | fatal_warnings: bool, |
| 85 | emit_relocs: bool, |
| 86 | z_nodelete: bool, |
| 87 | z_notext: bool, |
| 88 | z_defs: bool, |
| 89 | z_origin: bool, |
| 90 | z_nocopyreloc: bool, |
| 91 | z_now: bool, |
| 92 | z_relro: bool, |
| 93 | z_common_page_size: ?u64, |
| 94 | z_max_page_size: ?u64, |
| 95 | rpath_list: []const []const u8, |
| 96 | symbol_wrap_set: []const []const u8, |
| 97 | soname: ?[]const u8, |
| 98 | allow_undefined_version: bool, |
| 99 | enable_new_dtags: ?bool, |
| 100 | compress_debug_sections: std.zig.CompressDebugSections, |
| 101 | bind_global_refs_locally: bool, |
| 102 | pub const HashStyle = enum { sysv, gnu, both }; |
| 103 | pub const SortSection = enum { name, alignment }; |
| 104 | |
| 105 | fn init(comp: *Compilation, options: link.File.OpenOptions) !Elf { |
| 106 | const PtrWidth = enum { p32, p64 }; |
| 107 | const target = &comp.root_mod.resolved_target.result; |
| 108 | const output_mode = comp.config.output_mode; |
| 109 | const is_dyn_lib = output_mode == .Lib and comp.config.link_mode == .dynamic; |
| 110 | const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { |
| 111 | 0...32 => .p32, |
| 112 | 33...64 => .p64, |
| 113 | else => return error.UnsupportedElfArchitecture, |
| 114 | }; |
| 115 | const default_entry_name: []const u8 = switch (target.cpu.arch) { |
| 116 | .mips, .mipsel, .mips64, .mips64el => "__start", |
| 117 | else => "_start", |
| 118 | }; |
| 119 | return .{ |
| 120 | .entry_name = switch (options.entry) { |
| 121 | .disabled => null, |
| 122 | .default => if (output_mode != .Exe) null else default_entry_name, |
| 123 | .enabled => default_entry_name, |
| 124 | .named => |name| name, |
| 125 | }, |
| 126 | .hash_style = options.hash_style, |
| 127 | .image_base = b: { |
| 128 | if (is_dyn_lib) break :b 0; |
| 129 | if (output_mode == .Exe and (comp.config.pie or target.os.tag == .haiku)) break :b 0; |
| 130 | break :b options.image_base orelse switch (ptr_width) { |
| 131 | .p32 => 0x10000, |
| 132 | .p64 => 0x1000000, |
| 133 | }; |
| 134 | }, |
| 135 | .linker_script = options.linker_script, |
| 136 | .version_script = options.version_script, |
| 137 | .sort_section = options.sort_section, |
| 138 | .print_icf_sections = options.print_icf_sections, |
| 139 | .print_map = options.print_map, |
| 140 | .nmagic = options.nmagic, |
| 141 | .fatal_warnings = options.fatal_warnings, |
| 142 | .emit_relocs = options.emit_relocs, |
| 143 | .z_nodelete = options.z_nodelete, |
| 144 | .z_notext = options.z_notext, |
| 145 | .z_defs = options.z_defs, |
| 146 | .z_origin = options.z_origin, |
| 147 | .z_nocopyreloc = options.z_nocopyreloc, |
| 148 | .z_now = options.z_now, |
| 149 | .z_relro = options.z_relro, |
| 150 | .z_common_page_size = options.z_common_page_size, |
| 151 | .z_max_page_size = options.z_max_page_size, |
| 152 | .rpath_list = options.rpath_list, |
| 153 | .symbol_wrap_set = options.symbol_wrap_set.keys(), |
| 154 | .soname = options.soname, |
| 155 | .allow_undefined_version = options.allow_undefined_version, |
| 156 | .enable_new_dtags = options.enable_new_dtags, |
| 157 | .compress_debug_sections = options.compress_debug_sections, |
| 158 | .bind_global_refs_locally = options.bind_global_refs_locally, |
| 159 | }; |
| 160 | } |
| 161 | }; |
| 162 | const Wasm = struct { |
| 163 | /// Symbol name of the entry function to export |
| 164 | entry_name: ?[]const u8, |
| 165 | /// When true, will import the function table from the host environment. |
| 166 | import_table: bool, |
| 167 | /// When true, will export the function table to the host environment. |
| 168 | export_table: bool, |
| 169 | /// When true, remove maximum size from function table, allowing table to grow. |
| 170 | growable_table: bool, |
| 171 | /// When defined, sets the initial memory size of the memory. |
| 172 | initial_memory: ?u64, |
| 173 | /// When defined, sets the maximum memory size of the memory. |
| 174 | max_memory: ?u64, |
| 175 | /// When defined, sets the start of the data section. |
| 176 | global_base: ?u64, |
| 177 | /// Set of *global* symbol names to export to the host environment. |
| 178 | export_symbol_names: []const []const u8, |
| 179 | /// When true, will allow undefined symbols |
| 180 | import_symbols: bool, |
| 181 | fn init(comp: *Compilation, options: link.File.OpenOptions) !Wasm { |
| 182 | const default_entry_name: []const u8 = switch (comp.config.wasi_exec_model) { |
| 183 | .reactor => "_initialize", |
| 184 | .command => "_start", |
| 185 | }; |
| 186 | return .{ |
| 187 | .entry_name = switch (options.entry) { |
| 188 | .disabled => null, |
| 189 | .default => if (comp.config.output_mode != .Exe) null else default_entry_name, |
| 190 | .enabled => default_entry_name, |
| 191 | .named => |name| name, |
| 192 | }, |
| 193 | .import_table = options.import_table, |
| 194 | .export_table = options.export_table, |
| 195 | .growable_table = options.growable_table, |
| 196 | .initial_memory = options.initial_memory, |
| 197 | .max_memory = options.max_memory, |
| 198 | .global_base = options.global_base, |
| 199 | .export_symbol_names = options.export_symbol_names, |
| 200 | .import_symbols = options.import_symbols, |
| 201 | }; |
| 202 | } |
| 203 | }; |
| 204 | |
| 205 | pub fn createEmpty( |
| 206 | arena: Allocator, |
| 207 | comp: *Compilation, |
| 208 | emit: Cache.Path, |
| 209 | options: link.File.OpenOptions, |
| 210 | ) !*Lld { |
| 211 | const target = &comp.root_mod.resolved_target.result; |
| 212 | const output_mode = comp.config.output_mode; |
| 213 | const optimize_mode = comp.root_mod.optimize_mode; |
| 214 | |
| 215 | const gc_sections: bool = options.gc_sections orelse switch (target.ofmt) { |
| 216 | .coff => optimize_mode != .debug, |
| 217 | .elf => optimize_mode != .debug and output_mode != .Obj, |
| 218 | .wasm => output_mode != .Obj, |
| 219 | else => unreachable, |
| 220 | }; |
| 221 | const stack_size: u64 = options.stack_size orelse default: { |
| 222 | if (target.ofmt == .wasm and target.os.tag == .freestanding) |
| 223 | break :default 1 * 1024 * 1024; // 1 MiB |
| 224 | break :default 16 * 1024 * 1024; // 16 MiB |
| 225 | }; |
| 226 | |
| 227 | const lld = try arena.create(Lld); |
| 228 | lld.* = .{ |
| 229 | .base = .{ |
| 230 | .tag = .lld, |
| 231 | .comp = comp, |
| 232 | .emit = emit, |
| 233 | .gc_sections = gc_sections, |
| 234 | .print_gc_sections = options.print_gc_sections, |
| 235 | .stack_size = stack_size, |
| 236 | .allow_shlib_undefined = options.allow_shlib_undefined orelse false, |
| 237 | .file = null, |
| 238 | .build_id = options.build_id, |
| 239 | }, |
| 240 | .ofmt = switch (target.ofmt) { |
| 241 | .coff => .{ .coff = try .init(comp, options) }, |
| 242 | .elf => .{ .elf = try .init(comp, options) }, |
| 243 | .wasm => .{ .wasm = try .init(comp, options) }, |
| 244 | else => unreachable, |
| 245 | }, |
| 246 | }; |
| 247 | return lld; |
| 248 | } |
| 249 | pub fn deinit(lld: *Lld) void { |
| 250 | _ = lld; |
| 251 | } |
| 252 | pub fn flush( |
| 253 | lld: *Lld, |
| 254 | arena: Allocator, |
| 255 | tid: Zcu.PerThread.Id, |
| 256 | prog_node: std.Progress.Node, |
| 257 | ) link.Error!void { |
| 258 | dev.check(.lld_linker); |
| 259 | _ = tid; |
| 260 | |
| 261 | const tracy = trace(@src()); |
| 262 | defer tracy.end(); |
| 263 | |
| 264 | const sub_prog_node = prog_node.start("LLD Link", 0); |
| 265 | defer sub_prog_node.end(); |
| 266 | |
| 267 | const comp = lld.base.comp; |
| 268 | const result = if (comp.config.output_mode == .Lib and comp.config.link_mode == .static) r: { |
| 269 | if (!@import("build_options").have_llvm or !comp.config.use_lib_llvm) { |
| 270 | return lld.base.comp.link_diags.fail("using lld without libllvm not implemented", .{}); |
| 271 | } |
| 272 | break :r linkAsArchive(lld, arena); |
| 273 | } else switch (lld.ofmt) { |
| 274 | .coff => coffLink(lld, arena), |
| 275 | .elf => elfLink(lld, arena), |
| 276 | .wasm => wasmLink(lld, arena), |
| 277 | }; |
| 278 | result catch |err| switch (err) { |
| 279 | error.OutOfMemory, error.AlreadyReported, error.Canceled => |e| return e, |
| 280 | else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {t}", .{e}), |
| 281 | }; |
| 282 | } |
| 283 | |
| 284 | fn linkAsArchive(lld: *Lld, arena: Allocator) link.Error!void { |
| 285 | const base = &lld.base; |
| 286 | const comp = base.comp; |
| 287 | const directory = base.emit.root_dir; // Just an alias to make it shorter to type. |
| 288 | const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); |
| 289 | const full_out_path_z = try arena.dupeSentinel(u8, full_out_path, 0); |
| 290 | const opt_zcu = comp.zcu; |
| 291 | |
| 292 | const zcu_obj_path: ?Cache.Path = if (opt_zcu) |zcu| p: { |
| 293 | break :p try comp.resolveEmitPathFlush(arena, .temp, zcu.llvm_object.?.out_bin_basename); |
| 294 | } else null; |
| 295 | |
| 296 | log.debug("zcu_obj_path={?f}", .{zcu_obj_path}); |
| 297 | |
| 298 | const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj) |
| 299 | comp.compiler_rt_obj.?.full_object_path |
| 300 | else |
| 301 | null; |
| 302 | |
| 303 | const ubsan_rt_path: ?Cache.Path = if (comp.ubsan_rt_strat == .obj) |
| 304 | comp.ubsan_rt_obj.?.full_object_path |
| 305 | else |
| 306 | null; |
| 307 | |
| 308 | // This function follows the same pattern as link.Elf.linkWithLLD so if you want some |
| 309 | // insight as to what's going on here you can read that function body which is more |
| 310 | // well-commented. |
| 311 | |
| 312 | var object_files: std.ArrayList([*:0]const u8) = .empty; |
| 313 | |
| 314 | try object_files.ensureUnusedCapacity(arena, comp.link_inputs.len); |
| 315 | for (comp.link_inputs) |input| switch (input) { |
| 316 | .dso, .dso_exact, .archive => {}, // static archives should not contain shared libraries or other static archives |
| 317 | .res, .object => { |
| 318 | const path = try input.path().?.toStringZ(arena); |
| 319 | object_files.appendAssumeCapacity(path); |
| 320 | }, |
| 321 | }; |
| 322 | |
| 323 | try object_files.ensureUnusedCapacity(arena, comp.c_objects.items.len + |
| 324 | comp.win32_resources.items.len + 2); |
| 325 | |
| 326 | for (comp.c_objects.items) |c_object| { |
| 327 | object_files.appendAssumeCapacity(try c_object.status.success.object_path.toStringZ(arena)); |
| 328 | } |
| 329 | for (comp.win32_resources.items) |win32_resource| { |
| 330 | object_files.appendAssumeCapacity(try arena.dupeSentinel(u8, win32_resource.status.success.res_path, 0)); |
| 331 | } |
| 332 | if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); |
| 333 | if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); |
| 334 | if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena)); |
| 335 | |
| 336 | if (comp.verbose_link) { |
| 337 | std.debug.print("ar rcs {s}", .{full_out_path_z}); |
| 338 | for (object_files.items) |arg| { |
| 339 | std.debug.print(" {s}", .{arg}); |
| 340 | } |
| 341 | std.debug.print("\n", .{}); |
| 342 | } |
| 343 | |
| 344 | const llvm_bindings = @import("../codegen/llvm/bindings.zig"); |
| 345 | const llvm = @import("../codegen/llvm.zig"); |
| 346 | const target = &comp.root_mod.resolved_target.result; |
| 347 | llvm.initializeLLVMTarget(comp.io, target.cpu.arch); |
| 348 | var err_file_index: usize = undefined; |
| 349 | var err_msg: [*:0]u8 = undefined; |
| 350 | if (llvm_bindings.WriteArchive( |
| 351 | full_out_path_z, |
| 352 | object_files.items.ptr, |
| 353 | object_files.items.len, |
| 354 | switch (target.os.tag) { |
| 355 | .windows => .COFF, |
| 356 | else => if (target.os.tag.isDarwin()) .DARWIN else .GNU, |
| 357 | }, |
| 358 | &err_file_index, |
| 359 | &err_msg, |
| 360 | )) { |
| 361 | defer std.c.free(err_msg); |
| 362 | if (err_file_index < object_files.items.len) { |
| 363 | return comp.link_diags.fail("LLD failed to open input file '{s}': {s}", .{ |
| 364 | object_files.items[err_file_index], |
| 365 | err_msg, |
| 366 | }); |
| 367 | } else { |
| 368 | return comp.link_diags.fail("LLD failed to write archive: {s}", .{err_msg}); |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | fn addCommonArgs(argv: *std.array_list.Managed([]const u8), coff: bool) !void { |
| 374 | if (builtin.os.tag == .netbsd) { |
| 375 | // NetBSD 10.1's `malloc` appears to have some nasty bugs that occur |
| 376 | // when doing parallel linking in LLD, manifesting as input and/or |
| 377 | // output section memory randomly being unmapped. So just don't do |
| 378 | // parallel linking for now. |
| 379 | try argv.append(if (coff) "-threads:1" else "--threads=1"); |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | fn coffLink(lld: *Lld, arena: Allocator) !void { |
| 384 | const comp = lld.base.comp; |
| 385 | const gpa = comp.gpa; |
| 386 | const io = comp.io; |
| 387 | const base = &lld.base; |
| 388 | const coff = &lld.ofmt.coff; |
| 389 | |
| 390 | const directory = base.emit.root_dir; // Just an alias to make it shorter to type. |
| 391 | const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); |
| 392 | |
| 393 | const zcu_obj_path: ?Cache.Path = if (comp.zcu) |zcu| p: { |
| 394 | break :p try comp.resolveEmitPathFlush(arena, .temp, zcu.llvm_object.?.out_bin_basename); |
| 395 | } else null; |
| 396 | |
| 397 | const is_lib = comp.config.output_mode == .Lib; |
| 398 | const is_dyn_lib = comp.config.link_mode == .dynamic and is_lib; |
| 399 | const is_exe_or_dyn_lib = is_dyn_lib or comp.config.output_mode == .Exe; |
| 400 | const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib; |
| 401 | const target = &comp.root_mod.resolved_target.result; |
| 402 | const optimize_mode = comp.root_mod.optimize_mode; |
| 403 | const entry_name: ?[]const u8 = switch (coff.entry) { |
| 404 | // This logic isn't quite right for default or enabled. No point in fixing it |
| 405 | // when the goal is to eliminate dependency on LLD anyway. |
| 406 | // https://github.com/ziglang/zig/issues/17751 |
| 407 | .disabled, .default, .enabled => null, |
| 408 | .named => |name| name, |
| 409 | }; |
| 410 | |
| 411 | if (comp.config.output_mode == .Obj) { |
| 412 | // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy |
| 413 | // here. TODO: think carefully about how we can avoid this redundant operation when doing |
| 414 | // build-obj. See also the corresponding TODO in linkAsArchive. |
| 415 | const the_object_path = blk: { |
| 416 | if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; |
| 417 | |
| 418 | if (comp.c_objects.items.len != 0) |
| 419 | break :blk comp.c_objects.items[0].status.success.object_path; |
| 420 | |
| 421 | if (zcu_obj_path) |p| |
| 422 | break :blk p; |
| 423 | |
| 424 | // TODO I think this is unreachable. Audit this situation when solving the above TODO |
| 425 | // regarding eliding redundant object -> object transformations. |
| 426 | return error.NoObjectsToLink; |
| 427 | }; |
| 428 | try Io.Dir.copyFile( |
| 429 | the_object_path.root_dir.handle, |
| 430 | the_object_path.sub_path, |
| 431 | directory.handle, |
| 432 | base.emit.sub_path, |
| 433 | io, |
| 434 | .{}, |
| 435 | ); |
| 436 | } else { |
| 437 | // Create an LLD command line and invoke it. |
| 438 | var argv = std.array_list.Managed([]const u8).init(gpa); |
| 439 | defer argv.deinit(); |
| 440 | // We will invoke ourselves as a child process to gain access to LLD. |
| 441 | // This is necessary because LLD does not behave properly as a library - |
| 442 | // it calls exit() and does not reset all global data between invocations. |
| 443 | const linker_command = "lld-link"; |
| 444 | try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); |
| 445 | try addCommonArgs(&argv, true); |
| 446 | |
| 447 | if (target.isMinGW()) { |
| 448 | try argv.append("-lldmingw"); |
| 449 | } |
| 450 | |
| 451 | try argv.append("-ERRORLIMIT:0"); |
| 452 | try argv.append("-NOLOGO"); |
| 453 | if (comp.config.debug_format != .strip) { |
| 454 | try argv.append("-DEBUG"); |
| 455 | |
| 456 | const out_ext = std.fs.path.extension(full_out_path); |
| 457 | const out_pdb = coff.pdb_out_path orelse try arena.print("{s}.pdb", .{ |
| 458 | full_out_path[0 .. full_out_path.len - out_ext.len], |
| 459 | }); |
| 460 | const out_pdb_basename = std.fs.path.basename(out_pdb); |
| 461 | |
| 462 | try argv.append(try arena.print("-PDB:{s}", .{out_pdb})); |
| 463 | try argv.append(try arena.print("-PDBALTPATH:{s}", .{out_pdb_basename})); |
| 464 | } |
| 465 | if (comp.version) |version| { |
| 466 | try argv.append(try arena.print("-VERSION:{d}.{d}", .{ version.major, version.minor })); |
| 467 | } |
| 468 | |
| 469 | if (target_util.llvmMachineAbi(target)) |mabi| { |
| 470 | try argv.append(try arena.print("-MLLVM:-target-abi={s}", .{mabi})); |
| 471 | } |
| 472 | |
| 473 | try argv.append(try arena.print("-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"})); |
| 474 | |
| 475 | if (comp.config.lto != .none) { |
| 476 | switch (optimize_mode) { |
| 477 | .debug => {}, |
| 478 | .small => try argv.append("-OPT:lldlto=2"), |
| 479 | .fast, .safe => try argv.append("-OPT:lldlto=3"), |
| 480 | } |
| 481 | } |
| 482 | if (comp.config.output_mode == .Exe) { |
| 483 | try argv.append(try arena.print("-STACK:{d}", .{base.stack_size})); |
| 484 | } |
| 485 | try argv.append(try arena.print("-BASE:{d}", .{coff.image_base})); |
| 486 | |
| 487 | switch (base.build_id) { |
| 488 | .none => try argv.append("-BUILD-ID:NO"), |
| 489 | .fast => try argv.append("-BUILD-ID"), |
| 490 | .uuid, .sha1, .md5, .hexstring => {}, |
| 491 | } |
| 492 | |
| 493 | if (target.cpu.arch == .x86) { |
| 494 | try argv.append("-MACHINE:X86"); |
| 495 | } else if (target.cpu.arch == .x86_64) { |
| 496 | try argv.append("-MACHINE:X64"); |
| 497 | } else if (target.cpu.arch == .thumb) { |
| 498 | try argv.append("-MACHINE:ARM"); |
| 499 | } else if (target.cpu.arch == .aarch64) { |
| 500 | try argv.append("-MACHINE:ARM64"); |
| 501 | } |
| 502 | |
| 503 | for (comp.force_undefined_symbols.keys()) |symbol| { |
| 504 | try argv.append(try arena.print("-INCLUDE:{s}", .{symbol})); |
| 505 | } |
| 506 | |
| 507 | if (is_dyn_lib) { |
| 508 | try argv.append("-DLL"); |
| 509 | } |
| 510 | |
| 511 | if (entry_name) |name| { |
| 512 | try argv.append(try arena.print("-ENTRY:{s}", .{name})); |
| 513 | } else if (coff.entry == .disabled) { |
| 514 | try argv.append("-NOENTRY"); |
| 515 | } |
| 516 | |
| 517 | if (coff.repro) { |
| 518 | try argv.append("-BREPRO"); |
| 519 | } |
| 520 | |
| 521 | if (coff.tsaware) { |
| 522 | try argv.append("-tsaware"); |
| 523 | } |
| 524 | if (coff.nxcompat) { |
| 525 | try argv.append("-nxcompat"); |
| 526 | } |
| 527 | if (!coff.dynamicbase) { |
| 528 | try argv.append("-dynamicbase:NO"); |
| 529 | } |
| 530 | if (base.allow_shlib_undefined) { |
| 531 | try argv.append("-FORCE:UNRESOLVED"); |
| 532 | } |
| 533 | |
| 534 | try argv.append(try arena.print("-OUT:{s}", .{full_out_path})); |
| 535 | |
| 536 | if (comp.emit_implib) |raw_emit_path| { |
| 537 | const path = try comp.resolveEmitPathFlush(arena, .artifact, raw_emit_path); |
| 538 | try argv.append(try arena.print("-IMPLIB:{f}", .{path})); |
| 539 | } |
| 540 | |
| 541 | if (comp.config.link_libc) { |
| 542 | if (comp.libc_installation) |libc_installation| { |
| 543 | try argv.append(try arena.print("-LIBPATH:{s}", .{libc_installation.crt_dir.?})); |
| 544 | |
| 545 | if (target.abi == .msvc or target.abi == .itanium) { |
| 546 | try argv.append(try arena.print("-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); |
| 547 | try argv.append(try arena.print("-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); |
| 548 | } |
| 549 | } |
| 550 | } |
| 551 | |
| 552 | for (coff.lib_directories) |lib_directory| { |
| 553 | try argv.append(try arena.print("-LIBPATH:{s}", .{lib_directory.path orelse "."})); |
| 554 | } |
| 555 | |
| 556 | try argv.ensureUnusedCapacity(comp.link_inputs.len); |
| 557 | for (comp.link_inputs) |link_input| switch (link_input) { |
| 558 | .dso_exact => unreachable, // not applicable to PE/COFF |
| 559 | inline .dso, .res => |x| { |
| 560 | argv.appendAssumeCapacity(try x.path.toString(arena)); |
| 561 | }, |
| 562 | .object, .archive => |obj| { |
| 563 | if (obj.must_link) { |
| 564 | argv.appendAssumeCapacity(try arena.print("-WHOLEARCHIVE:{f}", .{@as(Cache.Path, obj.path)})); |
| 565 | } else { |
| 566 | argv.appendAssumeCapacity(try obj.path.toString(arena)); |
| 567 | } |
| 568 | }, |
| 569 | }; |
| 570 | |
| 571 | for (comp.c_objects.items) |c_object| { |
| 572 | try argv.append(try c_object.status.success.object_path.toString(arena)); |
| 573 | } |
| 574 | |
| 575 | for (comp.win32_resources.items) |win32_resource| { |
| 576 | try argv.append(win32_resource.status.success.res_path); |
| 577 | } |
| 578 | |
| 579 | if (zcu_obj_path) |p| { |
| 580 | try argv.append(try p.toString(arena)); |
| 581 | } |
| 582 | |
| 583 | if (coff.module_definition_file) |def| { |
| 584 | try argv.append(try arena.print("-DEF:{s}", .{def})); |
| 585 | } |
| 586 | |
| 587 | const resolved_subsystem: ?std.zig.Subsystem = blk: { |
| 588 | if (coff.subsystem) |explicit| break :blk explicit; |
| 589 | switch (target.os.tag) { |
| 590 | .windows => { |
| 591 | if (comp.zcu != null) { |
| 592 | if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib) |
| 593 | break :blk null; |
| 594 | if (coff.lld_export_flags.c_main or comp.config.is_test or |
| 595 | coff.lld_export_flags.winmain_crt_startup or |
| 596 | coff.lld_export_flags.wwinmain_crt_startup) |
| 597 | { |
| 598 | break :blk .console; |
| 599 | } |
| 600 | if (coff.lld_export_flags.winmain or coff.lld_export_flags.wwinmain) |
| 601 | break :blk .windows; |
| 602 | } |
| 603 | }, |
| 604 | .uefi => break :blk .efi_application, |
| 605 | else => {}, |
| 606 | } |
| 607 | break :blk null; |
| 608 | }; |
| 609 | |
| 610 | const Mode = enum { uefi, win32 }; |
| 611 | const mode: Mode = mode: { |
| 612 | if (resolved_subsystem) |subsystem| { |
| 613 | try argv.append(try arena.print("-SUBSYSTEM:{s},{d}.{d}", .{ |
| 614 | @tagName(subsystem), |
| 615 | coff.major_subsystem_version, |
| 616 | coff.minor_subsystem_version, |
| 617 | })); |
| 618 | break :mode switch (subsystem) { |
| 619 | .console, |
| 620 | .windows, |
| 621 | .posix, |
| 622 | .native, |
| 623 | => .win32, |
| 624 | .efi_application, |
| 625 | .efi_boot_service_driver, |
| 626 | .efi_rom, |
| 627 | .efi_runtime_driver, |
| 628 | => .uefi, |
| 629 | }; |
| 630 | } else if (target.os.tag == .uefi) { |
| 631 | break :mode .uefi; |
| 632 | } else { |
| 633 | break :mode .win32; |
| 634 | } |
| 635 | }; |
| 636 | |
| 637 | switch (mode) { |
| 638 | .uefi => try argv.appendSlice(&[_][]const u8{ |
| 639 | "-BASE:0", |
| 640 | "-ENTRY:EfiMain", |
| 641 | "-OPT:REF", |
| 642 | "-SAFESEH:NO", |
| 643 | "-MERGE:.rdata=.data", |
| 644 | "-NODEFAULTLIB", |
| 645 | "-SECTION:.xdata,D", |
| 646 | }), |
| 647 | .win32 => { |
| 648 | if (link_in_crt) { |
| 649 | if (target.abi.isGnu()) { |
| 650 | if (target.cpu.arch == .x86) { |
| 651 | try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); |
| 652 | } else { |
| 653 | try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); |
| 654 | } |
| 655 | |
| 656 | try argv.append(try comp.crtFileAsString(arena, if (is_dyn_lib) "dllcrt2.obj" else "crt2.obj")); |
| 657 | try argv.append(try comp.crtFileAsString(arena, "libmingw32.lib")); |
| 658 | } else { |
| 659 | try argv.append(switch (comp.config.link_mode) { |
| 660 | .static => "libcmt.lib", |
| 661 | .dynamic => "msvcrt.lib", |
| 662 | }); |
| 663 | |
| 664 | const lib_str = switch (comp.config.link_mode) { |
| 665 | .static => "lib", |
| 666 | .dynamic => "", |
| 667 | }; |
| 668 | try argv.append(try arena.print("{s}vcruntime.lib", .{lib_str})); |
| 669 | try argv.append(try arena.print("{s}ucrt.lib", .{lib_str})); |
| 670 | |
| 671 | //Visual C++ 2015 Conformance Changes |
| 672 | //https://msdn.microsoft.com/en-us/library/bb531344.aspx |
| 673 | try argv.append("legacy_stdio_definitions.lib"); |
| 674 | |
| 675 | // msvcrt depends on kernel32 and ntdll |
| 676 | try argv.append("kernel32.lib"); |
| 677 | try argv.append("ntdll.lib"); |
| 678 | } |
| 679 | } else { |
| 680 | try argv.append("-NODEFAULTLIB"); |
| 681 | if (!is_lib and entry_name == null) { |
| 682 | if (comp.zcu != null) { |
| 683 | if (coff.lld_export_flags.winmain_crt_startup) { |
| 684 | try argv.append("-ENTRY:WinMainCRTStartup"); |
| 685 | } else { |
| 686 | try argv.append("-ENTRY:wWinMainCRTStartup"); |
| 687 | } |
| 688 | } else { |
| 689 | try argv.append("-ENTRY:wWinMainCRTStartup"); |
| 690 | } |
| 691 | } |
| 692 | } |
| 693 | }, |
| 694 | } |
| 695 | |
| 696 | if (comp.config.link_libc and link_in_crt) { |
| 697 | if (comp.zigc_static_lib) |zigc| { |
| 698 | try argv.append(try zigc.full_object_path.toString(arena)); |
| 699 | } |
| 700 | } |
| 701 | |
| 702 | // libc++ dep |
| 703 | if (comp.config.link_libcpp) { |
| 704 | try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); |
| 705 | try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); |
| 706 | } |
| 707 | |
| 708 | // libunwind dep |
| 709 | if (comp.config.link_libunwind) { |
| 710 | try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); |
| 711 | } |
| 712 | |
| 713 | if (comp.config.any_fuzz) { |
| 714 | try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); |
| 715 | } |
| 716 | |
| 717 | const ubsan_rt_path: ?Cache.Path = blk: { |
| 718 | if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; |
| 719 | if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; |
| 720 | break :blk null; |
| 721 | }; |
| 722 | if (ubsan_rt_path) |path| { |
| 723 | try argv.append(try path.toString(arena)); |
| 724 | } |
| 725 | |
| 726 | if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) { |
| 727 | // MSVC compiler_rt is missing some stuff, so we build it unconditionally but |
| 728 | // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. |
| 729 | if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); |
| 730 | if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); |
| 731 | } |
| 732 | |
| 733 | try argv.ensureUnusedCapacity(comp.windows_libs.count()); |
| 734 | for (comp.windows_libs.keys()) |key| { |
| 735 | const lib_basename = try arena.print("{s}.lib", .{key}); |
| 736 | if (comp.crt_files.get(lib_basename)) |crt_file| { |
| 737 | argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena)); |
| 738 | continue; |
| 739 | } |
| 740 | if (try findLib(arena, io, lib_basename, coff.lib_directories)) |full_path| { |
| 741 | argv.appendAssumeCapacity(full_path); |
| 742 | continue; |
| 743 | } |
| 744 | if (target.abi.isGnu()) { |
| 745 | const fallback_name = try arena.print("lib{s}.dll.a", .{key}); |
| 746 | if (try findLib(arena, io, fallback_name, coff.lib_directories)) |full_path| { |
| 747 | argv.appendAssumeCapacity(full_path); |
| 748 | continue; |
| 749 | } |
| 750 | } |
| 751 | if (target.abi == .msvc or target.abi == .itanium) { |
| 752 | argv.appendAssumeCapacity(lib_basename); |
| 753 | continue; |
| 754 | } |
| 755 | |
| 756 | log.err("DLL import library for -l{s} not found", .{key}); |
| 757 | return error.DllImportLibraryNotFound; |
| 758 | } |
| 759 | |
| 760 | try spawnLld(comp, arena, argv.items); |
| 761 | } |
| 762 | } |
| 763 | fn findLib(arena: Allocator, io: Io, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 { |
| 764 | for (lib_directories) |lib_directory| { |
| 765 | lib_directory.handle.access(io, name, .{}) catch |err| switch (err) { |
| 766 | error.FileNotFound => continue, |
| 767 | else => |e| return e, |
| 768 | }; |
| 769 | return try lib_directory.join(arena, &.{name}); |
| 770 | } |
| 771 | return null; |
| 772 | } |
| 773 | |
| 774 | fn elfLink(lld: *Lld, arena: Allocator) !void { |
| 775 | const comp = lld.base.comp; |
| 776 | const gpa = comp.gpa; |
| 777 | const io = comp.io; |
| 778 | const diags = &comp.link_diags; |
| 779 | const base = &lld.base; |
| 780 | const elf = &lld.ofmt.elf; |
| 781 | |
| 782 | const directory = base.emit.root_dir; // Just an alias to make it shorter to type. |
| 783 | const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); |
| 784 | |
| 785 | const zcu_obj_path: ?Cache.Path = if (comp.zcu) |zcu| p: { |
| 786 | break :p try comp.resolveEmitPathFlush(arena, .temp, zcu.llvm_object.?.out_bin_basename); |
| 787 | } else null; |
| 788 | |
| 789 | const output_mode = comp.config.output_mode; |
| 790 | const is_obj = output_mode == .Obj; |
| 791 | const is_lib = output_mode == .Lib; |
| 792 | const link_mode = comp.config.link_mode; |
| 793 | const is_dyn_lib = link_mode == .dynamic and is_lib; |
| 794 | const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe; |
| 795 | const target = &comp.root_mod.resolved_target.result; |
| 796 | const compiler_rt_path: ?Cache.Path = blk: { |
| 797 | if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; |
| 798 | if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; |
| 799 | break :blk null; |
| 800 | }; |
| 801 | const ubsan_rt_path: ?Cache.Path = blk: { |
| 802 | if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path; |
| 803 | if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path; |
| 804 | break :blk null; |
| 805 | }; |
| 806 | |
| 807 | // Due to a deficiency in LLD, we need to special-case BPF to a simple file |
| 808 | // copy when generating relocatables. Normally, we would expect `lld -r` to work. |
| 809 | // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails |
| 810 | // before even generating the relocatable. |
| 811 | // |
| 812 | // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can |
| 813 | // produce usable object files. |
| 814 | if (output_mode == .Obj and |
| 815 | (comp.config.lto != .none or |
| 816 | target.cpu.arch.isBpf() or |
| 817 | target.cpu.arch == .lanai or |
| 818 | target.cpu.arch == .m68k or |
| 819 | target.cpu.arch.isSPARC() or |
| 820 | target.cpu.arch == .ve or |
| 821 | target.cpu.arch == .xcore or |
| 822 | target.cpu.arch == .xtensa)) |
| 823 | { |
| 824 | // In this case we must do a simple file copy |
| 825 | // here. TODO: think carefully about how we can avoid this redundant operation when doing |
| 826 | // build-obj. See also the corresponding TODO in linkAsArchive. |
| 827 | const the_object_path = blk: { |
| 828 | if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; |
| 829 | |
| 830 | if (comp.c_objects.items.len != 0) |
| 831 | break :blk comp.c_objects.items[0].status.success.object_path; |
| 832 | |
| 833 | if (zcu_obj_path) |p| |
| 834 | break :blk p; |
| 835 | |
| 836 | // TODO I think this is unreachable. Audit this situation when solving the above TODO |
| 837 | // regarding eliding redundant object -> object transformations. |
| 838 | return error.NoObjectsToLink; |
| 839 | }; |
| 840 | try Io.Dir.copyFile( |
| 841 | the_object_path.root_dir.handle, |
| 842 | the_object_path.sub_path, |
| 843 | directory.handle, |
| 844 | base.emit.sub_path, |
| 845 | io, |
| 846 | .{}, |
| 847 | ); |
| 848 | } else { |
| 849 | // Create an LLD command line and invoke it. |
| 850 | var argv = std.array_list.Managed([]const u8).init(gpa); |
| 851 | defer argv.deinit(); |
| 852 | // We will invoke ourselves as a child process to gain access to LLD. |
| 853 | // This is necessary because LLD does not behave properly as a library - |
| 854 | // it calls exit() and does not reset all global data between invocations. |
| 855 | const linker_command = "ld.lld"; |
| 856 | try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); |
| 857 | try addCommonArgs(&argv, false); |
| 858 | |
| 859 | if (is_obj) { |
| 860 | try argv.append("-r"); |
| 861 | } |
| 862 | |
| 863 | try argv.append("--error-limit=0"); |
| 864 | |
| 865 | if (comp.sysroot) |sysroot| { |
| 866 | try argv.append(try arena.print("--sysroot={s}", .{sysroot})); |
| 867 | } |
| 868 | |
| 869 | if (target_util.llvmMachineAbi(target)) |mabi| { |
| 870 | try argv.appendSlice(&.{ |
| 871 | "-mllvm", |
| 872 | try arena.print("-target-abi={s}", .{mabi}), |
| 873 | }); |
| 874 | } |
| 875 | |
| 876 | try argv.appendSlice(&.{ |
| 877 | "-mllvm", |
| 878 | try arena.print("-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}), |
| 879 | }); |
| 880 | |
| 881 | switch (target.cpu.arch) { |
| 882 | .armeb, .thumbeb => if (is_exe_or_dyn_lib and target.cpu.has(.arm, .has_v6)) try argv.append("--be8"), |
| 883 | else => {}, |
| 884 | } |
| 885 | |
| 886 | if (comp.config.lto != .none) { |
| 887 | switch (comp.root_mod.optimize_mode) { |
| 888 | .debug => {}, |
| 889 | .small => try argv.append("--lto-O2"), |
| 890 | .fast, .safe => try argv.append("--lto-O3"), |
| 891 | } |
| 892 | } |
| 893 | switch (comp.root_mod.optimize_mode) { |
| 894 | .debug => {}, |
| 895 | .small => try argv.append("-O2"), |
| 896 | .fast, .safe => try argv.append("-O3"), |
| 897 | } |
| 898 | |
| 899 | if (elf.entry_name) |name| { |
| 900 | try argv.appendSlice(&.{ "--entry", name }); |
| 901 | } |
| 902 | |
| 903 | for (comp.force_undefined_symbols.keys()) |sym| { |
| 904 | try argv.append("-u"); |
| 905 | try argv.append(sym); |
| 906 | } |
| 907 | |
| 908 | switch (elf.hash_style) { |
| 909 | .gnu => try argv.append("--hash-style=gnu"), |
| 910 | .sysv => try argv.append("--hash-style=sysv"), |
| 911 | .both => {}, // this is the default |
| 912 | } |
| 913 | |
| 914 | if (output_mode == .Exe) { |
| 915 | try argv.appendSlice(&.{ |
| 916 | "-z", |
| 917 | try arena.print("stack-size={d}", .{base.stack_size}), |
| 918 | }); |
| 919 | } |
| 920 | |
| 921 | switch (base.build_id) { |
| 922 | .none => try argv.append("--build-id=none"), |
| 923 | .fast, .uuid, .sha1, .md5 => try argv.append(try arena.print("--build-id={s}", .{ |
| 924 | @tagName(base.build_id), |
| 925 | })), |
| 926 | .hexstring => |hs| try argv.append(try arena.print("--build-id=0x{x}", .{hs.toSlice()})), |
| 927 | } |
| 928 | |
| 929 | try argv.append(try arena.print("--image-base={d}", .{elf.image_base})); |
| 930 | |
| 931 | if (elf.linker_script) |linker_script| { |
| 932 | try argv.append("-T"); |
| 933 | try argv.append(try linker_script.toString(arena)); |
| 934 | } |
| 935 | |
| 936 | if (elf.sort_section) |how| { |
| 937 | const arg = try arena.print("--sort-section={s}", .{@tagName(how)}); |
| 938 | try argv.append(arg); |
| 939 | } |
| 940 | |
| 941 | if (base.gc_sections) { |
| 942 | try argv.append("--gc-sections"); |
| 943 | } |
| 944 | |
| 945 | if (base.print_gc_sections) { |
| 946 | try argv.append("--print-gc-sections"); |
| 947 | } |
| 948 | |
| 949 | if (elf.print_icf_sections) { |
| 950 | try argv.append("--print-icf-sections"); |
| 951 | } |
| 952 | |
| 953 | if (elf.print_map) { |
| 954 | try argv.append("--print-map"); |
| 955 | } |
| 956 | |
| 957 | if (elf.nmagic) { |
| 958 | try argv.append("--nmagic"); |
| 959 | } |
| 960 | |
| 961 | if (elf.fatal_warnings) { |
| 962 | try argv.append("--fatal-warnings"); |
| 963 | } |
| 964 | |
| 965 | if (comp.link_eh_frame_hdr) { |
| 966 | try argv.append("--eh-frame-hdr"); |
| 967 | } |
| 968 | |
| 969 | if (elf.emit_relocs) { |
| 970 | try argv.append("--emit-relocs"); |
| 971 | } |
| 972 | |
| 973 | if (comp.config.rdynamic) { |
| 974 | try argv.append("--export-dynamic"); |
| 975 | } |
| 976 | |
| 977 | if (comp.config.debug_format == .strip) { |
| 978 | try argv.append("-s"); |
| 979 | } |
| 980 | |
| 981 | if (elf.z_nodelete) { |
| 982 | try argv.append("-z"); |
| 983 | try argv.append("nodelete"); |
| 984 | } |
| 985 | if (elf.z_notext) { |
| 986 | try argv.append("-z"); |
| 987 | try argv.append("notext"); |
| 988 | } |
| 989 | if (elf.z_defs) { |
| 990 | try argv.append("-z"); |
| 991 | try argv.append("defs"); |
| 992 | } |
| 993 | if (elf.z_origin) { |
| 994 | try argv.append("-z"); |
| 995 | try argv.append("origin"); |
| 996 | } |
| 997 | if (elf.z_nocopyreloc) { |
| 998 | try argv.append("-z"); |
| 999 | try argv.append("nocopyreloc"); |
| 1000 | } |
| 1001 | if (elf.z_now) { |
| 1002 | // LLD defaults to -zlazy |
| 1003 | try argv.append("-znow"); |
| 1004 | } |
| 1005 | if (!elf.z_relro) { |
| 1006 | // LLD defaults to -zrelro |
| 1007 | try argv.append("-znorelro"); |
| 1008 | } |
| 1009 | if (elf.z_common_page_size) |size| { |
| 1010 | try argv.append("-z"); |
| 1011 | try argv.append(try arena.print("common-page-size={d}", .{size})); |
| 1012 | } |
| 1013 | if (elf.z_max_page_size) |size| { |
| 1014 | try argv.append("-z"); |
| 1015 | try argv.append(try arena.print("max-page-size={d}", .{size})); |
| 1016 | } |
| 1017 | |
| 1018 | if (getLDMOption(target)) |ldm| { |
| 1019 | try argv.append("-m"); |
| 1020 | try argv.append(ldm); |
| 1021 | } |
| 1022 | |
| 1023 | if (link_mode == .static) { |
| 1024 | if (target.cpu.arch.isArm()) { |
| 1025 | try argv.append("-Bstatic"); |
| 1026 | } else { |
| 1027 | try argv.append("-static"); |
| 1028 | } |
| 1029 | } else if (switch (target.os.tag) { |
| 1030 | else => is_dyn_lib, |
| 1031 | .haiku => is_exe_or_dyn_lib, |
| 1032 | }) { |
| 1033 | try argv.append("-shared"); |
| 1034 | } |
| 1035 | |
| 1036 | if (comp.config.pie and output_mode == .Exe) { |
| 1037 | try argv.append("-pie"); |
| 1038 | } |
| 1039 | |
| 1040 | if (is_exe_or_dyn_lib and target.os.tag == .netbsd) { |
| 1041 | // Add options to produce shared objects with only 2 PT.LOAD segments. |
| 1042 | // NetBSD expects 2 PT.LOAD segments in a shared object, otherwise |
| 1043 | // ld.elf_so fails loading dynamic libraries with "not found" error. |
| 1044 | // See https://github.com/ziglang/zig/issues/9109 . |
| 1045 | try argv.append("--no-rosegment"); |
| 1046 | try argv.append("-znorelro"); |
| 1047 | } |
| 1048 | |
| 1049 | try argv.append("-o"); |
| 1050 | try argv.append(full_out_path); |
| 1051 | |
| 1052 | // csu prelude |
| 1053 | const csu = try comp.getCrtPaths(arena); |
| 1054 | if (csu.crt0) |p| try argv.append(try p.toString(arena)); |
| 1055 | if (csu.crti) |p| try argv.append(try p.toString(arena)); |
| 1056 | if (csu.crtbegin) |p| try argv.append(try p.toString(arena)); |
| 1057 | |
| 1058 | for (elf.rpath_list) |rpath| { |
| 1059 | try argv.appendSlice(&.{ "-rpath", rpath }); |
| 1060 | } |
| 1061 | |
| 1062 | for (elf.symbol_wrap_set) |symbol_name| { |
| 1063 | try argv.appendSlice(&.{ "-wrap", symbol_name }); |
| 1064 | } |
| 1065 | |
| 1066 | if (comp.config.link_libc) { |
| 1067 | if (comp.libc_installation) |libc_installation| { |
| 1068 | try argv.append("-L"); |
| 1069 | try argv.append(libc_installation.crt_dir.?); |
| 1070 | } |
| 1071 | } |
| 1072 | |
| 1073 | if (output_mode == .Exe and link_mode == .dynamic) { |
| 1074 | if (target.dynamic_linker.get()) |dynamic_linker| { |
| 1075 | try argv.append("--dynamic-linker"); |
| 1076 | try argv.append(dynamic_linker); |
| 1077 | } else { |
| 1078 | try argv.append("--no-dynamic-linker"); |
| 1079 | } |
| 1080 | } |
| 1081 | |
| 1082 | if (is_dyn_lib) { |
| 1083 | if (elf.soname) |soname| { |
| 1084 | try argv.append("-soname"); |
| 1085 | try argv.append(soname); |
| 1086 | } |
| 1087 | if (elf.version_script) |version_script| { |
| 1088 | try argv.append("-version-script"); |
| 1089 | try argv.append(try version_script.toString(arena)); |
| 1090 | } |
| 1091 | if (elf.allow_undefined_version) { |
| 1092 | try argv.append("--undefined-version"); |
| 1093 | } else { |
| 1094 | try argv.append("--no-undefined-version"); |
| 1095 | } |
| 1096 | if (elf.enable_new_dtags) |enable_new_dtags| { |
| 1097 | if (enable_new_dtags) { |
| 1098 | try argv.append("--enable-new-dtags"); |
| 1099 | } else { |
| 1100 | try argv.append("--disable-new-dtags"); |
| 1101 | } |
| 1102 | } |
| 1103 | } |
| 1104 | |
| 1105 | // Positional arguments to the linker such as object files. |
| 1106 | var whole_archive = false; |
| 1107 | |
| 1108 | for (base.comp.link_inputs) |link_input| switch (link_input) { |
| 1109 | .res => unreachable, // Windows-only |
| 1110 | .dso => continue, |
| 1111 | .object, .archive => |obj| { |
| 1112 | if (obj.must_link and !whole_archive) { |
| 1113 | try argv.append("--whole-archive"); |
| 1114 | whole_archive = true; |
| 1115 | } else if (!obj.must_link and whole_archive) { |
| 1116 | try argv.append("--no-whole-archive"); |
| 1117 | whole_archive = false; |
| 1118 | } |
| 1119 | try argv.append(try obj.path.toString(arena)); |
| 1120 | }, |
| 1121 | .dso_exact => |dso_exact| { |
| 1122 | assert(dso_exact.name[0] == ':'); |
| 1123 | try argv.appendSlice(&.{ "-l", dso_exact.name }); |
| 1124 | }, |
| 1125 | }; |
| 1126 | |
| 1127 | if (whole_archive) { |
| 1128 | try argv.append("--no-whole-archive"); |
| 1129 | whole_archive = false; |
| 1130 | } |
| 1131 | |
| 1132 | for (comp.c_objects.items) |c_object| { |
| 1133 | try argv.append(try c_object.status.success.object_path.toString(arena)); |
| 1134 | } |
| 1135 | |
| 1136 | if (zcu_obj_path) |p| { |
| 1137 | try argv.append(try p.toString(arena)); |
| 1138 | } |
| 1139 | |
| 1140 | if (comp.tsan_lib) |lib| { |
| 1141 | assert(comp.config.any_sanitize_thread); |
| 1142 | try argv.appendSlice(&.{ |
| 1143 | "--whole-archive", |
| 1144 | try lib.full_object_path.toString(arena), |
| 1145 | "--no-whole-archive", |
| 1146 | }); |
| 1147 | } |
| 1148 | |
| 1149 | if (comp.fuzzer_lib) |lib| { |
| 1150 | assert(comp.config.any_fuzz); |
| 1151 | try argv.append(try lib.full_object_path.toString(arena)); |
| 1152 | } |
| 1153 | |
| 1154 | if (ubsan_rt_path) |p| { |
| 1155 | try argv.append(try p.toString(arena)); |
| 1156 | } |
| 1157 | |
| 1158 | // Shared libraries. |
| 1159 | if (is_exe_or_dyn_lib) { |
| 1160 | // Worst-case, we need an --as-needed argument for every lib, as well |
| 1161 | // as one before and one after. |
| 1162 | try argv.ensureUnusedCapacity(2 * base.comp.link_inputs.len + 2); |
| 1163 | argv.appendAssumeCapacity("--as-needed"); |
| 1164 | var as_needed = true; |
| 1165 | |
| 1166 | for (base.comp.link_inputs) |link_input| switch (link_input) { |
| 1167 | .res => unreachable, // Windows-only |
| 1168 | .object, .archive, .dso_exact => continue, |
| 1169 | .dso => |dso| { |
| 1170 | const lib_as_needed = !dso.needed; |
| 1171 | switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { |
| 1172 | 0b00, 0b11 => {}, |
| 1173 | 0b01 => { |
| 1174 | argv.appendAssumeCapacity("--no-as-needed"); |
| 1175 | as_needed = false; |
| 1176 | }, |
| 1177 | 0b10 => { |
| 1178 | argv.appendAssumeCapacity("--as-needed"); |
| 1179 | as_needed = true; |
| 1180 | }, |
| 1181 | } |
| 1182 | |
| 1183 | // By this time, we depend on these libs being dynamically linked |
| 1184 | // libraries and not static libraries (the check for that needs to be earlier), |
| 1185 | // but they could be full paths to .so files, in which case we |
| 1186 | // want to avoid prepending "-l". |
| 1187 | argv.appendAssumeCapacity(try dso.path.toString(arena)); |
| 1188 | }, |
| 1189 | }; |
| 1190 | |
| 1191 | if (!as_needed) { |
| 1192 | argv.appendAssumeCapacity("--as-needed"); |
| 1193 | as_needed = true; |
| 1194 | } |
| 1195 | |
| 1196 | // libc++ dep |
| 1197 | if (comp.config.link_libcpp) { |
| 1198 | try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); |
| 1199 | try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); |
| 1200 | } |
| 1201 | |
| 1202 | // libunwind dep |
| 1203 | if (comp.config.link_libunwind) { |
| 1204 | try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena)); |
| 1205 | } |
| 1206 | |
| 1207 | // libc dep |
| 1208 | diags.flags.missing_libc = false; |
| 1209 | if (comp.config.link_libc) { |
| 1210 | if (comp.libc_installation != null) { |
| 1211 | const needs_grouping = link_mode == .static; |
| 1212 | if (needs_grouping) try argv.append("--start-group"); |
| 1213 | try argv.appendSlice(target_util.libcFullLinkFlags(target)); |
| 1214 | if (needs_grouping) try argv.append("--end-group"); |
| 1215 | } else if (target.isGnuLibC()) { |
| 1216 | for (glibc.libs) |lib| { |
| 1217 | if (lib.removed_in) |rem_in| { |
| 1218 | if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue; |
| 1219 | } |
| 1220 | |
| 1221 | const lib_path = try arena.print("{f}{c}lib{s}.so.{d}", .{ |
| 1222 | comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, |
| 1223 | }); |
| 1224 | try argv.append(lib_path); |
| 1225 | } |
| 1226 | try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a")); |
| 1227 | } else if (target.isMuslLibC()) { |
| 1228 | try argv.append(try comp.crtFileAsString(arena, switch (link_mode) { |
| 1229 | .static => "libc.a", |
| 1230 | .dynamic => "libc.so", |
| 1231 | })); |
| 1232 | } else if (target.isFreeBSDLibC()) { |
| 1233 | for (freebsd.libs) |lib| { |
| 1234 | if (lib.added_in) |add_in| { |
| 1235 | if (target.os.version_range.semver.min.order(add_in) == .lt) continue; |
| 1236 | } |
| 1237 | |
| 1238 | const lib_path = try arena.print("{f}{c}lib{s}.so.{d}", .{ |
| 1239 | comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.getSoVersion(&target.os), |
| 1240 | }); |
| 1241 | try argv.append(lib_path); |
| 1242 | } |
| 1243 | } else if (target.isNetBSDLibC()) { |
| 1244 | for (netbsd.libs) |lib| { |
| 1245 | const lib_path = try arena.print("{f}{c}lib{s}.so.{d}", .{ |
| 1246 | comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, |
| 1247 | }); |
| 1248 | try argv.append(lib_path); |
| 1249 | } |
| 1250 | } else if (target.isOpenBSDLibC()) { |
| 1251 | for (openbsd.libs) |lib| { |
| 1252 | const lib_path = try arena.print("{f}{c}lib{s}.so", .{ |
| 1253 | comp.openbsd_so_files.?.dir_path, fs.path.sep, lib.name, |
| 1254 | }); |
| 1255 | try argv.append(lib_path); |
| 1256 | } |
| 1257 | } else { |
| 1258 | diags.flags.missing_libc = true; |
| 1259 | } |
| 1260 | |
| 1261 | if (comp.zigc_static_lib) |zigc| { |
| 1262 | try argv.append(try zigc.full_object_path.toString(arena)); |
| 1263 | } |
| 1264 | } |
| 1265 | } |
| 1266 | |
| 1267 | // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs |
| 1268 | // to be after the shared libraries, so they are picked up from the shared |
| 1269 | // libraries, not libcompiler_rt. |
| 1270 | if (compiler_rt_path) |p| { |
| 1271 | try argv.append(try p.toString(arena)); |
| 1272 | } |
| 1273 | |
| 1274 | // crt postlude |
| 1275 | if (csu.crtend) |p| try argv.append(try p.toString(arena)); |
| 1276 | if (csu.crtn) |p| try argv.append(try p.toString(arena)); |
| 1277 | |
| 1278 | if (base.allow_shlib_undefined) { |
| 1279 | try argv.append("--allow-shlib-undefined"); |
| 1280 | } |
| 1281 | |
| 1282 | switch (elf.compress_debug_sections) { |
| 1283 | .none => {}, |
| 1284 | .zlib => try argv.append("--compress-debug-sections=zlib"), |
| 1285 | .zstd => try argv.append("--compress-debug-sections=zstd"), |
| 1286 | } |
| 1287 | |
| 1288 | if (elf.bind_global_refs_locally) { |
| 1289 | try argv.append("-Bsymbolic"); |
| 1290 | } |
| 1291 | |
| 1292 | try spawnLld(comp, arena, argv.items); |
| 1293 | } |
| 1294 | } |
| 1295 | fn getLDMOption(target: *const std.Target) ?[]const u8 { |
| 1296 | // This should only return emulations understood by LLD's parseEmulation(). |
| 1297 | return switch (target.cpu.arch) { |
| 1298 | .aarch64 => switch (target.os.tag) { |
| 1299 | .linux => "aarch64linux", |
| 1300 | else => "aarch64elf", |
| 1301 | }, |
| 1302 | .aarch64_be => switch (target.os.tag) { |
| 1303 | .linux => "aarch64linuxb", |
| 1304 | else => "aarch64elfb", |
| 1305 | }, |
| 1306 | .amdgcn => "elf64_amdgpu", |
| 1307 | .arm, .thumb => switch (target.os.tag) { |
| 1308 | .linux => "armelf_linux_eabi", |
| 1309 | else => "armelf", |
| 1310 | }, |
| 1311 | .armeb, .thumbeb => switch (target.os.tag) { |
| 1312 | .linux => "armelfb_linux_eabi", |
| 1313 | else => "armelfb", |
| 1314 | }, |
| 1315 | .hexagon => "hexagonelf", |
| 1316 | .loongarch32 => "elf32loongarch", |
| 1317 | .loongarch64 => "elf64loongarch", |
| 1318 | .mips => switch (target.os.tag) { |
| 1319 | .freebsd => "elf32btsmip_fbsd", |
| 1320 | else => "elf32btsmip", |
| 1321 | }, |
| 1322 | .mipsel => switch (target.os.tag) { |
| 1323 | .freebsd => "elf32ltsmip_fbsd", |
| 1324 | else => "elf32ltsmip", |
| 1325 | }, |
| 1326 | .mips64 => switch (target.os.tag) { |
| 1327 | .freebsd => switch (target.abi) { |
| 1328 | .gnuabin32, .muslabin32, .abin32 => "elf32btsmipn32_fbsd", |
| 1329 | else => "elf64btsmip_fbsd", |
| 1330 | }, |
| 1331 | else => switch (target.abi) { |
| 1332 | .gnuabin32, .muslabin32, .abin32 => "elf32btsmipn32", |
| 1333 | else => "elf64btsmip", |
| 1334 | }, |
| 1335 | }, |
| 1336 | .mips64el => switch (target.os.tag) { |
| 1337 | .freebsd => switch (target.abi) { |
| 1338 | .gnuabin32, .muslabin32, .abin32 => "elf32ltsmipn32_fbsd", |
| 1339 | else => "elf64ltsmip_fbsd", |
| 1340 | }, |
| 1341 | else => switch (target.abi) { |
| 1342 | .gnuabin32, .muslabin32, .abin32 => "elf32ltsmipn32", |
| 1343 | else => "elf64ltsmip", |
| 1344 | }, |
| 1345 | }, |
| 1346 | .msp430 => "msp430elf", |
| 1347 | .powerpc => switch (target.os.tag) { |
| 1348 | .freebsd => "elf32ppc_fbsd", |
| 1349 | .linux => "elf32ppclinux", |
| 1350 | else => "elf32ppc", |
| 1351 | }, |
| 1352 | .powerpcle => switch (target.os.tag) { |
| 1353 | .linux => "elf32lppclinux", |
| 1354 | else => "elf32lppc", |
| 1355 | }, |
| 1356 | .powerpc64 => "elf64ppc", |
| 1357 | .powerpc64le => "elf64lppc", |
| 1358 | .riscv32 => "elf32lriscv", |
| 1359 | .riscv32be => "elf32briscv", |
| 1360 | .riscv64 => "elf64lriscv", |
| 1361 | .riscv64be => "elf64briscv", |
| 1362 | .s390x => "elf64_s390", |
| 1363 | .sparc64 => "elf64_sparc", |
| 1364 | .x86 => switch (target.os.tag) { |
| 1365 | .freebsd => "elf_i386_fbsd", |
| 1366 | else => "elf_i386", |
| 1367 | }, |
| 1368 | .x86_64 => switch (target.abi) { |
| 1369 | .gnux32, .muslx32, .x32 => "elf32_x86_64", |
| 1370 | else => "elf_x86_64", |
| 1371 | }, |
| 1372 | else => null, |
| 1373 | }; |
| 1374 | } |
| 1375 | fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1376 | const comp = lld.base.comp; |
| 1377 | const diags = &comp.link_diags; |
| 1378 | const shared_memory = comp.config.shared_memory; |
| 1379 | const export_memory = comp.config.export_memory; |
| 1380 | const import_memory = comp.config.import_memory; |
| 1381 | const target = &comp.root_mod.resolved_target.result; |
| 1382 | const base = &lld.base; |
| 1383 | const wasm = &lld.ofmt.wasm; |
| 1384 | |
| 1385 | const gpa = comp.gpa; |
| 1386 | const io = comp.io; |
| 1387 | |
| 1388 | const directory = base.emit.root_dir; // Just an alias to make it shorter to type. |
| 1389 | const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path}); |
| 1390 | |
| 1391 | const zcu_obj_path: ?Cache.Path = if (comp.zcu) |zcu| p: { |
| 1392 | break :p try comp.resolveEmitPathFlush(arena, .temp, zcu.llvm_object.?.out_bin_basename); |
| 1393 | } else null; |
| 1394 | |
| 1395 | const is_obj = comp.config.output_mode == .Obj; |
| 1396 | const compiler_rt_path: ?Cache.Path = blk: { |
| 1397 | if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path; |
| 1398 | if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path; |
| 1399 | break :blk null; |
| 1400 | }; |
| 1401 | const ubsan_rt_path: ?Cache.Path = blk: { |
| 1402 | if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path; |
| 1403 | if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path; |
| 1404 | break :blk null; |
| 1405 | }; |
| 1406 | |
| 1407 | if (is_obj) { |
| 1408 | // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy |
| 1409 | // here. TODO: think carefully about how we can avoid this redundant operation when doing |
| 1410 | // build-obj. See also the corresponding TODO in linkAsArchive. |
| 1411 | const the_object_path = blk: { |
| 1412 | if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path; |
| 1413 | |
| 1414 | if (comp.c_objects.items.len != 0) |
| 1415 | break :blk comp.c_objects.items[0].status.success.object_path; |
| 1416 | |
| 1417 | if (zcu_obj_path) |p| |
| 1418 | break :blk p; |
| 1419 | |
| 1420 | // TODO I think this is unreachable. Audit this situation when solving the above TODO |
| 1421 | // regarding eliding redundant object -> object transformations. |
| 1422 | return error.NoObjectsToLink; |
| 1423 | }; |
| 1424 | try Io.Dir.copyFile( |
| 1425 | the_object_path.root_dir.handle, |
| 1426 | the_object_path.sub_path, |
| 1427 | directory.handle, |
| 1428 | base.emit.sub_path, |
| 1429 | io, |
| 1430 | .{}, |
| 1431 | ); |
| 1432 | } else { |
| 1433 | // Create an LLD command line and invoke it. |
| 1434 | var argv = std.array_list.Managed([]const u8).init(gpa); |
| 1435 | defer argv.deinit(); |
| 1436 | // We will invoke ourselves as a child process to gain access to LLD. |
| 1437 | // This is necessary because LLD does not behave properly as a library - |
| 1438 | // it calls exit() and does not reset all global data between invocations. |
| 1439 | const linker_command = "wasm-ld"; |
| 1440 | try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); |
| 1441 | try addCommonArgs(&argv, false); |
| 1442 | |
| 1443 | try argv.append("--error-limit=0"); |
| 1444 | |
| 1445 | if (comp.config.lto != .none) { |
| 1446 | switch (comp.root_mod.optimize_mode) { |
| 1447 | .debug => {}, |
| 1448 | .small => try argv.append("-O2"), |
| 1449 | .fast, .safe => try argv.append("-O3"), |
| 1450 | } |
| 1451 | } |
| 1452 | |
| 1453 | if (import_memory) { |
| 1454 | try argv.append("--import-memory"); |
| 1455 | } |
| 1456 | |
| 1457 | if (export_memory) { |
| 1458 | try argv.append("--export-memory"); |
| 1459 | } |
| 1460 | |
| 1461 | if (wasm.import_table) { |
| 1462 | assert(!wasm.export_table); |
| 1463 | try argv.append("--import-table"); |
| 1464 | } |
| 1465 | |
| 1466 | if (wasm.export_table) { |
| 1467 | assert(!wasm.import_table); |
| 1468 | try argv.append("--export-table"); |
| 1469 | } |
| 1470 | |
| 1471 | if (wasm.growable_table) { |
| 1472 | try argv.append("--growable-table"); |
| 1473 | } |
| 1474 | |
| 1475 | // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly |
| 1476 | // specified it as garbage collection is enabled by default. |
| 1477 | if (!base.gc_sections) { |
| 1478 | try argv.append("--no-gc-sections"); |
| 1479 | } |
| 1480 | |
| 1481 | if (comp.config.debug_format == .strip) { |
| 1482 | try argv.append("-s"); |
| 1483 | } |
| 1484 | |
| 1485 | if (wasm.initial_memory) |initial_memory| { |
| 1486 | const arg = try arena.print("--initial-memory={d}", .{initial_memory}); |
| 1487 | try argv.append(arg); |
| 1488 | } |
| 1489 | |
| 1490 | if (wasm.max_memory) |max_memory| { |
| 1491 | const arg = try arena.print("--max-memory={d}", .{max_memory}); |
| 1492 | try argv.append(arg); |
| 1493 | } |
| 1494 | |
| 1495 | if (shared_memory) { |
| 1496 | try argv.append("--shared-memory"); |
| 1497 | } |
| 1498 | |
| 1499 | if (wasm.global_base) |global_base| { |
| 1500 | const arg = try arena.print("--global-base={d}", .{global_base}); |
| 1501 | try argv.append(arg); |
| 1502 | } else { |
| 1503 | // We prepend it by default, so when a stack overflow happens the runtime will trap correctly, |
| 1504 | // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496 |
| 1505 | // |
| 1506 | // The user can overwrite this behavior by setting the global-base |
| 1507 | try argv.append("--stack-first"); |
| 1508 | } |
| 1509 | |
| 1510 | // Users are allowed to specify which symbols they want to export to the wasm host. |
| 1511 | for (wasm.export_symbol_names) |symbol_name| { |
| 1512 | const arg = try arena.print("--export={s}", .{symbol_name}); |
| 1513 | try argv.append(arg); |
| 1514 | } |
| 1515 | |
| 1516 | if (comp.config.rdynamic) { |
| 1517 | try argv.append("--export-dynamic"); |
| 1518 | } |
| 1519 | |
| 1520 | if (wasm.entry_name) |entry_name| { |
| 1521 | try argv.appendSlice(&.{ "--entry", entry_name }); |
| 1522 | } else { |
| 1523 | try argv.append("--no-entry"); |
| 1524 | } |
| 1525 | |
| 1526 | try argv.appendSlice(&.{ |
| 1527 | "-z", |
| 1528 | try arena.print("stack-size={d}", .{base.stack_size}), |
| 1529 | }); |
| 1530 | |
| 1531 | switch (base.build_id) { |
| 1532 | .none => try argv.append("--build-id=none"), |
| 1533 | .fast, .uuid, .sha1 => try argv.append(try arena.print("--build-id={s}", .{ |
| 1534 | @tagName(base.build_id), |
| 1535 | })), |
| 1536 | .hexstring => |hs| try argv.append(try arena.print("--build-id=0x{x}", .{hs.toSlice()})), |
| 1537 | .md5 => {}, |
| 1538 | } |
| 1539 | |
| 1540 | if (wasm.import_symbols) { |
| 1541 | try argv.append("--allow-undefined"); |
| 1542 | } |
| 1543 | |
| 1544 | if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) { |
| 1545 | try argv.append("--shared"); |
| 1546 | } |
| 1547 | if (comp.config.pie) { |
| 1548 | try argv.append("--pie"); |
| 1549 | } |
| 1550 | |
| 1551 | try argv.appendSlice(&.{ "-o", full_out_path }); |
| 1552 | |
| 1553 | if (target.cpu.arch == .wasm64) { |
| 1554 | try argv.append("-mwasm64"); |
| 1555 | } |
| 1556 | |
| 1557 | const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or |
| 1558 | (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); |
| 1559 | |
| 1560 | if (comp.config.link_libc and is_exe_or_dyn_lib) { |
| 1561 | if (target.os.tag == .wasi) { |
| 1562 | try argv.append(try comp.crtFileAsString( |
| 1563 | arena, |
| 1564 | wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model), |
| 1565 | )); |
| 1566 | try argv.append(try comp.crtFileAsString(arena, "libc.a")); |
| 1567 | } |
| 1568 | |
| 1569 | if (comp.zigc_static_lib) |zigc| { |
| 1570 | try argv.append(try zigc.full_object_path.toString(arena)); |
| 1571 | } |
| 1572 | |
| 1573 | if (comp.config.link_libcpp) { |
| 1574 | try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); |
| 1575 | try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); |
| 1576 | } |
| 1577 | } |
| 1578 | |
| 1579 | // Positional arguments to the linker such as object files. |
| 1580 | var whole_archive = false; |
| 1581 | for (comp.link_inputs) |link_input| switch (link_input) { |
| 1582 | .object, .archive => |obj| { |
| 1583 | if (obj.must_link and !whole_archive) { |
| 1584 | try argv.append("--whole-archive"); |
| 1585 | whole_archive = true; |
| 1586 | } else if (!obj.must_link and whole_archive) { |
| 1587 | try argv.append("--no-whole-archive"); |
| 1588 | whole_archive = false; |
| 1589 | } |
| 1590 | try argv.append(try obj.path.toString(arena)); |
| 1591 | }, |
| 1592 | .dso => |dso| { |
| 1593 | try argv.append(try dso.path.toString(arena)); |
| 1594 | }, |
| 1595 | .dso_exact => unreachable, |
| 1596 | .res => unreachable, |
| 1597 | }; |
| 1598 | if (whole_archive) { |
| 1599 | try argv.append("--no-whole-archive"); |
| 1600 | whole_archive = false; |
| 1601 | } |
| 1602 | |
| 1603 | for (comp.c_objects.items) |c_object| { |
| 1604 | try argv.append(try c_object.status.success.object_path.toString(arena)); |
| 1605 | } |
| 1606 | if (zcu_obj_path) |p| { |
| 1607 | try argv.append(try p.toString(arena)); |
| 1608 | } |
| 1609 | |
| 1610 | if (compiler_rt_path) |p| { |
| 1611 | try argv.append(try p.toString(arena)); |
| 1612 | } |
| 1613 | |
| 1614 | if (ubsan_rt_path) |p| { |
| 1615 | try argv.append(try p.toStringZ(arena)); |
| 1616 | } |
| 1617 | |
| 1618 | try spawnLld(comp, arena, argv.items); |
| 1619 | |
| 1620 | // Give +x to the .wasm file if it is an executable and the OS is WASI. |
| 1621 | // Some systems may be configured to execute such binaries directly. Even if that |
| 1622 | // is not the case, it means we will get "exec format error" when trying to run |
| 1623 | // it, and then can react to that in the same way as trying to run an ELF file |
| 1624 | // from a foreign CPU architecture. |
| 1625 | if (Io.File.Permissions.has_executable_bit and target.os.tag == .wasi and |
| 1626 | comp.config.output_mode == .Exe) |
| 1627 | { |
| 1628 | // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. |
| 1629 | Io.Dir.cwd().setFilePermissions(io, full_out_path, .fromMode(0o744), .{}) catch |err| |
| 1630 | return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err }); |
| 1631 | } |
| 1632 | } |
| 1633 | } |
| 1634 | |
| 1635 | fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void { |
| 1636 | const io = comp.io; |
| 1637 | const gpa = comp.gpa; |
| 1638 | |
| 1639 | if (comp.verbose_link) { |
| 1640 | // Skip over our own name so that the LLD linker name is the first argv item. |
| 1641 | try Compilation.dumpArgv(io, argv[1..]); |
| 1642 | } |
| 1643 | |
| 1644 | // If possible, we run LLD as a child process because it does not always |
| 1645 | // behave properly as a library, unfortunately. |
| 1646 | // https://github.com/ziglang/zig/issues/3825 |
| 1647 | if (!std.process.can_spawn) { |
| 1648 | const exit_code = try lldMain(arena, argv, false); |
| 1649 | if (exit_code == 0) return; |
| 1650 | if (comp.clang_passthrough_mode) std.process.exit(exit_code); |
| 1651 | return error.AlreadyReported; |
| 1652 | } |
| 1653 | |
| 1654 | var stderr: []u8 = &.{}; |
| 1655 | defer gpa.free(stderr); |
| 1656 | |
| 1657 | // TODO rework this awkward logic to call child.kill() in the failure case |
| 1658 | const term = (if (comp.clang_passthrough_mode) term: { |
| 1659 | var child = std.process.spawn(io, .{ |
| 1660 | .argv = argv, |
| 1661 | .stdin = .inherit, |
| 1662 | .stdout = .inherit, |
| 1663 | .stderr = .inherit, |
| 1664 | }) catch |err| break :term err; |
| 1665 | |
| 1666 | break :term child.wait(io); |
| 1667 | } else term: { |
| 1668 | var child = std.process.spawn(io, .{ |
| 1669 | .argv = argv, |
| 1670 | .stdin = .ignore, |
| 1671 | .stdout = .ignore, |
| 1672 | .stderr = .pipe, |
| 1673 | }) catch |err| break :term err; |
| 1674 | |
| 1675 | var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); |
| 1676 | stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) { |
| 1677 | error.StreamTooLong => unreachable, // unlimited |
| 1678 | error.OutOfMemory => |e| return e, |
| 1679 | error.ReadFailed => return stderr_reader.err.?, |
| 1680 | }; |
| 1681 | break :term child.wait(io); |
| 1682 | }) catch |first_err| term: { |
| 1683 | const err = switch (first_err) { |
| 1684 | error.NameTooLong => err: { |
| 1685 | const s = fs.path.sep_str; |
| 1686 | const rand_int = r: { |
| 1687 | var x: u64 = undefined; |
| 1688 | io.random(@ptrCast(&x)); |
| 1689 | break :r x; |
| 1690 | }; |
| 1691 | const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; |
| 1692 | |
| 1693 | const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{}); |
| 1694 | defer comp.dirs.local_cache.handle.deleteFile(io, rsp_path) catch |err| |
| 1695 | log.warn("failed to delete response file {s}: {t}", .{ rsp_path, err }); |
| 1696 | { |
| 1697 | defer rsp_file.close(io); |
| 1698 | var rsp_file_buffer: [1024]u8 = undefined; |
| 1699 | var rsp_file_writer = rsp_file.writer(io, &rsp_file_buffer); |
| 1700 | const rsp_writer = &rsp_file_writer.interface; |
| 1701 | for (argv[2..]) |arg| { |
| 1702 | try rsp_writer.writeByte('"'); |
| 1703 | for (arg) |c| { |
| 1704 | switch (c) { |
| 1705 | '\"', '\\' => try rsp_writer.writeByte('\\'), |
| 1706 | else => {}, |
| 1707 | } |
| 1708 | try rsp_writer.writeByte(c); |
| 1709 | } |
| 1710 | try rsp_writer.writeByte('"'); |
| 1711 | try rsp_writer.writeByte('\n'); |
| 1712 | } |
| 1713 | try rsp_writer.flush(); |
| 1714 | } |
| 1715 | |
| 1716 | var rsp_child = std.process.spawn(io, .{ |
| 1717 | .argv = &.{ |
| 1718 | argv[0], |
| 1719 | argv[1], |
| 1720 | try arena.print("@{s}", .{ |
| 1721 | try comp.dirs.local_cache.join(arena, &.{rsp_path}), |
| 1722 | }), |
| 1723 | }, |
| 1724 | .stdin = if (comp.clang_passthrough_mode) .inherit else .ignore, |
| 1725 | .stdout = if (comp.clang_passthrough_mode) .inherit else .ignore, |
| 1726 | .stderr = if (comp.clang_passthrough_mode) .inherit else .pipe, |
| 1727 | }) catch |err| break :err err; |
| 1728 | if (comp.clang_passthrough_mode) { |
| 1729 | break :term rsp_child.wait(io) catch |err| break :err err; |
| 1730 | } else { |
| 1731 | var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); |
| 1732 | stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) { |
| 1733 | error.StreamTooLong => unreachable, // unlimited |
| 1734 | error.OutOfMemory => |e| return e, |
| 1735 | error.ReadFailed => return stderr_reader.err.?, |
| 1736 | }; |
| 1737 | break :term rsp_child.wait(io) catch |err| break :err err; |
| 1738 | } |
| 1739 | }, |
| 1740 | else => first_err, |
| 1741 | }; |
| 1742 | log.err("unable to spawn LLD {s}: {t}", .{ argv[0], err }); |
| 1743 | return error.UnableToSpawnSelf; |
| 1744 | }; |
| 1745 | |
| 1746 | const diags = &comp.link_diags; |
| 1747 | switch (term) { |
| 1748 | .exited => |code| if (code != 0) { |
| 1749 | if (comp.clang_passthrough_mode) std.process.exit(code); |
| 1750 | diags.lockAndParseLldStderr(argv[1], stderr); |
| 1751 | return error.AlreadyReported; |
| 1752 | }, |
| 1753 | .signal => |sig| { |
| 1754 | if (comp.clang_passthrough_mode) std.process.abort(); |
| 1755 | return diags.fail("{s} terminated with signal {t} and stderr:\n{s}", .{ argv[0], sig, stderr }); |
| 1756 | }, |
| 1757 | .stopped => |sig| { |
| 1758 | if (comp.clang_passthrough_mode) std.process.abort(); |
| 1759 | return diags.fail("{s} stopped with signal {t} and stderr:\n{s}", .{ argv[0], sig, stderr }); |
| 1760 | }, |
| 1761 | .unknown => |code| { |
| 1762 | if (comp.clang_passthrough_mode) std.process.abort(); |
| 1763 | return diags.fail("{s} terminated for unknown reason with code {d} and stderr:\n{s}", .{ argv[0], code, stderr }); |
| 1764 | }, |
| 1765 | } |
| 1766 | |
| 1767 | if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr}); |
| 1768 | } |
| 1769 | |
| 1770 | const builtin = @import("builtin"); |
| 1771 | const std = @import("std"); |
| 1772 | const Io = std.Io; |
| 1773 | const Allocator = std.mem.Allocator; |
| 1774 | const Cache = std.Build.Cache; |
| 1775 | const assert = std.debug.assert; |
| 1776 | const fs = std.fs; |
| 1777 | const log = std.log.scoped(.link); |
| 1778 | const mem = std.mem; |
| 1779 | |
| 1780 | const Compilation = @import("../Compilation.zig"); |
| 1781 | const Zcu = @import("../Zcu.zig"); |
| 1782 | const dev = @import("../dev.zig"); |
| 1783 | const freebsd = @import("../libs/freebsd.zig"); |
| 1784 | const glibc = @import("../libs/glibc.zig"); |
| 1785 | const netbsd = @import("../libs/netbsd.zig"); |
| 1786 | const openbsd = @import("../libs/openbsd.zig"); |
| 1787 | const wasi_libc = @import("../libs/wasi_libc.zig"); |
| 1788 | const link = @import("../link.zig"); |
| 1789 | const lldMain = @import("../main.zig").lldMain; |
| 1790 | const target_util = @import("../target.zig"); |
| 1791 | const trace = @import("../tracy.zig").trace; |
| 1792 | const Lld = @This(); |