authorgravatar for ben@happyspork.comBen Anderman <ben@happyspork.com> 2026-08-21 00:40:50-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-26 20:54:49-07:00
logd101570c5cc78ba0cbed3b75abba29fa00749d2a
treed019909cf1995df749f0d0732ba4431c070350fb
parentfb6cce8cf3f2977d1828cff4aa3a9cb1b6ff6b5f

Add spork8 support for halt


4 files changed, 107 insertions(+), 9 deletions(-)

src/Compilation/Config.zig+1
...@@ -487,6 +487,7 @@ pub fn resolve(options: Options) ResolveError!Config {...@@ -487,6 +487,7 @@ pub fn resolve(options: Options) ResolveError!Config {
487 const root_strip = b: {487 const root_strip = b: {
488 if (options.root_strip) |x| break :b x;488 if (options.root_strip) |x| break :b x;
489 if (root_optimize_mode == .small) break :b true;489 if (root_optimize_mode == .small) break :b true;
490 if (target.cpu.arch == .spork8) break :b true;
490 if (!target_util.hasDebugInfo(target)) break :b true;491 if (!target_util.hasDebugInfo(target)) break :b true;
491 break :b false;492 break :b false;
492 };493 };
src/codegen/spork8/CodeGen.zig+16-3
...@@ -189,8 +189,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -189,8 +189,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
189 return switch (air_tags[@intFromEnum(inst)]) {189 return switch (air_tags[@intFromEnum(inst)]) {
190 .inferred_alloc, .inferred_alloc_comptime => unreachable,190 .inferred_alloc, .inferred_alloc_comptime => unreachable,
191191
192 .unreach => cg.airUnreachable(inst),
193
194 .add,192 .add,
195 .add_sat,193 .add_sat,
196 .add_wrap,194 .add_wrap,
...@@ -257,7 +255,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -257,7 +255,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
257 .alloc,255 .alloc,
258 .arg,256 .arg,
259 .block,257 .block,
260 .trap,
261 .breakpoint,258 .breakpoint,
262 .br,259 .br,
263 .repeat,260 .repeat,
...@@ -436,6 +433,9 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -436,6 +433,9 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
436 .legalize_compiler_rt_call,433 .legalize_compiler_rt_call,
437 => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}),434 => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}),
438435
436 .unreach => cg.airUnreachable(inst),
437 .trap => cg.airTrap(inst),
438
439 .work_item_id,439 .work_item_id,
440 .work_group_size,440 .work_group_size,
441 .work_group_id,441 .work_group_id,
...@@ -448,6 +448,19 @@ fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -448,6 +448,19 @@ fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
448 _ = inst;448 _ = inst;
449}449}
450450
451fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
452 _ = inst;
453 try cg.addTag(.halt);
454}
455
456pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void {
457 try cg.mir_instructions.append(cg.gpa, inst);
458}
459
460pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
461 try cg.addInst(.{ .tag = tag, .data = .{ .nothing = {} } });
462}
463
451fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {464fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {
452 const zcu = cg.pt.zcu;465 const zcu = cg.pt.zcu;
453 const func = zcu.funcInfo(cg.func_index);466 const func = zcu.funcInfo(cg.func_index);
src/codegen/spork8/Mir.zig+3
...@@ -27,6 +27,8 @@ pub const Inst = struct {...@@ -27,6 +27,8 @@ pub const Inst = struct {
27 load_i = 0x11,27 load_i = 0x11,
28 /// index28 /// index
29 jump = 0x68,29 jump = 0x68,
30 /// nothing
31 halt = 0xE3,
30 };32 };
3133
32 /// All instructions contain a 4-byte payload, which is contained within34 /// All instructions contain a 4-byte payload, which is contained within
...@@ -35,6 +37,7 @@ pub const Inst = struct {...@@ -35,6 +37,7 @@ pub const Inst = struct {
35 pub const Data = union {37 pub const Data = union {
36 imm8: u8,38 imm8: u8,
37 index: Index,39 index: Index,
40 nothing: void,
3841
39 comptime {42 comptime {
40 switch (builtin.mode) {43 switch (builtin.mode) {
src/link/Spork8.zig+87-6
...@@ -26,6 +26,8 @@ base: link.File,...@@ -26,6 +26,8 @@ base: link.File,
26mir_instructions: std.MultiArrayList(Mir.Inst) = .{},26mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
27/// Corresponds to `mir_instructions`.27/// Corresponds to `mir_instructions`.
28mir_extra: std.ArrayListUnmanaged(u32) = .empty,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,
2931
30pub fn open(32pub fn open(
31 arena: Allocator,33 arena: Allocator,
...@@ -88,18 +90,74 @@ pub fn updateFunc(...@@ -88,18 +90,74 @@ pub fn updateFunc(
88 any_mir: *const codegen.AnyMir,90 any_mir: *const codegen.AnyMir,
89) !void {91) !void {
90 dev.check(.spork8_backend);92 dev.check(.spork8_backend);
91 // This linker implementation only works with codegen backend `.stage2_wasm`.93 // This linker implementation only works with codegen backend `.stage2_spork8`.
92 const mir = &any_mir.spork8;94 const mir = &any_mir.spork8;
93 const zcu = pt.zcu;95 const zcu = pt.zcu;
94 const gpa = zcu.gpa;96 const gpa = zcu.gpa;
95 const ip = &zcu.intern_pool;97 const ip = &zcu.intern_pool;
96 const owner_nav = zcu.funcInfo(func_index).owner_nav;98 const owner_nav = zcu.funcInfo(func_index).owner_nav;
97 _ = gpa;99
98 _ = mir;
99 _ = spork8;
100 log.debug("updateFunc {f}", .{ip.getNav(owner_nav).fqn.fmt(ip)});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 } });
101}130}
102131
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
103// Generate code for the "Nav", storing it in memory to be later written to161// Generate code for the "Nav", storing it in memory to be later written to
104// the file on flush().162// the file on flush().
105pub fn updateNav(spork8: *Spork8, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {163pub fn updateNav(spork8: *Spork8, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
...@@ -172,11 +230,34 @@ pub fn flush(...@@ -172,11 +230,34 @@ pub fn flush(
172) link.Error!void {230) link.Error!void {
173 const sub_prog_node = prog_node.start("Spork8 Flush", 0);231 const sub_prog_node = prog_node.start("Spork8 Flush", 0);
174 defer sub_prog_node.end();232 defer sub_prog_node.end();
233 const io = spork8.base.comp.io;
234 const diags = &spork8.base.comp.link_diags;
175235
176 _ = spork8;
177 _ = arena;236 _ = arena;
178 _ = tid;237 _ = tid;
179 log.debug("TODO implement flush", .{});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)) |tag| {
253 switch (tag) {
254 .set_page_i => @panic("TODO"),
255 .set_addr_i => @panic("TODO"),
256 .load_i => @panic("TODO"),
257 .jump => @panic("TODO"),
258 .halt => try w.writeByte(@backingInt(tag)),
259 }
260 }
180}261}
181262
182pub fn prelink(spork8: *Spork8, prog_node: std.Progress.Node) link.Error!void {263pub fn prelink(spork8: *Spork8, prog_node: std.Progress.Node) link.Error!void {