cli+build: handle -ObjC flag and route it to MachO linker

This commit is contained in:
Jakub Konka 2024-02-02 14:05:51 +01:00
parent 9eda6ccefc
commit 92deebcd66
11 changed files with 91 additions and 31 deletions

View file

@ -149,6 +149,9 @@ headerpad_max_install_names: bool = false,
/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. /// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
dead_strip_dylibs: bool = false, dead_strip_dylibs: bool = false,
/// (Darwin) Force load all members of static archives that implement an Objective-C class or category
force_load_objc: bool = false,
/// Position Independent Executable /// Position Independent Executable
pie: ?bool = null, pie: ?bool = null,
@ -1433,6 +1436,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
if (self.dead_strip_dylibs) { if (self.dead_strip_dylibs) {
try zig_args.append("-dead_strip_dylibs"); try zig_args.append("-dead_strip_dylibs");
} }
if (self.force_load_objc) {
try zig_args.append("-ObjC");
}
try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt);
try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns);

View file

@ -1113,6 +1113,8 @@ pub const CreateOptions = struct {
headerpad_max_install_names: bool = false, headerpad_max_install_names: bool = false,
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
dead_strip_dylibs: bool = false, dead_strip_dylibs: bool = false,
/// (Darwin) Force load all members of static archives that implement an Objective-C class or category
force_load_objc: bool = false,
libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default, libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default,
/// (Windows) PDB source path prefix to instruct the linker how to resolve relative /// (Windows) PDB source path prefix to instruct the linker how to resolve relative
/// paths when consolidating CodeView streams into a single PDB file. /// paths when consolidating CodeView streams into a single PDB file.
@ -1591,6 +1593,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.headerpad_size = options.headerpad_size, .headerpad_size = options.headerpad_size,
.headerpad_max_install_names = options.headerpad_max_install_names, .headerpad_max_install_names = options.headerpad_max_install_names,
.dead_strip_dylibs = options.dead_strip_dylibs, .dead_strip_dylibs = options.dead_strip_dylibs,
.force_load_objc = options.force_load_objc,
.pdb_source_path = options.pdb_source_path, .pdb_source_path = options.pdb_source_path,
.pdb_out_path = options.pdb_out_path, .pdb_out_path = options.pdb_out_path,
.entry_addr = null, // CLI does not expose this option (yet?) .entry_addr = null, // CLI does not expose this option (yet?)
@ -2456,7 +2459,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
/// to remind the programmer to update multiple related pieces of code that /// to remind the programmer to update multiple related pieces of code that
/// are in different locations. Bump this number when adding or deleting /// are in different locations. Bump this number when adding or deleting
/// anything from the link cache manifest. /// anything from the link cache manifest.
pub const link_hash_implementation_version = 11; pub const link_hash_implementation_version = 12;
fn addNonIncrementalStuffToCacheManifest( fn addNonIncrementalStuffToCacheManifest(
comp: *Compilation, comp: *Compilation,
@ -2465,7 +2468,7 @@ fn addNonIncrementalStuffToCacheManifest(
) !void { ) !void {
const gpa = comp.gpa; const gpa = comp.gpa;
comptime assert(link_hash_implementation_version == 11); comptime assert(link_hash_implementation_version == 12);
if (comp.module) |mod| { if (comp.module) |mod| {
try addModuleTableToCacheHash(gpa, arena, &man.hash, mod.root_mod, mod.main_mod, .{ .files = man }); try addModuleTableToCacheHash(gpa, arena, &man.hash, mod.root_mod, mod.main_mod, .{ .files = man });
@ -2589,6 +2592,7 @@ fn addNonIncrementalStuffToCacheManifest(
man.hash.addOptional(opts.headerpad_size); man.hash.addOptional(opts.headerpad_size);
man.hash.add(opts.headerpad_max_install_names); man.hash.add(opts.headerpad_max_install_names);
man.hash.add(opts.dead_strip_dylibs); man.hash.add(opts.dead_strip_dylibs);
man.hash.add(opts.force_load_objc);
// COFF specific stuff // COFF specific stuff
man.hash.addOptional(opts.subsystem); man.hash.addOptional(opts.subsystem);

View file

@ -115,7 +115,14 @@ flagpd1("Mach"),
.pd2 = false, .pd2 = false,
.psl = false, .psl = false,
}, },
flagpd1("ObjC"), .{
.name = "ObjC",
.syntax = .flag,
.zig_equivalent = .force_load_objc,
.pd1 = true,
.pd2 = false,
.psl = false,
},
flagpd1("ObjC++"), flagpd1("ObjC++"),
flagpd1("P"), flagpd1("P"),
flagpd1("Q"), flagpd1("Q"),

View file

@ -136,30 +136,36 @@ pub const File = struct {
framework_dirs: []const []const u8, framework_dirs: []const []const u8,
rpath_list: []const []const u8, rpath_list: []const []const u8,
/// (Zig compiler development) Enable dumping of linker's state as JSON. /// Zig compiler development linker flags.
/// Enable dumping of linker's state as JSON.
enable_link_snapshots: bool, enable_link_snapshots: bool,
/// (Darwin) Install name for the dylib /// Darwin-specific linker flags:
/// Install name for the dylib
install_name: ?[]const u8, install_name: ?[]const u8,
/// (Darwin) Path to entitlements file /// Path to entitlements file
entitlements: ?[]const u8, entitlements: ?[]const u8,
/// (Darwin) size of the __PAGEZERO segment /// size of the __PAGEZERO segment
pagezero_size: ?u64, pagezero_size: ?u64,
/// (Darwin) set minimum space for future expansion of the load commands /// Set minimum space for future expansion of the load commands
headerpad_size: ?u32, headerpad_size: ?u32,
/// (Darwin) set enough space as if all paths were MATPATHLEN /// Set enough space as if all paths were MATPATHLEN
headerpad_max_install_names: bool, headerpad_max_install_names: bool,
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols /// Remove dylibs that are unreachable by the entry point or exported symbols
dead_strip_dylibs: bool, dead_strip_dylibs: bool,
frameworks: []const MachO.Framework, frameworks: []const MachO.Framework,
darwin_sdk_layout: ?MachO.SdkLayout, darwin_sdk_layout: ?MachO.SdkLayout,
/// Force load all members of static archives that implement an
/// Objective-C class or category
force_load_objc: bool,
/// (Windows) PDB source path prefix to instruct the linker how to resolve relative /// Windows-specific linker flags:
/// PDB source path prefix to instruct the linker how to resolve relative
/// paths when consolidating CodeView streams into a single PDB file. /// paths when consolidating CodeView streams into a single PDB file.
pdb_source_path: ?[]const u8, pdb_source_path: ?[]const u8,
/// (Windows) PDB output path /// PDB output path
pdb_out_path: ?[]const u8, pdb_out_path: ?[]const u8,
/// (Windows) .def file to specify when linking /// .def file to specify when linking
module_definition_file: ?[]const u8, module_definition_file: ?[]const u8,
pub const Entry = union(enum) { pub const Entry = union(enum) {

View file

@ -70,7 +70,7 @@ pub fn linkWithLLD(self: *Coff, arena: Allocator, prog_node: *std.Progress.Node)
man = comp.cache_parent.obtain(); man = comp.cache_parent.obtain();
self.base.releaseLock(); self.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 11); comptime assert(Compilation.link_hash_implementation_version == 12);
for (comp.objects) |obj| { for (comp.objects) |obj| {
_ = try man.addFile(obj.path, null); _ = try man.addFile(obj.path, null);

View file

@ -2380,7 +2380,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node) !voi
// We are about to obtain this lock, so here we give other processes a chance first. // We are about to obtain this lock, so here we give other processes a chance first.
self.base.releaseLock(); self.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 11); comptime assert(Compilation.link_hash_implementation_version == 12);
try man.addOptionalFile(self.linker_script); try man.addOptionalFile(self.linker_script);
try man.addOptionalFile(self.version_script); try man.addOptionalFile(self.version_script);

View file

@ -217,6 +217,7 @@ pub fn createEmpty(
.undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error", .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
.lib_dirs = options.lib_dirs, .lib_dirs = options.lib_dirs,
.framework_dirs = options.framework_dirs, .framework_dirs = options.framework_dirs,
.force_load_objc = options.force_load_objc,
}; };
if (use_llvm and comp.config.have_zcu) { if (use_llvm and comp.config.have_zcu) {
self.llvm_object = try LlvmObject.create(arena, comp); self.llvm_object = try LlvmObject.create(arena, comp);
@ -807,6 +808,10 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append("-dead_strip_dylibs"); try argv.append("-dead_strip_dylibs");
} }
if (self.force_load_objc) {
try argv.append("-ObjC");
}
if (self.entry_name) |entry_name| { if (self.entry_name) |entry_name| {
try argv.appendSlice(&.{ "-e", entry_name }); try argv.appendSlice(&.{ "-e", entry_name });
} }
@ -1247,7 +1252,7 @@ fn parseDependentDylibs(self: *MachO) !void {
try checked_paths.append(rel_path); try checked_paths.append(rel_path);
var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const full_path = std.fs.realpath(rel_path, &buffer) catch continue; const full_path = std.fs.realpath(rel_path, &buffer) catch continue;
break :full_path full_path; break :full_path try arena.dupe(u8, full_path);
} }
} else if (eatPrefix(id.name, "@loader_path/")) |_| { } else if (eatPrefix(id.name, "@loader_path/")) |_| {
try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name}); try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
@ -1260,7 +1265,7 @@ fn parseDependentDylibs(self: *MachO) !void {
try checked_paths.append(try arena.dupe(u8, id.name)); try checked_paths.append(try arena.dupe(u8, id.name));
var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
if (std.fs.realpath(id.name, &buffer)) |full_path| { if (std.fs.realpath(id.name, &buffer)) |full_path| {
break :full_path full_path; break :full_path try arena.dupe(u8, full_path);
} else |_| { } else |_| {
try self.reportMissingDependencyError( try self.reportMissingDependencyError(
self.getFile(dylib_index).?.dylib.getUmbrella(self).index, self.getFile(dylib_index).?.dylib.getUmbrella(self).index,

View file

@ -3511,7 +3511,7 @@ fn linkWithZld(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) lin
// We are about to obtain this lock, so here we give other processes a chance first. // We are about to obtain this lock, so here we give other processes a chance first.
wasm.base.releaseLock(); wasm.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 11); comptime assert(Compilation.link_hash_implementation_version == 12);
for (objects) |obj| { for (objects) |obj| {
_ = try man.addFile(obj.path, null); _ = try man.addFile(obj.path, null);
@ -4580,7 +4580,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo
// We are about to obtain this lock, so here we give other processes a chance first. // We are about to obtain this lock, so here we give other processes a chance first.
wasm.base.releaseLock(); wasm.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 11); comptime assert(Compilation.link_hash_implementation_version == 12);
for (comp.objects) |obj| { for (comp.objects) |obj| {
_ = try man.addFile(obj.path, null); _ = try man.addFile(obj.path, null);

View file

@ -559,6 +559,7 @@ const usage_build_generic =
\\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN \\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN
\\ -dead_strip (Darwin) remove functions and data that are unreachable by the entry point or exported symbols \\ -dead_strip (Darwin) remove functions and data that are unreachable by the entry point or exported symbols
\\ -dead_strip_dylibs (Darwin) remove dylibs that are unreachable by the entry point or exported symbols \\ -dead_strip_dylibs (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
\\ -ObjC (Darwin) force load all members of static archives that implement an Objective-C class or category
\\ --import-memory (WebAssembly) import memory from the environment \\ --import-memory (WebAssembly) import memory from the environment
\\ --export-memory (WebAssembly) export memory to the host (Default unless --import-memory used) \\ --export-memory (WebAssembly) export memory to the host (Default unless --import-memory used)
\\ --import-symbols (WebAssembly) import missing symbols from the host environment \\ --import-symbols (WebAssembly) import missing symbols from the host environment
@ -589,7 +590,7 @@ const usage_build_generic =
\\ -rpath [path] Add directory to the runtime library search path \\ -rpath [path] Add directory to the runtime library search path
\\ -framework [name] (Darwin) link against framework \\ -framework [name] (Darwin) link against framework
\\ -needed_framework [name] (Darwin) link against framework (even if unused) \\ -needed_framework [name] (Darwin) link against framework (even if unused)
\\ -needed_library [lib] link against system library (even if unused) \\ -needed_library [lib] (Darwin) link against system library (even if unused)
\\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak \\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak
\\ -F[dir] (Darwin) add search path for frameworks \\ -F[dir] (Darwin) add search path for frameworks
\\ --export=[value] (WebAssembly) Force a symbol to be exported \\ --export=[value] (WebAssembly) Force a symbol to be exported
@ -889,6 +890,7 @@ fn buildOutputType(
var headerpad_size: ?u32 = null; var headerpad_size: ?u32 = null;
var headerpad_max_install_names: bool = false; var headerpad_max_install_names: bool = false;
var dead_strip_dylibs: bool = false; var dead_strip_dylibs: bool = false;
var force_load_objc: bool = false;
var contains_res_file: bool = false; var contains_res_file: bool = false;
var reference_trace: ?u32 = null; var reference_trace: ?u32 = null;
var pdb_out_path: ?[]const u8 = null; var pdb_out_path: ?[]const u8 = null;
@ -1182,6 +1184,8 @@ fn buildOutputType(
linker_gc_sections = true; linker_gc_sections = true;
} else if (mem.eql(u8, arg, "-dead_strip_dylibs")) { } else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
dead_strip_dylibs = true; dead_strip_dylibs = true;
} else if (mem.eql(u8, arg, "-ObjC")) {
force_load_objc = true;
} else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) { } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
linker_script = args_iter.nextOrFatal(); linker_script = args_iter.nextOrFatal();
} else if (mem.eql(u8, arg, "-version-script") or mem.eql(u8, arg, "--version-script")) { } else if (mem.eql(u8, arg, "-version-script") or mem.eql(u8, arg, "--version-script")) {
@ -2061,6 +2065,7 @@ fn buildOutputType(
.force_undefined_symbol => { .force_undefined_symbol => {
try force_undefined_symbols.put(arena, it.only_arg, {}); try force_undefined_symbols.put(arena, it.only_arg, {});
}, },
.force_load_objc => force_load_objc = true,
.weak_library => try create_module.system_libs.put(arena, it.only_arg, .{ .weak_library => try create_module.system_libs.put(arena, it.only_arg, .{
.needed = false, .needed = false,
.weak = true, .weak = true,
@ -2167,6 +2172,8 @@ fn buildOutputType(
linker_gc_sections = true; linker_gc_sections = true;
} else if (mem.eql(u8, arg, "-dead_strip_dylibs")) { } else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
dead_strip_dylibs = true; dead_strip_dylibs = true;
} else if (mem.eql(u8, arg, "-ObjC")) {
force_load_objc = true;
} else if (mem.eql(u8, arg, "--no-undefined")) { } else if (mem.eql(u8, arg, "--no-undefined")) {
linker_z_defs = true; linker_z_defs = true;
} else if (mem.eql(u8, arg, "--gc-sections")) { } else if (mem.eql(u8, arg, "--gc-sections")) {
@ -3246,6 +3253,7 @@ fn buildOutputType(
.headerpad_size = headerpad_size, .headerpad_size = headerpad_size,
.headerpad_max_install_names = headerpad_max_install_names, .headerpad_max_install_names = headerpad_max_install_names,
.dead_strip_dylibs = dead_strip_dylibs, .dead_strip_dylibs = dead_strip_dylibs,
.force_load_objc = force_load_objc,
.reference_trace = reference_trace, .reference_trace = reference_trace,
.pdb_out_path = pdb_out_path, .pdb_out_path = pdb_out_path,
.error_limit = error_limit, .error_limit = error_limit,
@ -6266,6 +6274,7 @@ pub const ClangArgIterator = struct {
compress_debug_sections, compress_debug_sections,
install_name, install_name,
undefined, undefined,
force_load_objc,
}; };
const Args = struct { const Args = struct {

View file

@ -969,19 +969,38 @@ fn testObjc(b: *Build, opts: Options) *Step {
\\@end \\@end
}); });
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" }); {
exe.root_module.linkSystemLibrary("a", .{}); const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkFramework("Foundation", .{}); exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory()); exe.root_module.linkFramework("Foundation", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
const check = exe.checkObject(); const check = exe.checkObject();
check.checkInSymtab(); check.checkInSymtab();
check.checkContains("_OBJC_"); check.checkNotPresent("_OBJC_");
test_step.dependOn(&check.step); test_step.dependOn(&check.step);
const run = addRunArtifact(exe); const run = addRunArtifact(exe);
run.expectExitCode(0); run.expectExitCode(0);
test_step.dependOn(&run.step); test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.force_load_objc = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_OBJC_");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step; return test_step;
} }

View file

@ -528,6 +528,10 @@ const known_options = [_]KnownOpt{
.name = "x", .name = "x",
.ident = "x", .ident = "x",
}, },
.{
.name = "ObjC",
.ident = "force_load_objc",
},
}; };
const blacklisted_options = [_][]const u8{}; const blacklisted_options = [_][]const u8{};