1const Spork8 = @This();
2const builtin = @import("builtin");
3const build_options = @import("build_options");
4
5const std = @import("std");
6const Io = std.Io;
7const Allocator = std.mem.Allocator;
8const assert = std.debug.assert;
9const Path = std.Build.Cache.Path;
10const log = std.log.scoped(.link);
11
12const Air = @import("../Air.zig");
13const InternPool = @import("../InternPool.zig");
14const Zcu = @import("../Zcu.zig");
15const CodeGen = @import("../codegen/spork8/CodeGen.zig");
16const codegen = @import("../codegen.zig");
17const Mir = @import("../codegen/spork8/Mir.zig");
18const link = @import("../link.zig");
19const Compilation = @import("../Compilation.zig");
20const Liveness = @import("../Air/Liveness.zig");
21const Value = @import("../Value.zig");
22
23base: link.File,
24/// All MIR instructions for all Zcu functions.
25mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
26/// Corresponds to `mir_instructions`.
27mir_extra: std.ArrayListUnmanaged(u32) = .empty,
28/// When the key is an enum type, this represents a `@tagName` function.
29zcu_funcs: std.array_hash_map.Auto(InternPool.Index, ZcuFunc) = .empty,
30
31pub fn open(
32 arena: Allocator,
33 comp: *Compilation,
34 emit: Path,
35 options: link.File.OpenOptions,
36) !*Spork8 {
37 // TODO: restore saved linker state, don't truncate the file, and
38 // participate in incremental compilation.
39 return createEmpty(arena, comp, emit, options);
40}
41
42pub fn createEmpty(
43 arena: Allocator,
44 comp: *Compilation,
45 emit: Path,
46 options: link.File.OpenOptions,
47) !*Spork8 {
48 const target = comp.root_mod.resolved_target.result;
49 assert(target.ofmt == .raw);
50 assert(comp.config.output_mode == .Exe);
51 const io = comp.io;
52
53 const spork8 = try arena.create(Spork8);
54 spork8.* = .{
55 .base = .{
56 .tag = .spork8,
57 .comp = comp,
58 .emit = emit,
59 .gc_sections = options.gc_sections orelse true,
60 .print_gc_sections = options.print_gc_sections,
61 .stack_size = options.stack_size orelse switch (target.os.tag) {
62 .freestanding => 1 * 1024 * 1024, // 1 MiB
63 else => 16 * 1024 * 1024, // 16 MiB
64 },
65 .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
66 .file = null,
67 .build_id = options.build_id,
68 },
69 };
70 errdefer spork8.base.destroy();
71
72 spork8.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{
73 .truncate = true,
74 .read = true,
75 });
76
77 return spork8;
78}
79
80pub fn deinit(spork8: *Spork8) void {
81 const gpa = spork8.base.comp.gpa;
82 _ = gpa;
83}
84
85pub fn updateFunc(
86 spork8: *Spork8,
87 pt: Zcu.PerThread,
88 func_index: InternPool.Index,
89 any_mir: *const codegen.AnyMir,
90) !void {
91 // This linker implementation only works with `std.lang.CompilerBackend.zsf_spork8`.
92 const mir = &any_mir.spork8;
93 const zcu = pt.zcu;
94 const gpa = zcu.gpa;
95 const ip = &zcu.intern_pool;
96 const owner_nav = zcu.funcInfo(func_index).owner_nav;
97
98 log.debug("updateFunc {f}", .{ip.getNav(owner_nav).fqn.fmt(ip)});
99
100 // For Spork8, we do not lower the MIR to code just yet. That lowering happens during `flush`,
101 // after garbage collection, which can affect function and global indexes, which affects the
102 // LEB integer encoding, which affects the output binary size.
103
104 // However, we do move the MIR into a more efficient in-memory representation, where the arrays
105 // for all functions are packed together rather than keeping them each in their own `Mir`.
106 const mir_instructions_off: u32 = @intCast(spork8.mir_instructions.len);
107 const mir_extra_off: u32 = @intCast(spork8.mir_extra.items.len);
108 {
109 // Copying MultiArrayList data is a little non-trivial. Resize, then memcpy both slices.
110 const old_len = spork8.mir_instructions.len;
111 try spork8.mir_instructions.resize(gpa, old_len + mir.instructions.len);
112 const dest_slice = spork8.mir_instructions.slice().subslice(old_len, mir.instructions.len);
113 const src_slice = mir.instructions;
114 @memcpy(dest_slice.items(.tag), src_slice.items(.tag));
115 @memcpy(dest_slice.items(.data), src_slice.items(.data));
116 }
117 try spork8.mir_extra.appendSlice(gpa, mir.extra);
118
119 try spork8.zcu_funcs.ensureUnusedCapacity(gpa, 1);
120
121 // This converts AIR to MIR but does not yet lower to Spork8 code.
122 spork8.zcu_funcs.putAssumeCapacity(func_index, .{ .function = .{
123 .instructions_off = mir_instructions_off,
124 .instructions_len = @intCast(mir.instructions.len),
125 .extra_off = mir_extra_off,
126 .extra_len = @intCast(mir.extra.len),
127 } });
128}
129
130pub const ZcuFunc = union {
131 function: Function,
132
133 pub const Function = extern struct {
134 /// Index into `Spork8.mir_instructions`.
135 instructions_off: u32,
136 /// This is unused except for as a safety slice bound and could be removed.
137 instructions_len: u32,
138 /// Index into `Spork8.mir_extra`.
139 extra_off: u32,
140 /// This is unused except for as a safety slice bound and could be removed.
141 extra_len: u32,
142 };
143
144 /// Index into `Spork8.zcu_funcs`.
145 /// Note that swapRemove is sometimes performed on `zcu_funcs`.
146 pub const Index = enum(u32) {
147 _,
148
149 pub fn key(i: @This(), spork8: *const Spork8) *InternPool.Index {
150 return &spork8.zcu_funcs.keys()[@backingInt(i)];
151 }
152
153 pub fn value(i: @This(), spork8: *const Spork8) *ZcuFunc {
154 return &spork8.zcu_funcs.values()[@backingInt(i)];
155 }
156 };
157};
158
159// Generate code for the "Nav", storing it in memory to be later written to
160// the file on flush().
161pub fn updateNav(spork8: *Spork8, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
162 _ = spork8;
163 const zcu = pt.zcu;
164 const ip = &zcu.intern_pool;
165 const nav = ip.getNav(nav_index);
166 log.debug("updateNav {f}", .{nav.fqn.fmt(ip)});
167}
168
169pub fn updateLineNumber(spork8: *Spork8, pt: Zcu.PerThread, inst: InternPool.TrackedInst.Index, line: u32) !void {
170 _ = spork8;
171 _ = pt;
172 _ = inst;
173 _ = line;
174}
175
176pub fn deleteExport(
177 spork8: *Spork8,
178 exported: Zcu.Exported,
179 name: InternPool.NullTerminatedString,
180) void {
181 const zcu = spork8.base.comp.zcu.?;
182 const ip = &zcu.intern_pool;
183 const name_slice = name.toSlice(ip);
184 switch (exported) {
185 .nav => |nav_index| {
186 log.debug("deleteExport '{s}' nav={d}", .{ name_slice, @backingInt(nav_index) });
187 },
188 .uav => |uav_index| {
189 log.debug("deleteExport '{s}' uav={d}", .{ name_slice, @backingInt(uav_index) });
190 },
191 }
192}
193
194pub fn updateExports(
195 spork8: *Spork8,
196 pt: Zcu.PerThread,
197 export_indices: []const Zcu.Export.Index,
198) !void {
199 _ = spork8;
200 const zcu = pt.zcu;
201 const ip = &zcu.intern_pool;
202
203 for (export_indices) |export_idx| {
204 const exp = export_idx.ptr(zcu);
205 const name_slice = exp.opts.name.toSlice(ip);
206 switch (exp.exported) {
207 .nav => |nav_index| {
208 log.debug("updateExports {q} nav={d}", .{ name_slice, @backingInt(nav_index) });
209 },
210 .uav => |uav_index| {
211 log.debug("updateExports {q} uav={d}", .{ name_slice, @backingInt(uav_index) });
212 },
213 }
214 }
215}
216
217pub fn loadInput(spork8: *Spork8, input: link.Input) !void {
218 _ = input;
219 const comp = spork8.base.comp;
220 const diags = &comp.link_diags;
221 return diags.failParse("spork8 does not support linking files together", .{});
222}
223
224pub fn flush(
225 spork8: *Spork8,
226 arena: Allocator,
227 tid: Zcu.PerThread.Id,
228 prog_node: std.Progress.Node,
229) link.Error!void {
230 const sub_prog_node = prog_node.start("Spork8 Flush", 0);
231 defer sub_prog_node.end();
232 const io = spork8.base.comp.io;
233 const diags = &spork8.base.comp.link_diags;
234
235 _ = arena;
236 _ = tid;
237
238 // Finally, write the entire binary into the file.
239 var buffer: [1000]u8 = undefined;
240 var file_writer = spork8.base.file.?.writer(io, &buffer);
241 mirToMC(spork8, &file_writer.interface) catch |err| switch (err) {
242 error.WriteFailed => return diags.fail("failed writing to file: {t}", .{file_writer.err.?}),
243 };
244 file_writer.end() catch |err| switch (err) {
245 error.WriteFailed => return diags.fail("failed writing to file: {t}", .{file_writer.err.?}),
246 else => |e| return diags.fail("failed writing to file: {t}", .{e}),
247 };
248}
249
250fn mirToMC(spork8: *Spork8, w: *Io.Writer) !void {
251 for (spork8.mir_instructions.items(.tag), spork8.mir_instructions.items(.data)) |tag, data| {
252 switch (tag) {
253 .set_page_i => @panic("TODO"),
254 .set_addr_i => @panic("TODO"),
255 .load_i_outa => {
256 try w.writeByte(@backingInt(tag));
257 try w.writeByte(data.imm8);
258 },
259 .jump => @panic("TODO"),
260 .halt => try w.writeByte(@backingInt(tag)),
261 }
262 }
263}
264
265pub fn prelink(spork8: *Spork8, prog_node: std.Progress.Node) link.Error!void {
266 const sub_prog_node = prog_node.start("Spork8 Prelink", 0);
267 defer sub_prog_node.end();
268
269 _ = spork8;
270}