diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 9b97d4a2cbafbc2fc8c964ea4ba8aa0159483625..58a69ba70100065ab0175fd94319ebaf0ae8cce4 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -487,6 +487,7 @@ pub fn resolve(options: Options) ResolveError!Config { const root_strip = b: { if (options.root_strip) |x| break :b x; if (root_optimize_mode == .small) break :b true; + if (target.cpu.arch == .spork8) break :b true; if (!target_util.hasDebugInfo(target)) break :b true; break :b false; }; diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig index 07007a41083e5d2535f9d0a5cdded39e75437658..21e172e203865f5342b9a736ef4184ecf3681b67 100644 --- a/src/codegen/spork8/CodeGen.zig +++ b/src/codegen/spork8/CodeGen.zig @@ -189,8 +189,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { return switch (air_tags[@intFromEnum(inst)]) { .inferred_alloc, .inferred_alloc_comptime => unreachable, - .unreach => cg.airUnreachable(inst), - .add, .add_sat, .add_wrap, @@ -257,7 +255,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .alloc, .arg, .block, - .trap, .breakpoint, .br, .repeat, @@ -436,6 +433,9 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .legalize_compiler_rt_call, => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}), + .unreach => cg.airUnreachable(inst), + .trap => cg.airTrap(inst), + .work_item_id, .work_group_size, .work_group_id, @@ -448,6 +448,19 @@ fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { _ = inst; } +fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + _ = inst; + try cg.addTag(.halt); +} + +pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void { + try cg.mir_instructions.append(cg.gpa, inst); +} + +pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { + try cg.addInst(.{ .tag = tag, .data = .{ .nothing = {} } }); +} + fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } { const zcu = cg.pt.zcu; const func = zcu.funcInfo(cg.func_index); diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig index f81fbd9a2157a756ea4da5df862bc4e10b5d6e70..6f191e88b5f896a1617dbd576dbe7030034e8f34 100644 --- a/src/codegen/spork8/Mir.zig +++ b/src/codegen/spork8/Mir.zig @@ -27,6 +27,8 @@ pub const Inst = struct { load_i = 0x11, /// index jump = 0x68, + /// nothing + halt = 0xE3, }; /// All instructions contain a 4-byte payload, which is contained within @@ -35,6 +37,7 @@ pub const Inst = struct { pub const Data = union { imm8: u8, index: Index, + nothing: void, comptime { switch (builtin.mode) { diff --git a/src/link/Spork8.zig b/src/link/Spork8.zig index e2786080d393f48282bc4a57d44d3a5c3300b60e..3f005570cd7b7113423a7853bb523ba3a8f782be 100644 --- a/src/link/Spork8.zig +++ b/src/link/Spork8.zig @@ -26,6 +26,8 @@ base: link.File, mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, /// Corresponds to `mir_instructions`. mir_extra: std.ArrayListUnmanaged(u32) = .empty, +/// When the key is an enum type, this represents a `@tagName` function. +zcu_funcs: std.array_hash_map.Auto(InternPool.Index, ZcuFunc) = .empty, pub fn open( arena: Allocator, @@ -88,18 +90,74 @@ pub fn updateFunc( any_mir: *const codegen.AnyMir, ) !void { dev.check(.spork8_backend); - // This linker implementation only works with codegen backend `.stage2_wasm`. + // This linker implementation only works with codegen backend `.stage2_spork8`. const mir = &any_mir.spork8; const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; const owner_nav = zcu.funcInfo(func_index).owner_nav; - _ = gpa; - _ = mir; - _ = spork8; + log.debug("updateFunc {f}", .{ip.getNav(owner_nav).fqn.fmt(ip)}); + + // For Spork8, we do not lower the MIR to code just yet. That lowering happens during `flush`, + // after garbage collection, which can affect function and global indexes, which affects the + // LEB integer encoding, which affects the output binary size. + + // However, we do move the MIR into a more efficient in-memory representation, where the arrays + // for all functions are packed together rather than keeping them each in their own `Mir`. + const mir_instructions_off: u32 = @intCast(spork8.mir_instructions.len); + const mir_extra_off: u32 = @intCast(spork8.mir_extra.items.len); + { + // Copying MultiArrayList data is a little non-trivial. Resize, then memcpy both slices. + const old_len = spork8.mir_instructions.len; + try spork8.mir_instructions.resize(gpa, old_len + mir.instructions.len); + const dest_slice = spork8.mir_instructions.slice().subslice(old_len, mir.instructions.len); + const src_slice = mir.instructions; + @memcpy(dest_slice.items(.tag), src_slice.items(.tag)); + @memcpy(dest_slice.items(.data), src_slice.items(.data)); + } + try spork8.mir_extra.appendSlice(gpa, mir.extra); + + try spork8.zcu_funcs.ensureUnusedCapacity(gpa, 1); + + // This converts AIR to MIR but does not yet lower to Spork8 code. + spork8.zcu_funcs.putAssumeCapacity(func_index, .{ .function = .{ + .instructions_off = mir_instructions_off, + .instructions_len = @intCast(mir.instructions.len), + .extra_off = mir_extra_off, + .extra_len = @intCast(mir.extra.len), + } }); } +pub const ZcuFunc = union { + function: Function, + + pub const Function = extern struct { + /// Index into `Spork8.mir_instructions`. + instructions_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + instructions_len: u32, + /// Index into `Spork8.mir_extra`. + extra_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + extra_len: u32, + }; + + /// Index into `Spork8.zcu_funcs`. + /// Note that swapRemove is sometimes performed on `zcu_funcs`. + pub const Index = enum(u32) { + _, + + pub fn key(i: @This(), spork8: *const Spork8) *InternPool.Index { + return &spork8.zcu_funcs.keys()[@backingInt(i)]; + } + + pub fn value(i: @This(), spork8: *const Spork8) *ZcuFunc { + return &spork8.zcu_funcs.values()[@backingInt(i)]; + } + }; +}; + // Generate code for the "Nav", storing it in memory to be later written to // the file on flush(). pub fn updateNav(spork8: *Spork8, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -172,11 +230,34 @@ pub fn flush( ) link.Error!void { const sub_prog_node = prog_node.start("Spork8 Flush", 0); defer sub_prog_node.end(); + const io = spork8.base.comp.io; + const diags = &spork8.base.comp.link_diags; - _ = spork8; _ = arena; _ = tid; - log.debug("TODO implement flush", .{}); + + // Finally, write the entire binary into the file. + var buffer: [1000]u8 = undefined; + var file_writer = spork8.base.file.?.writer(io, &buffer); + mirToMC(spork8, &file_writer.interface) catch |err| switch (err) { + error.WriteFailed => return diags.fail("failed writing to file: {t}", .{file_writer.err.?}), + }; + file_writer.end() catch |err| switch (err) { + error.WriteFailed => return diags.fail("failed writing to file: {t}", .{file_writer.err.?}), + else => |e| return diags.fail("failed writing to file: {t}", .{e}), + }; +} + +fn mirToMC(spork8: *Spork8, w: *Io.Writer) !void { + for (spork8.mir_instructions.items(.tag)) |tag| { + switch (tag) { + .set_page_i => @panic("TODO"), + .set_addr_i => @panic("TODO"), + .load_i => @panic("TODO"), + .jump => @panic("TODO"), + .halt => try w.writeByte(@backingInt(tag)), + } + } } pub fn prelink(spork8: *Spork8, prog_node: std.Progress.Node) link.Error!void {