diff --git a/src/Compilation.zig b/src/Compilation.zig index 2646da2f6f..525835ce61 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -903,6 +903,8 @@ pub const InitOptions = struct { install_name: ?[]const u8 = null, /// (Darwin) Path to entitlements file entitlements: ?[]const u8 = null, + /// (Darwin) size of the __PAGEZERO segment + pagezero_size: ?u64 = null, }; fn addPackageTableToCacheHash( @@ -1742,6 +1744,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .native_darwin_sdk = options.native_darwin_sdk, .install_name = options.install_name, .entitlements = options.entitlements, + .pagezero_size = options.pagezero_size, }); errdefer bin_file.destroy(); comp.* = .{ @@ -2359,7 +2362,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo /// to remind the programmer to update multiple related pieces of code that /// are in different locations. Bump this number when adding or deleting /// anything from the link cache manifest. -pub const link_hash_implementation_version = 3; +pub const link_hash_implementation_version = 4; fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void { const gpa = comp.gpa; @@ -2369,7 +2372,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - comptime assert(link_hash_implementation_version == 3); + comptime assert(link_hash_implementation_version == 4); if (comp.bin_file.options.module) |mod| { const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{ @@ -2474,6 +2477,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.addListOfBytes(comp.bin_file.options.framework_dirs); man.hash.addListOfBytes(comp.bin_file.options.frameworks); try man.addOptionalFile(comp.bin_file.options.entitlements); + man.hash.addOptional(comp.bin_file.options.pagezero_size); // COFF specific stuff man.hash.addOptional(comp.bin_file.options.subsystem); diff --git a/src/link.zig b/src/link.zig index 65e9de8ca3..ad1fc417f3 100644 --- a/src/link.zig +++ b/src/link.zig @@ -187,6 +187,9 @@ pub const Options = struct { /// (Darwin) Path to entitlements file entitlements: ?[]const u8 = null, + /// (Darwin) size of the __PAGEZERO segment + pagezero_size: ?u64 = null, + pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode { return if (options.use_lld) .Obj else options.output_mode; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2943cae36a..6b4ff26bce 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) ! man = comp.cache_parent.obtain(); self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 3); + comptime assert(Compilation.link_hash_implementation_version == 4); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index a0e40e5682..0bcde06f33 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 3); + comptime assert(Compilation.link_hash_implementation_version == 4); try man.addOptionalFile(self.base.options.linker_script); try man.addOptionalFile(self.base.options.version_script); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c71007157a..aea44d9357 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -286,9 +286,9 @@ const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; const minimum_text_block_size = 64; pub const min_text_capacity = padToIdeal(minimum_text_block_size); -/// Virtual memory offset corresponds to the size of __PAGEZERO segment and start of -/// __TEXT segment. -const pagezero_vmsize: u64 = 0x100000000; +/// Default virtual memory offset corresponds to the size of __PAGEZERO segment and +/// start of __TEXT segment. +const default_pagezero_vmsize: u64 = 0x100000000; pub const Export = struct { sym_index: ?u32 = null, @@ -536,7 +536,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 3); + comptime assert(Compilation.link_hash_implementation_version == 4); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); @@ -549,6 +549,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No // We can skip hashing libc and libc++ components that we are in charge of building from Zig // installation sources because they are always a product of the compiler version + target information. man.hash.add(stack_size); + man.hash.addOptional(self.base.options.pagezero_size); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.addListOfBytes(self.base.options.framework_dirs); man.hash.addListOfBytes(self.base.options.frameworks); @@ -928,6 +929,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try argv.append(rpath); } + if (self.base.options.pagezero_size) |pagezero_size| { + try argv.append("-pagezero_size"); + try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size})); + } + try argv.appendSlice(positionals.items); try argv.append("-o"); @@ -4365,14 +4371,21 @@ pub fn getDeclVAddr(self: *MachO, decl_index: Module.Decl.Index, reloc_info: Fil fn populateMissingMetadata(self: *MachO) !void { const cpu_arch = self.base.options.target.cpu.arch; + const pagezero_vmsize = self.base.options.pagezero_size orelse default_pagezero_vmsize; + const aligned_pagezero_vmsize = mem.alignBackwardGeneric(u64, pagezero_vmsize, self.page_size); - if (self.pagezero_segment_cmd_index == null) { + if (self.pagezero_segment_cmd_index == null) blk: { + if (aligned_pagezero_vmsize == 0) break :blk; + if (aligned_pagezero_vmsize != pagezero_vmsize) { + log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_vmsize}); + log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize}); + } self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ .segment = .{ .inner = .{ .segname = makeStaticString("__PAGEZERO"), - .vmsize = pagezero_vmsize, + .vmsize = aligned_pagezero_vmsize, .cmdsize = @sizeOf(macho.segment_command_64), }, }, @@ -4394,7 +4407,7 @@ fn populateMissingMetadata(self: *MachO) !void { .segment = .{ .inner = .{ .segname = makeStaticString("__TEXT"), - .vmaddr = pagezero_vmsize, + .vmaddr = aligned_pagezero_vmsize, .vmsize = needed_size, .filesize = needed_size, .maxprot = macho.PROT.READ | macho.PROT.EXEC, @@ -4881,7 +4894,10 @@ fn populateMissingMetadata(self: *MachO) !void { fn allocateTextSegment(self: *MachO) !void { const seg = &self.load_commands.items[self.text_segment_cmd_index.?].segment; - const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].segment.inner.vmsize; + const base_vmaddr = if (self.pagezero_segment_cmd_index) |index| + self.load_commands.items[index].segment.inner.vmsize + else + 0; seg.inner.fileoff = 0; seg.inner.vmaddr = base_vmaddr; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 5a910e188b..cff47ccdf5 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2274,7 +2274,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 3); + comptime assert(Compilation.link_hash_implementation_version == 4); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); diff --git a/src/main.zig b/src/main.zig index cfd802d11c..6942d0ba49 100644 --- a/src/main.zig +++ b/src/main.zig @@ -447,6 +447,7 @@ const usage_build_generic = \\ -F[dir] (Darwin) add search path for frameworks \\ -install_name=[value] (Darwin) add dylib's install name \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature + \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation \\ --import-memory (WebAssembly) import memory from the environment \\ --import-table (WebAssembly) import function table from the host environment \\ --export-table (WebAssembly) export function table to the host environment @@ -694,6 +695,7 @@ fn buildOutputType( var install_name: ?[]const u8 = null; var hash_style: link.HashStyle = .both; var entitlements: ?[]const u8 = null; + var pagezero_size: ?u64 = null; // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. // This array is populated by zig cc frontend and then has to be converted to zig-style @@ -908,6 +910,13 @@ fn buildOutputType( install_name = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); }; + } else if (mem.eql(u8, arg, "-pagezero_size")) { + const next_arg = args_iter.next() orelse { + fatal("expected parameter after {s}", .{arg}); + }; + pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) { linker_script = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); @@ -1647,6 +1656,15 @@ fn buildOutputType( linker_optimization = std.fmt.parseUnsigned(u8, arg["-O".len..], 10) catch |err| { fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; + } else if (mem.eql(u8, arg, "-pagezero_size")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + const next_arg = linker_args.items[i]; + pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; } else if (mem.eql(u8, arg, "--gc-sections")) { linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { @@ -2763,6 +2781,7 @@ fn buildOutputType( .native_darwin_sdk = native_darwin_sdk, .install_name = install_name, .entitlements = entitlements, + .pagezero_size = pagezero_size, }) catch |err| switch (err) { error.LibCUnavailable => { const target = target_info.target; @@ -5048,6 +5067,18 @@ pub fn cmdChangelist( try bw.flush(); } +fn eatIntPrefix(arg: []const u8, radix: u8) []const u8 { + if (arg.len > 2 and arg[0] == '0') { + switch (std.ascii.toLower(arg[1])) { + 'b' => if (radix == 2) return arg[2..], + 'o' => if (radix == 8) return arg[2..], + 'x' => if (radix == 16) return arg[2..], + else => {}, + } + } + return arg; +} + fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 { return std.fmt.parseUnsigned(u64, arg[prefix_len..], 0) catch |err| { fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });