authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-08-02 16:11:55+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-26 18:21:21+01:00
log37ba5b8812a1add16e44f658d2aa7c5551aacd47
tree45d0c2c5d5742fbcf295322cbda77cc22af30a2a
parent4bd05848f28f413b415b29491ba0e2b3fa3c2dda
signature Commit is signed but in an unrecognized format.

MachO2: stub new linker implementation


4 files changed, 295 insertions(+), 10 deletions(-)

src/dev.zig+2
...@@ -112,6 +112,7 @@ pub const Env = enum {...@@ -112,6 +112,7 @@ pub const Env = enum {
112 .elf_linker,112 .elf_linker,
113 .elf2_linker,113 .elf2_linker,
114 .macho_linker,114 .macho_linker,
115 .macho2_linker,
115 .c_linker,116 .c_linker,
116 .wasm_linker,117 .wasm_linker,
117 .spirv_linker,118 .spirv_linker,
...@@ -323,6 +324,7 @@ pub const Feature = enum {...@@ -323,6 +324,7 @@ pub const Feature = enum {
323 elf_linker,324 elf_linker,
324 elf2_linker,325 elf2_linker,
325 macho_linker,326 macho_linker,
327 macho2_linker,
326 c_linker,328 c_linker,
327 wasm_linker,329 wasm_linker,
328 spirv_linker,330 spirv_linker,
src/link.zig+17-9
...@@ -587,7 +587,7 @@ pub const File = struct {...@@ -587,7 +587,7 @@ pub const File = struct {
587 pub fn startProgress(base: *File, prog_node: std.Progress.Node) void {587 pub fn startProgress(base: *File, prog_node: std.Progress.Node) void {
588 switch (base.tag) {588 switch (base.tag) {
589 else => {},589 else => {},
590 inline .elf2, .coff => |tag| {590 inline .elf2, .coff, .macho2 => |tag| {
591 dev.check(tag.devFeature());591 dev.check(tag.devFeature());
592 return @as(*tag.Type(), @fieldParentPtr("base", base)).startProgress(prog_node);592 return @as(*tag.Type(), @fieldParentPtr("base", base)).startProgress(prog_node);
593 },593 },
...@@ -597,7 +597,7 @@ pub const File = struct {...@@ -597,7 +597,7 @@ pub const File = struct {
597 pub fn endProgress(base: *File) void {597 pub fn endProgress(base: *File) void {
598 switch (base.tag) {598 switch (base.tag) {
599 else => {},599 else => {},
600 inline .elf2, .coff => |tag| {600 inline .elf2, .coff, .macho2 => |tag| {
601 dev.check(tag.devFeature());601 dev.check(tag.devFeature());
602 return @as(*tag.Type(), @fieldParentPtr("base", base)).endProgress();602 return @as(*tag.Type(), @fieldParentPtr("base", base)).endProgress();
603 },603 },
...@@ -651,11 +651,13 @@ pub const File = struct {...@@ -651,11 +651,13 @@ pub const File = struct {
651 }651 }
652 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });652 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
653 },653 },
654 .elf2, .coff => if (base.file == null) {654 .elf2, .coff, .macho2 => if (base.file == null) {
655 const mf = if (base.cast(.elf2)) |elf|655 const mf = if (base.cast(.elf2)) |elf|
656 &elf.mf656 &elf.mf
657 else if (base.cast(.coff)) |coff|657 else if (base.cast(.coff)) |coff|
658 &coff.mf658 &coff.mf
659 else if (base.cast(.macho2)) |macho|
660 &macho.mf
659 else661 else
660 unreachable;662 unreachable;
661 mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{663 mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
...@@ -740,11 +742,13 @@ pub const File = struct {...@@ -740,11 +742,13 @@ pub const File = struct {
740 }742 }
741 }743 }
742 },744 },
743 .elf2, .coff => if (base.file) |f| {745 .elf2, .coff, .macho2 => if (base.file) |f| {
744 const mf = if (base.cast(.elf2)) |elf|746 const mf = if (base.cast(.elf2)) |elf|
745 &elf.mf747 &elf.mf
746 else if (base.cast(.coff)) |coff|748 else if (base.cast(.coff)) |coff|
747 &coff.mf749 &coff.mf
750 else if (base.cast(.macho2)) |macho|
751 &macho.mf
748 else752 else
749 unreachable;753 unreachable;
750 mf.unmap();754 mf.unmap();
...@@ -900,6 +904,7 @@ pub const File = struct {...@@ -900,6 +904,7 @@ pub const File = struct {
900 .plan9 => unreachable,904 .plan9 => unreachable,
901 .spirv => {},905 .spirv => {},
902 .coff => {},906 .coff => {},
907 .macho2 => {},
903 inline else => |tag| {908 inline else => |tag| {
904 dev.check(tag.devFeature());909 dev.check(tag.devFeature());
905 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateLineNumber(pt, ti_id, line);910 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateLineNumber(pt, ti_id, line);
...@@ -951,7 +956,7 @@ pub const File = struct {...@@ -951,7 +956,7 @@ pub const File = struct {
951 pub fn idle(base: *File, tid: Zcu.PerThread.Id) Error!bool {956 pub fn idle(base: *File, tid: Zcu.PerThread.Id) Error!bool {
952 switch (base.tag) {957 switch (base.tag) {
953 else => return false,958 else => return false,
954 inline .elf2, .coff => |tag| {959 inline .elf2, .coff, .macho2 => |tag| {
955 dev.check(tag.devFeature());960 dev.check(tag.devFeature());
956 return @as(*tag.Type(), @fieldParentPtr("base", base)).idle(tid);961 return @as(*tag.Type(), @fieldParentPtr("base", base)).idle(tid);
957 },962 },
...@@ -961,7 +966,7 @@ pub const File = struct {...@@ -961,7 +966,7 @@ pub const File = struct {
961 pub fn updateErrorData(base: *File, pt: Zcu.PerThread) Error!void {966 pub fn updateErrorData(base: *File, pt: Zcu.PerThread) Error!void {
962 switch (base.tag) {967 switch (base.tag) {
963 else => {},968 else => {},
964 inline .elf2, .coff => |tag| {969 inline .elf2, .coff, .macho2 => |tag| {
965 dev.check(tag.devFeature());970 dev.check(tag.devFeature());
966 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateErrorData(pt);971 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateErrorData(pt);
967 },972 },
...@@ -1234,7 +1239,7 @@ pub const File = struct {...@@ -1234,7 +1239,7 @@ pub const File = struct {
1234 assert(!base.post_prelink);1239 assert(!base.post_prelink);
12351240
1236 switch (base.tag) {1241 switch (base.tag) {
1237 inline .coff, .elf, .elf2, .wasm, .spirv => |tag| {1242 inline .coff, .elf, .elf2, .macho2, .wasm, .spirv => |tag| {
1238 dev.check(tag.devFeature());1243 dev.check(tag.devFeature());
1239 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);1244 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
1240 },1245 },
...@@ -1253,7 +1258,7 @@ pub const File = struct {...@@ -1253,7 +1258,7 @@ pub const File = struct {
1253 }1258 }
12541259
1255 switch (base.tag) {1260 switch (base.tag) {
1256 inline .elf2, .coff, .wasm, .c => |tag| {1261 inline .elf2, .coff, .macho2, .wasm, .c => |tag| {
1257 dev.check(tag.devFeature());1262 dev.check(tag.devFeature());
1258 try @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(base.comp.link_prog_node);1263 try @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(base.comp.link_prog_node);
1259 },1264 },
...@@ -1298,6 +1303,7 @@ pub const File = struct {...@@ -1298,6 +1303,7 @@ pub const File = struct {
1298 elf,1303 elf,
1299 elf2,1304 elf2,
1300 macho,1305 macho,
1306 macho2,
1301 c,1307 c,
1302 wasm,1308 wasm,
1303 spirv,1309 spirv,
...@@ -1311,6 +1317,7 @@ pub const File = struct {...@@ -1311,6 +1317,7 @@ pub const File = struct {
1311 .elf => Elf,1317 .elf => Elf,
1312 .elf2 => Elf2,1318 .elf2 => Elf2,
1313 .macho => MachO,1319 .macho => MachO,
1320 .macho2 => MachO2,
1314 .c => C,1321 .c => C,
1315 .wasm => Wasm,1322 .wasm => Wasm,
1316 .spirv => Spirv,1323 .spirv => Spirv,
...@@ -1324,7 +1331,7 @@ pub const File = struct {...@@ -1324,7 +1331,7 @@ pub const File = struct {
1324 return switch (ofmt) {1331 return switch (ofmt) {
1325 .coff => .coff,1332 .coff => .coff,
1326 .elf => if (use_new_linker) .elf2 else .elf,1333 .elf => if (use_new_linker) .elf2 else .elf,
1327 .macho => .macho,1334 .macho => if (use_new_linker) .macho2 else .macho,
1328 .wasm => .wasm,1335 .wasm => .wasm,
1329 .plan9 => .plan9,1336 .plan9 => .plan9,
1330 .c => .c,1337 .c => .c,
...@@ -1417,6 +1424,7 @@ pub const File = struct {...@@ -1417,6 +1424,7 @@ pub const File = struct {
1417 pub const Elf = @import("link/Elf.zig");1424 pub const Elf = @import("link/Elf.zig");
1418 pub const Elf2 = @import("link/Elf2.zig");1425 pub const Elf2 = @import("link/Elf2.zig");
1419 pub const MachO = @import("link/MachO.zig");1426 pub const MachO = @import("link/MachO.zig");
1427 pub const MachO2 = @import("link/MachO2.zig");
1420 pub const Spirv = @import("link/Spirv.zig");1428 pub const Spirv = @import("link/Spirv.zig");
1421 pub const Wasm = @import("link/Wasm.zig");1429 pub const Wasm = @import("link/Wasm.zig");
1422 pub const Dwarf = @import("link/Dwarf.zig");1430 pub const Dwarf = @import("link/Dwarf.zig");
src/link/MachO2.zig created+275
...@@ -0,0 +1,275 @@
1const MachO = @This();
2
3base: link.File,
4options: link.File.OpenOptions,
5mf: MappedFile,
6nodes: std.MultiArrayList(Node),
7
8const_prog_node: std.Progress.Node,
9synth_prog_node: std.Progress.Node,
10input_prog_node: std.Progress.Node,
11
12const Node = union(enum) {
13 macho,
14};
15fn getNode(macho: *MachO, ni: MappedFile.Node.Index) Node {
16 return macho.nodes.get(@backingInt(ni));
17}
18
19pub fn open(
20 arena: std.mem.Allocator,
21 comp: *Compilation,
22 path: std.Build.Cache.Path,
23 options: link.File.OpenOptions,
24) !*MachO {
25 return create(arena, comp, path, options);
26}
27pub fn createEmpty(
28 arena: std.mem.Allocator,
29 comp: *Compilation,
30 path: std.Build.Cache.Path,
31 options: link.File.OpenOptions,
32) !*MachO {
33 return create(arena, comp, path, options);
34}
35fn create(
36 arena: std.mem.Allocator,
37 comp: *Compilation,
38 path: std.Build.Cache.Path,
39 options: link.File.OpenOptions,
40) !*MachO {
41 const io = comp.io;
42
43 const macho = try arena.create(MachO);
44 const file = try path.root_dir.handle.createFile(io, path.sub_path, .{
45 .read = true,
46 .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode),
47 });
48
49 macho.* = .{
50 .base = .{
51 .tag = .macho2,
52
53 .comp = comp,
54 .emit = path,
55
56 .file = file,
57 .gc_sections = false,
58 .print_gc_sections = false,
59 .build_id = .none,
60 .allow_shlib_undefined = false,
61 .stack_size = 0,
62 },
63 .options = options,
64 .mf = try .init(file, comp.gpa, io),
65 .nodes = .empty,
66 .const_prog_node = .none,
67 .synth_prog_node = .none,
68 .input_prog_node = .none,
69 };
70 errdefer macho.deinit();
71
72 try macho.initInner();
73
74 return macho;
75}
76fn initInner(macho: *MachO) !void {
77 const comp = macho.base.comp;
78 const gpa = comp.gpa;
79
80 try macho.nodes.append(gpa, .macho); // file root node
81
82 @panic("TODO");
83}
84
85pub fn deinit(macho: *MachO) void {
86 const gpa = macho.base.comp.gpa;
87 macho.mf.deinit(gpa);
88 macho.nodes.deinit(gpa);
89 macho.* = undefined;
90}
91
92pub fn startProgress(macho: *MachO, prog_node: std.Progress.Node) void {
93 prog_node.increaseEstimatedTotalItems(4);
94 macho.const_prog_node = prog_node.start("Constants", 0);
95 macho.synth_prog_node = prog_node.start("Synthetics", 0);
96 macho.input_prog_node = prog_node.start("Inputs", 0);
97 macho.mf.update_prog_node = prog_node.start("Relocations", macho.mf.updates.items.len);
98}
99pub fn endProgress(macho: *MachO) void {
100 macho.const_prog_node.end();
101 macho.const_prog_node = .none;
102 macho.synth_prog_node.end();
103 macho.synth_prog_node = .none;
104 macho.input_prog_node.end();
105 macho.input_prog_node = .none;
106 macho.mf.update_prog_node.end();
107 macho.mf.update_prog_node = .none;
108}
109
110pub fn navSymbol(macho: *MachO, nav_id: InternPool.Nav.Index) link.Error!link.File.SymbolId {
111 _ = macho;
112 _ = nav_id;
113 @panic("TODO");
114}
115pub fn uavSymbol(
116 macho: *MachO,
117 pt: Zcu.PerThread,
118 uav_val: InternPool.Index,
119 uav_align: InternPool.Alignment,
120) link.Error!link.File.SymbolId {
121 _ = macho;
122 _ = pt;
123 _ = uav_val;
124 _ = uav_align;
125 @panic("TODO");
126}
127pub fn relocSymAddr(
128 macho: *MachO,
129 reloc_info: link.File.RelocInfo,
130) link.Error!void {
131 _ = macho;
132 _ = reloc_info;
133 @panic("TODO");
134}
135
136pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {
137 _ = macho;
138 _ = input;
139 @panic("TODO");
140}
141pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {
142 _ = macho;
143 _ = prog_node;
144 @panic("TODO");
145}
146pub fn flush(
147 macho: *MachO,
148 arena: std.mem.Allocator,
149 tid: Zcu.PerThread.Id,
150 prog_node: std.Progress.Node,
151) link.Error!void {
152 _ = macho;
153 _ = arena;
154 _ = tid;
155 _ = prog_node;
156 @panic("TODO");
157}
158pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void {
159 _ = macho;
160 _ = pt;
161 @panic("TODO");
162}
163pub fn idle(macho: *MachO, tid: Zcu.PerThread.Id) link.Error!bool {
164 _ = macho;
165 _ = tid;
166 @panic("TODO");
167}
168
169pub fn updateFunc(
170 macho: *MachO,
171 pt: Zcu.PerThread,
172 func_index: InternPool.Index,
173 mir: *const codegen.AnyMir,
174) link.Error!void {
175 _ = macho;
176 _ = pt;
177 _ = func_index;
178 _ = mir;
179 @panic("TODO");
180}
181pub fn updateNav(macho: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void {
182 _ = macho;
183 _ = pt;
184 _ = nav_index;
185 @panic("TODO");
186}
187pub fn updateExports(
188 macho: *MachO,
189 pt: Zcu.PerThread,
190 export_indices: []const Zcu.Export.Index,
191) link.Error!void {
192 _ = macho;
193 _ = pt;
194 _ = export_indices;
195 @panic("TODO");
196}
197
198pub fn dump(macho: *MachO, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
199 if (macho.options.enable_link_snapshots) {
200 try macho.printNode(tid, w, .root, 0);
201 return .enabled;
202 }
203 return .disabled;
204}
205fn printNode(
206 macho: *MachO,
207 tid: Zcu.PerThread.Id,
208 w: *Io.Writer,
209 ni: MappedFile.Node.Index,
210 indent: usize,
211) Io.Writer.Error!void {
212 const node = macho.getNode(ni);
213 try w.splatByteAll(' ', indent);
214 switch (node) {
215 .macho => try w.writeAll("macho"),
216 }
217 {
218 const mf_node = &macho.mf.nodes.items[@backingInt(ni)];
219 const off, const size = mf_node.location().resolve(&macho.mf);
220 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x} {t}{s}{s}{s}{s}{s}{s}\n", .{
221 @backingInt(ni),
222 off,
223 size,
224 mf_node.flags.alignment.toByteUnits(),
225 mf_node.flags.position,
226 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",
227 if (mf_node.flags.resized) " moved" else "",
228 if (mf_node.flags.resized) " resized" else "",
229 if (mf_node.flags.enable_next_moved) " enable_next_moved" else "",
230 if (mf_node.flags.next_moved) " next_moved" else "",
231 if (mf_node.flags.has_content) " has_content" else "",
232 });
233 }
234 if (ni.first(&macho.mf).unwrap()) |first_ni| {
235 // non-leaf, just print children
236 var child_ni = first_ni;
237 while (true) {
238 try macho.printNode(tid, w, child_ni, indent + 1);
239 child_ni = child_ni.next(&macho.mf).unwrap() orelse break;
240 }
241 return;
242 }
243 const file_loc = ni.fileLocation(&macho.mf, false);
244 var address = file_loc.offset;
245 if (file_loc.size == 0) {
246 try w.splatByteAll(' ', indent + 1);
247 try w.print("{x:0>8}\n", .{address});
248 return;
249 }
250 const line_len = 0x10;
251 var line_it = std.mem.window(
252 u8,
253 macho.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)],
254 line_len,
255 line_len,
256 );
257 while (line_it.next()) |line_bytes| : (address += line_len) {
258 try w.splatByteAll(' ', indent + 1);
259 try w.print("{x:0>8} ", .{address});
260 for (line_bytes) |byte| try w.print("{x:0>2} ", .{byte});
261 try w.splatByteAll(' ', 3 * (line_len - line_bytes.len) + 1);
262 for (line_bytes) |byte| try w.writeByte(if (std.ascii.isPrint(byte)) byte else '.');
263 try w.writeByte('\n');
264 }
265}
266
267const std = @import("std");
268const Io = std.Io;
269
270const link = @import("../link.zig");
271const codegen = @import("../codegen.zig");
272const Compilation = @import("../Compilation.zig");
273const InternPool = @import("../InternPool.zig");
274const MappedFile = @import("MappedFile.zig");
275const Zcu = @import("../Zcu.zig");
src/target.zig+1-1
...@@ -299,7 +299,7 @@ pub fn preferNewLinkerOverLld(target: *const std.Target) bool {...@@ -299,7 +299,7 @@ pub fn preferNewLinkerOverLld(target: *const std.Target) bool {
299/// Returns `true` if `ofmt` has two linker implementations, so `-fnew-linker` is meaningful.299/// Returns `true` if `ofmt` has two linker implementations, so `-fnew-linker` is meaningful.
300pub fn hasNewLinker(ofmt: std.Target.ObjectFormat) bool {300pub fn hasNewLinker(ofmt: std.Target.ObjectFormat) bool {
301 return switch (ofmt) {301 return switch (ofmt) {
302 .elf => true,302 .elf, .macho => true,
303 else => false,303 else => false,
304 };304 };
305}305}