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-27 10:19:55+01:00
log4019d010007294de58acd5370cc879f14d40e506
treeee801111eba463fb8e86eece6c7a92f4c955580c
parent02b67d7e19455cfc069daf9e665fe28d4d01a9f2
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 {
112112 .elf_linker,
113113 .elf2_linker,
114114 .macho_linker,
115 .macho2_linker,
115116 .c_linker,
116117 .wasm_linker,
117118 .spirv_linker,
......@@ -323,6 +324,7 @@ pub const Feature = enum {
323324 elf_linker,
324325 elf2_linker,
325326 macho_linker,
327 macho2_linker,
326328 c_linker,
327329 wasm_linker,
328330 spirv_linker,
src/link.zig+17-9
......@@ -587,7 +587,7 @@ pub const File = struct {
587587 pub fn startProgress(base: *File, prog_node: std.Progress.Node) void {
588588 switch (base.tag) {
589589 else => {},
590 inline .elf2, .coff => |tag| {
590 inline .elf2, .coff, .macho2 => |tag| {
591591 dev.check(tag.devFeature());
592592 return @as(*tag.Type(), @fieldParentPtr("base", base)).startProgress(prog_node);
593593 },
......@@ -597,7 +597,7 @@ pub const File = struct {
597597 pub fn endProgress(base: *File) void {
598598 switch (base.tag) {
599599 else => {},
600 inline .elf2, .coff => |tag| {
600 inline .elf2, .coff, .macho2 => |tag| {
601601 dev.check(tag.devFeature());
602602 return @as(*tag.Type(), @fieldParentPtr("base", base)).endProgress();
603603 },
......@@ -651,11 +651,13 @@ pub const File = struct {
651651 }
652652 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
653653 },
654 .elf2, .coff => if (base.file == null) {
654 .elf2, .coff, .macho2 => if (base.file == null) {
655655 const mf = if (base.cast(.elf2)) |elf|
656656 &elf.mf
657657 else if (base.cast(.coff)) |coff|
658658 &coff.mf
659 else if (base.cast(.macho2)) |macho|
660 &macho.mf
659661 else
660662 unreachable;
661663 mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
......@@ -740,11 +742,13 @@ pub const File = struct {
740742 }
741743 }
742744 },
743 .elf2, .coff => if (base.file) |f| {
745 .elf2, .coff, .macho2 => if (base.file) |f| {
744746 const mf = if (base.cast(.elf2)) |elf|
745747 &elf.mf
746748 else if (base.cast(.coff)) |coff|
747749 &coff.mf
750 else if (base.cast(.macho2)) |macho|
751 &macho.mf
748752 else
749753 unreachable;
750754 mf.unmap();
......@@ -900,6 +904,7 @@ pub const File = struct {
900904 .plan9 => unreachable,
901905 .spirv => {},
902906 .coff => {},
907 .macho2 => {},
903908 inline else => |tag| {
904909 dev.check(tag.devFeature());
905910 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateLineNumber(pt, ti_id, line);
......@@ -951,7 +956,7 @@ pub const File = struct {
951956 pub fn idle(base: *File, tid: Zcu.PerThread.Id) Error!bool {
952957 switch (base.tag) {
953958 else => return false,
954 inline .elf2, .coff => |tag| {
959 inline .elf2, .coff, .macho2 => |tag| {
955960 dev.check(tag.devFeature());
956961 return @as(*tag.Type(), @fieldParentPtr("base", base)).idle(tid);
957962 },
......@@ -961,7 +966,7 @@ pub const File = struct {
961966 pub fn updateErrorData(base: *File, pt: Zcu.PerThread) Error!void {
962967 switch (base.tag) {
963968 else => {},
964 inline .elf2, .coff => |tag| {
969 inline .elf2, .coff, .macho2 => |tag| {
965970 dev.check(tag.devFeature());
966971 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateErrorData(pt);
967972 },
......@@ -1234,7 +1239,7 @@ pub const File = struct {
12341239 assert(!base.post_prelink);
12351240
12361241 switch (base.tag) {
1237 inline .coff, .elf, .elf2, .wasm, .spirv => |tag| {
1242 inline .coff, .elf, .elf2, .macho2, .wasm, .spirv => |tag| {
12381243 dev.check(tag.devFeature());
12391244 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
12401245 },
......@@ -1253,7 +1258,7 @@ pub const File = struct {
12531258 }
12541259
12551260 switch (base.tag) {
1256 inline .elf2, .coff, .wasm, .c => |tag| {
1261 inline .elf2, .coff, .macho2, .wasm, .c => |tag| {
12571262 dev.check(tag.devFeature());
12581263 try @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(base.comp.link_prog_node);
12591264 },
......@@ -1298,6 +1303,7 @@ pub const File = struct {
12981303 elf,
12991304 elf2,
13001305 macho,
1306 macho2,
13011307 c,
13021308 wasm,
13031309 spirv,
......@@ -1311,6 +1317,7 @@ pub const File = struct {
13111317 .elf => Elf,
13121318 .elf2 => Elf2,
13131319 .macho => MachO,
1320 .macho2 => MachO2,
13141321 .c => C,
13151322 .wasm => Wasm,
13161323 .spirv => Spirv,
......@@ -1324,7 +1331,7 @@ pub const File = struct {
13241331 return switch (ofmt) {
13251332 .coff => .coff,
13261333 .elf => if (use_new_linker) .elf2 else .elf,
1327 .macho => .macho,
1334 .macho => if (use_new_linker) .macho2 else .macho,
13281335 .wasm => .wasm,
13291336 .plan9 => .plan9,
13301337 .c => .c,
......@@ -1417,6 +1424,7 @@ pub const File = struct {
14171424 pub const Elf = @import("link/Elf.zig");
14181425 pub const Elf2 = @import("link/Elf2.zig");
14191426 pub const MachO = @import("link/MachO.zig");
1427 pub const MachO2 = @import("link/MachO2.zig");
14201428 pub const Spirv = @import("link/Spirv.zig");
14211429 pub const Wasm = @import("link/Wasm.zig");
14221430 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 {
299299/// Returns `true` if `ofmt` has two linker implementations, so `-fnew-linker` is meaningful.
300300pub fn hasNewLinker(ofmt: std.Target.ObjectFormat) bool {
301301 return switch (ofmt) {
302 .elf => true,
302 .elf, .macho => true,
303303 else => false,
304304 };
305305}