| 1 | const Spork8 = @This(); |
| 2 | const builtin = @import("builtin"); |
| 3 | const build_options = @import("build_options"); |
| 4 | |
| 5 | const std = @import("std"); |
| 6 | const Io = std.Io; |
| 7 | const Allocator = std.mem.Allocator; |
| 8 | const assert = std.debug.assert; |
| 9 | const Path = std.Build.Cache.Path; |
| 10 | const log = std.log.scoped(.link); |
| 11 | |
| 12 | const Air = @import("../Air.zig"); |
| 13 | const InternPool = @import("../InternPool.zig"); |
| 14 | const Zcu = @import("../Zcu.zig"); |
| 15 | const CodeGen = @import("../codegen/spork8/CodeGen.zig"); |
| 16 | const codegen = @import("../codegen.zig"); |
| 17 | const Mir = @import("../codegen/spork8/Mir.zig"); |
| 18 | const link = @import("../link.zig"); |
| 19 | const Compilation = @import("../Compilation.zig"); |
| 20 | const Liveness = @import("../Air/Liveness.zig"); |
| 21 | const Value = @import("../Value.zig"); |
| 22 | |
| 23 | base: link.File, |
| 24 | /// All MIR instructions for all Zcu functions. |
| 25 | mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, |
| 26 | /// Corresponds to `mir_instructions`. |
| 27 | mir_extra: std.ArrayListUnmanaged(u32) = .empty, |
| 28 | /// When the key is an enum type, this represents a `@tagName` function. |
| 29 | zcu_funcs: std.array_hash_map.Auto(InternPool.Index, ZcuFunc) = .empty, |
| 30 | |
| 31 | pub 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 | |
| 42 | pub 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 | |
| 80 | pub fn deinit(spork8: *Spork8) void { |
| 81 | const gpa = spork8.base.comp.gpa; |
| 82 | _ = gpa; |
| 83 | } |
| 84 | |
| 85 | pub 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 | |
| 130 | pub 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(). |
| 161 | pub 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 | |
| 169 | pub fn updateLineNumber(spork8: *Spork8, pt: Zcu.PerThread, inst: InternPool.TrackedInst.Index, line: u32) !void { |
| 170 | _ = spork8; |
| 171 | _ = pt; |
| 172 | _ = inst; |
| 173 | _ = line; |
| 174 | } |
| 175 | |
| 176 | pub 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 | |
| 194 | pub 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 | |
| 217 | pub 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 | |
| 224 | pub 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 | |
| 250 | fn 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 | |
| 265 | pub 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 | } |