authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-02-16 06:29:08+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-02-22 06:35:19+01:00
logf87b443af1f654d3363ce6a1081bdf7932ae354c
treef4b242f4e9463026e1b53cab879fd21acbbb2e9a
parenta7467b9bb2aa667a8d34bc8b678ce35fcb19ebd4

link.MachO: Add support for the -x flag (discard local symbols).

This can also be extended to ELF later as it means roughly the same thing there. This addresses the main issue in #21721 but as I don't have a macOS machine to do further testing on, I can't confirm whether zig cc is able to pass the entire cgo test suite after this commit. It can, however, cross-compile a basic program that uses cgo to x86_64-macos-none which previously failed due to lack of -x support. Unlike previously, the resulting symbol table does not contain local symbols (such as C static functions). I believe this satisfies the related donor bounty: https://ziglang.org/news/second-donor-bounty

10 files changed, 83 insertions(+), 3 deletions(-)

lib/std/Build/Step/Compile.zig+6
...@@ -160,6 +160,9 @@ dead_strip_dylibs: bool = false,...@@ -160,6 +160,9 @@ dead_strip_dylibs: bool = false,
160/// (Darwin) Force load all members of static archives that implement an Objective-C class or category160/// (Darwin) Force load all members of static archives that implement an Objective-C class or category
161force_load_objc: bool = false,161force_load_objc: bool = false,
162162
163/// Whether local symbols should be discarded from the symbol table.
164discard_local_symbols: bool = false,
165
163/// Position Independent Executable166/// Position Independent Executable
164pie: ?bool = null,167pie: ?bool = null,
165168
...@@ -1555,6 +1558,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1555,6 +1558,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1555 if (compile.force_load_objc) {1558 if (compile.force_load_objc) {
1556 try zig_args.append("-ObjC");1559 try zig_args.append("-ObjC");
1557 }1560 }
1561 if (compile.discard_local_symbols) {
1562 try zig_args.append("--discard-all");
1563 }
15581564
1559 try addFlag(&zig_args, "compiler-rt", compile.bundle_compiler_rt);1565 try addFlag(&zig_args, "compiler-rt", compile.bundle_compiler_rt);
1560 try addFlag(&zig_args, "dll-export-fns", compile.dll_export_fns);1566 try addFlag(&zig_args, "dll-export-fns", compile.dll_export_fns);
src/Compilation.zig+4
...@@ -1160,6 +1160,8 @@ pub const CreateOptions = struct {...@@ -1160,6 +1160,8 @@ pub const CreateOptions = struct {
1160 dead_strip_dylibs: bool = false,1160 dead_strip_dylibs: bool = false,
1161 /// (Darwin) Force load all members of static archives that implement an Objective-C class or category1161 /// (Darwin) Force load all members of static archives that implement an Objective-C class or category
1162 force_load_objc: bool = false,1162 force_load_objc: bool = false,
1163 /// Whether local symbols should be discarded from the symbol table.
1164 discard_local_symbols: bool = false,
1163 libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default,1165 libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default,
1164 /// (Windows) PDB source path prefix to instruct the linker how to resolve relative1166 /// (Windows) PDB source path prefix to instruct the linker how to resolve relative
1165 /// paths when consolidating CodeView streams into a single PDB file.1167 /// paths when consolidating CodeView streams into a single PDB file.
...@@ -1585,6 +1587,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1585,6 +1587,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1585 .headerpad_max_install_names = options.headerpad_max_install_names,1587 .headerpad_max_install_names = options.headerpad_max_install_names,
1586 .dead_strip_dylibs = options.dead_strip_dylibs,1588 .dead_strip_dylibs = options.dead_strip_dylibs,
1587 .force_load_objc = options.force_load_objc,1589 .force_load_objc = options.force_load_objc,
1590 .discard_local_symbols = options.discard_local_symbols,
1588 .pdb_source_path = options.pdb_source_path,1591 .pdb_source_path = options.pdb_source_path,
1589 .pdb_out_path = options.pdb_out_path,1592 .pdb_out_path = options.pdb_out_path,
1590 .entry_addr = null, // CLI does not expose this option (yet?)1593 .entry_addr = null, // CLI does not expose this option (yet?)
...@@ -2665,6 +2668,7 @@ fn addNonIncrementalStuffToCacheManifest(...@@ -2665,6 +2668,7 @@ fn addNonIncrementalStuffToCacheManifest(
2665 man.hash.add(opts.headerpad_max_install_names);2668 man.hash.add(opts.headerpad_max_install_names);
2666 man.hash.add(opts.dead_strip_dylibs);2669 man.hash.add(opts.dead_strip_dylibs);
2667 man.hash.add(opts.force_load_objc);2670 man.hash.add(opts.force_load_objc);
2671 man.hash.add(opts.discard_local_symbols);
26682672
2669 // COFF specific stuff2673 // COFF specific stuff
2670 man.hash.addOptional(opts.subsystem);2674 man.hash.addOptional(opts.subsystem);
src/link.zig+2
...@@ -491,6 +491,8 @@ pub const File = struct {...@@ -491,6 +491,8 @@ pub const File = struct {
491 /// Force load all members of static archives that implement an491 /// Force load all members of static archives that implement an
492 /// Objective-C class or category492 /// Objective-C class or category
493 force_load_objc: bool,493 force_load_objc: bool,
494 /// Whether local symbols should be discarded from the symbol table.
495 discard_local_symbols: bool,
494496
495 /// Windows-specific linker flags:497 /// Windows-specific linker flags:
496 /// PDB source path prefix to instruct the linker how to resolve relative498 /// PDB source path prefix to instruct the linker how to resolve relative
src/link/MachO.zig+7
...@@ -139,6 +139,8 @@ no_implicit_dylibs: bool = false,...@@ -139,6 +139,8 @@ no_implicit_dylibs: bool = false,
139/// Whether the linker should parse and always force load objects containing ObjC in archives.139/// Whether the linker should parse and always force load objects containing ObjC in archives.
140// TODO: in Zig we currently take -ObjC as always on140// TODO: in Zig we currently take -ObjC as always on
141force_load_objc: bool = true,141force_load_objc: bool = true,
142/// Whether local symbols should be discarded from the symbol table.
143discard_local_symbols: bool = false,
142144
143/// Hot-code swapping state.145/// Hot-code swapping state.
144hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},146hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
...@@ -221,6 +223,7 @@ pub fn createEmpty(...@@ -221,6 +223,7 @@ pub fn createEmpty(
221 .lib_directories = options.lib_directories,223 .lib_directories = options.lib_directories,
222 .framework_dirs = options.framework_dirs,224 .framework_dirs = options.framework_dirs,
223 .force_load_objc = options.force_load_objc,225 .force_load_objc = options.force_load_objc,
226 .discard_local_symbols = options.discard_local_symbols,
224 };227 };
225 if (use_llvm and comp.config.have_zcu) {228 if (use_llvm and comp.config.have_zcu) {
226 self.llvm_object = try LlvmObject.create(arena, comp);229 self.llvm_object = try LlvmObject.create(arena, comp);
...@@ -720,6 +723,10 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {...@@ -720,6 +723,10 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
720 try argv.append("-ObjC");723 try argv.append("-ObjC");
721 }724 }
722725
726 if (self.discard_local_symbols) {
727 try argv.append("-x");
728 }
729
723 if (self.entry_name) |entry_name| {730 if (self.entry_name) |entry_name| {
724 try argv.appendSlice(&.{ "-e", entry_name });731 try argv.appendSlice(&.{ "-e", entry_name });
725 }732 }
src/link/MachO/InternalObject.zig+1
...@@ -583,6 +583,7 @@ pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) void {...@@ -583,6 +583,7 @@ pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) void {
583 const file = ref.getFile(macho_file) orelse continue;583 const file = ref.getFile(macho_file) orelse continue;
584 if (file.getIndex() != self.index) continue;584 if (file.getIndex() != self.index) continue;
585 if (sym.getName(macho_file).len == 0) continue;585 if (sym.getName(macho_file).len == 0) continue;
586 if (macho_file.discard_local_symbols and sym.isLocal()) continue;
586 sym.flags.output_symtab = true;587 sym.flags.output_symtab = true;
587 if (sym.isLocal()) {588 if (sym.isLocal()) {
588 sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);589 sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
src/link/MachO/Object.zig+1
...@@ -1722,6 +1722,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void {...@@ -1722,6 +1722,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void {
1722 if (file.getIndex() != self.index) continue;1722 if (file.getIndex() != self.index) continue;
1723 if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue;1723 if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue;
1724 if (sym.isSymbolStab(macho_file)) continue;1724 if (sym.isSymbolStab(macho_file)) continue;
1725 if (macho_file.discard_local_symbols and sym.isLocal()) continue;
1725 const name = sym.getName(macho_file);1726 const name = sym.getName(macho_file);
1726 if (name.len == 0) continue;1727 if (name.len == 0) continue;
1727 // TODO in -r mode, we actually want to merge symbol names and emit only one1728 // TODO in -r mode, we actually want to merge symbol names and emit only one
src/link/MachO/ZigObject.zig+1
...@@ -500,6 +500,7 @@ pub fn calcSymtabSize(self: *ZigObject, macho_file: *MachO) void {...@@ -500,6 +500,7 @@ pub fn calcSymtabSize(self: *ZigObject, macho_file: *MachO) void {
500 const file = ref.getFile(macho_file) orelse continue;500 const file = ref.getFile(macho_file) orelse continue;
501 if (file.getIndex() != self.index) continue;501 if (file.getIndex() != self.index) continue;
502 if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue;502 if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue;
503 if (macho_file.discard_local_symbols and sym.isLocal()) continue;
503 const name = sym.getName(macho_file);504 const name = sym.getName(macho_file);
504 assert(name.len > 0);505 assert(name.len > 0);
505 sym.flags.output_symtab = true;506 sym.flags.output_symtab = true;
src/link/MachO/synthetic.zig+9-3
...@@ -532,17 +532,23 @@ pub const Indsymtab = struct {...@@ -532,17 +532,23 @@ pub const Indsymtab = struct {
532532
533 for (macho_file.stubs.symbols.items) |ref| {533 for (macho_file.stubs.symbols.items) |ref| {
534 const sym = ref.getSymbol(macho_file).?;534 const sym = ref.getSymbol(macho_file).?;
535 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);535 if (sym.getOutputSymtabIndex(macho_file)) |idx| {
536 try writer.writeInt(u32, idx, .little);
537 }
536 }538 }
537539
538 for (macho_file.got.symbols.items) |ref| {540 for (macho_file.got.symbols.items) |ref| {
539 const sym = ref.getSymbol(macho_file).?;541 const sym = ref.getSymbol(macho_file).?;
540 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);542 if (sym.getOutputSymtabIndex(macho_file)) |idx| {
543 try writer.writeInt(u32, idx, .little);
544 }
541 }545 }
542546
543 for (macho_file.stubs.symbols.items) |ref| {547 for (macho_file.stubs.symbols.items) |ref| {
544 const sym = ref.getSymbol(macho_file).?;548 const sym = ref.getSymbol(macho_file).?;
545 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);549 if (sym.getOutputSymtabIndex(macho_file)) |idx| {
550 try writer.writeInt(u32, idx, .little);
551 }
546 }552 }
547 }553 }
548};554};
src/main.zig+6
...@@ -918,6 +918,7 @@ fn buildOutputType(...@@ -918,6 +918,7 @@ fn buildOutputType(
918 var headerpad_max_install_names: bool = false;918 var headerpad_max_install_names: bool = false;
919 var dead_strip_dylibs: bool = false;919 var dead_strip_dylibs: bool = false;
920 var force_load_objc: bool = false;920 var force_load_objc: bool = false;
921 var discard_local_symbols: bool = false;
921 var contains_res_file: bool = false;922 var contains_res_file: bool = false;
922 var reference_trace: ?u32 = null;923 var reference_trace: ?u32 = null;
923 var pdb_out_path: ?[]const u8 = null;924 var pdb_out_path: ?[]const u8 = null;
...@@ -1151,6 +1152,8 @@ fn buildOutputType(...@@ -1151,6 +1152,8 @@ fn buildOutputType(
1151 entry = .{ .named = arg["-fentry=".len..] };1152 entry = .{ .named = arg["-fentry=".len..] };
1152 } else if (mem.eql(u8, arg, "--force_undefined")) {1153 } else if (mem.eql(u8, arg, "--force_undefined")) {
1153 try force_undefined_symbols.put(arena, args_iter.nextOrFatal(), {});1154 try force_undefined_symbols.put(arena, args_iter.nextOrFatal(), {});
1155 } else if (mem.eql(u8, arg, "--discard-all")) {
1156 discard_local_symbols = true;
1154 } else if (mem.eql(u8, arg, "--stack")) {1157 } else if (mem.eql(u8, arg, "--stack")) {
1155 stack_size = parseStackSize(args_iter.nextOrFatal());1158 stack_size = parseStackSize(args_iter.nextOrFatal());
1156 } else if (mem.eql(u8, arg, "--image-base")) {1159 } else if (mem.eql(u8, arg, "--image-base")) {
...@@ -2510,6 +2513,8 @@ fn buildOutputType(...@@ -2510,6 +2513,8 @@ fn buildOutputType(
2510 entry = .{ .named = linker_args_it.nextOrFatal() };2513 entry = .{ .named = linker_args_it.nextOrFatal() };
2511 } else if (mem.eql(u8, arg, "-u")) {2514 } else if (mem.eql(u8, arg, "-u")) {
2512 try force_undefined_symbols.put(arena, linker_args_it.nextOrFatal(), {});2515 try force_undefined_symbols.put(arena, linker_args_it.nextOrFatal(), {});
2516 } else if (mem.eql(u8, arg, "-x") or mem.eql(u8, arg, "--discard-all")) {
2517 discard_local_symbols = true;
2513 } else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) {2518 } else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) {
2514 stack_size = parseStackSize(linker_args_it.nextOrFatal());2519 stack_size = parseStackSize(linker_args_it.nextOrFatal());
2515 } else if (mem.eql(u8, arg, "--image-base")) {2520 } else if (mem.eql(u8, arg, "--image-base")) {
...@@ -3579,6 +3584,7 @@ fn buildOutputType(...@@ -3579,6 +3584,7 @@ fn buildOutputType(
3579 .headerpad_max_install_names = headerpad_max_install_names,3584 .headerpad_max_install_names = headerpad_max_install_names,
3580 .dead_strip_dylibs = dead_strip_dylibs,3585 .dead_strip_dylibs = dead_strip_dylibs,
3581 .force_load_objc = force_load_objc,3586 .force_load_objc = force_load_objc,
3587 .discard_local_symbols = discard_local_symbols,
3582 .reference_trace = reference_trace,3588 .reference_trace = reference_trace,
3583 .pdb_out_path = pdb_out_path,3589 .pdb_out_path = pdb_out_path,
3584 .error_limit = error_limit,3590 .error_limit = error_limit,
test/link/macho.zig+46
...@@ -62,6 +62,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {...@@ -62,6 +62,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
62 macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));62 macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
63 macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));63 macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));
64 macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));64 macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
65 macho_step.dependOn(testDiscardLocalSymbols(b, .{ .target = default_target }));
65 macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));66 macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));
66 macho_step.dependOn(testUnresolvedError2(b, .{ .target = default_target }));67 macho_step.dependOn(testUnresolvedError2(b, .{ .target = default_target }));
67 macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target }));68 macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target }));
...@@ -2502,6 +2503,51 @@ fn testTwoLevelNamespace(b: *Build, opts: Options) *Step {...@@ -2502,6 +2503,51 @@ fn testTwoLevelNamespace(b: *Build, opts: Options) *Step {
2502 return test_step;2503 return test_step;
2503}2504}
25042505
2506fn testDiscardLocalSymbols(b: *Build, opts: Options) *Step {
2507 const test_step = addTestStep(b, "discard-local-symbols", opts);
2508
2509 const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes = "static int foo = 42;" });
2510
2511 const lib = addStaticLibrary(b, opts, .{ .name = "a" });
2512 lib.addObject(obj);
2513
2514 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
2515
2516 {
2517 const exe = addExecutable(b, opts, .{ .name = "main3" });
2518 exe.addObject(main_o);
2519 exe.addObject(obj);
2520 exe.discard_local_symbols = true;
2521
2522 const run = addRunArtifact(exe);
2523 run.expectExitCode(0);
2524 test_step.dependOn(&run.step);
2525
2526 const check = exe.checkObject();
2527 check.checkInSymtab();
2528 check.checkNotPresent("_foo");
2529 test_step.dependOn(&check.step);
2530 }
2531
2532 {
2533 const exe = addExecutable(b, opts, .{ .name = "main4" });
2534 exe.addObject(main_o);
2535 exe.linkLibrary(lib);
2536 exe.discard_local_symbols = true;
2537
2538 const run = addRunArtifact(exe);
2539 run.expectExitCode(0);
2540 test_step.dependOn(&run.step);
2541
2542 const check = exe.checkObject();
2543 check.checkInSymtab();
2544 check.checkNotPresent("_foo");
2545 test_step.dependOn(&check.step);
2546 }
2547
2548 return test_step;
2549}
2550
2505fn testUndefinedFlag(b: *Build, opts: Options) *Step {2551fn testUndefinedFlag(b: *Build, opts: Options) *Step {
2506 const test_step = addTestStep(b, "undefined-flag", opts);2552 const test_step = addTestStep(b, "undefined-flag", opts);
25072553