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