authorgravatar for ben@happyspork.comBen Anderman <ben@happyspork.com> 2026-08-21 17:55:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-26 20:54:49-07:00
loga95b6d22efa89e2496201ed4b02788426638bbf5
tree10613561a93bcec774a17eee8a9cf5f03a9d157c
parentd101570c5cc78ba0cbed3b75abba29fa00749d2a

Add support for Spork8 inline assembly with LoadI OutA


3 files changed, 225 insertions(+), 4 deletions(-)

src/codegen/spork8/CodeGen.zig+148-1
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const mem = std.mem;
23const Allocator = std.mem.Allocator;
34const assert = std.debug.assert;
45
......@@ -360,7 +361,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
360361 .error_set_has_value,
361362 .frame_addr,
362363
363 .assembly,
364364 .is_err_ptr,
365365 .is_non_err_ptr,
366366
......@@ -434,6 +434,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
434434 => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}),
435435
436436 .unreach => cg.airUnreachable(inst),
437 .assembly => cg.airAssembly(inst),
437438 .trap => cg.airTrap(inst),
438439
439440 .work_item_id,
......@@ -453,6 +454,77 @@ fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
453454 try cg.addTag(.halt);
454455}
455456
457fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
458 const unwrapped_asm = cg.air.unwrapAsm(inst);
459 const outputs = unwrapped_asm.outputs;
460 // const inputs = unwrapped_asm.inputs;
461
462 const zcu = cg.pt.zcu;
463 // const output_ty = cg.typeOfIndex(inst);
464
465 if (outputs.len != 0) {
466 @panic("TODO: Support assembly outputs");
467 }
468
469 var constValues: std.array_hash_map.String(u8) = .empty;
470 defer constValues.deinit(zcu.gpa);
471 {
472 var it = unwrapped_asm.iterateInputs();
473 while (it.next()) |input| {
474 const constraint = input.constraint;
475 if (!mem.eql(u8, constraint, "I")) {
476 return cg.fail("Assembly constraint {q} not supported", .{constraint});
477 }
478 const operand = input.operand.toInterned() orelse {
479 return cg.fail("Immediate argument to inline assembly must be compile-time value", .{});
480 };
481 const name = input.name;
482
483 const value = switch (zcu.intern_pool.indexToKey(operand)) {
484 .int => |val| v: {
485 if (val.ty != .u8_type) {
486 return cg.fail("Non-u8 type used in inline assembly value: {}", .{val.ty});
487 }
488 break :v val.storage.u64;
489 },
490 else => return cg.fail("Non-int operands not supported", .{}),
491 };
492
493 try constValues.put(zcu.gpa, name, @intCast(value));
494
495 // return cg.fail("TODO: Constraint={q}, name={q}, value={}", .{ constraint, name, value });
496 }
497 }
498
499 {
500 var lines = mem.tokenizeScalar(u8, unwrapped_asm.source, '\n');
501 while (lines.next()) |line| {
502 var tokens = mem.tokenizeScalar(u8, line, ' ');
503 const op = tokens.next().?;
504 const instType = std.meta.stringToEnum(AsmInstType, op) orelse return cg.fail("Invalid asm instruction: {q}", .{op});
505 switch (instType) {
506 .LoadI => {
507 const register = std.meta.stringToEnum(Register, tokens.next().?).?;
508 const value = tokens.next().?;
509 const intValue = v: {
510 if (mem.startsWith(u8, value, "%[")) {
511 const name = value[2 .. value.len - 1];
512 break :v constValues.get(name).?;
513 } else {
514 break :v std.fmt.parseInt(u8, value, 0) catch unreachable;
515 }
516 };
517 if (register != .OutA) {
518 return cg.fail("TODO: other variants of LoadI", .{});
519 }
520 try cg.addTagImm8(.load_i_outa, intValue);
521 },
522 else => return cg.fail("TODO: support asm instruction: {t}", .{instType}),
523 }
524 }
525 }
526}
527
456528pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void {
457529 try cg.mir_instructions.append(cg.gpa, inst);
458530}
......@@ -461,6 +533,10 @@ pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
461533 try cg.addInst(.{ .tag = tag, .data = .{ .nothing = {} } });
462534}
463535
536pub fn addTagImm8(cg: *CodeGen, tag: Mir.Inst.Tag, imm8: u8) error{OutOfMemory}!void {
537 try cg.addInst(.{ .tag = tag, .data = .{ .imm8 = imm8 } });
538}
539
464540fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {
465541 const zcu = cg.pt.zcu;
466542 const func = zcu.funcInfo(cg.func_index);
......@@ -470,3 +546,74 @@ fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemor
470546fn extraLen(cg: *const CodeGen) u32 {
471547 return @intCast(cg.mir_extra.items.len - cg.start_mir_extra_off);
472548}
549
550const AsmInstType = enum(u8) {
551 SetPageReg, // Set the memory address high byte to a register value.
552 SetPageI, // Set the memory address high byte to a constant value.
553 SetAddrReg, // Set the memory address low byte to a register value.
554 SetAddrI, // Set the memory address low byte to a constant value.
555 Load, // Load a value from a constant address into a register.
556 LoadI, // Load a constant value into a register.
557 LoadP, // Load a value from a constant address (setting low byte only) into a register.
558 LoadInc, // Load a value from the currently set memory address into a register, and increment the address n times.
559 LoadStck, // Load a value from an offset on the current stack frame into a register.
560 Store, // Store a value to a constant address from a register.
561 StoreI, // Store a constant value into a constant address.
562 StoreP, // Store a value to a constant address (low byte only) from a register.
563 StoreInc, // Store a value from the currently set memory address from a register, and increment the address n times.
564 StoreStck, // Store a value to an offset on the current stack frame, from a register.
565 StoreNStck, // Store a value to an offset on the next stack frame, from a register.
566 StorePStck, // Store a value to an offset on the previous stack frame, from a register.
567 StoreStckI, // Store a constant value to an offset on the current stack frame.
568 StoreNStckI, // Store a constant value to an offset on the next stack frame.
569 StorePStckI, // Store a constant value to an offset on the previous stack frame.
570 Copy, // Copy a value from one register to another register.
571 Jump, // Jump to a constant location.
572 JumpReg, // Jump to a register A (high byte) + register B (low byte).
573 JumpMem, // Jump to a location pointed to by memory at the current memory address (high byte first).
574 Call, // Call a function.
575 Return, // Return from a function.
576 CmpI, // Compare A to a constant value (sets flags, but discards result).
577 CmpAndI, // Compare A to a constant value with bitwise AND (sets flags, but discards result).
578 Cmp, // Compare A to a value from memory (sets flags, but discards result).
579 CmpAnd, // Compare A to a value in memory with bitwise AND (sets flags, but discards result).
580 CmpReg, // Compare A to a value from a register (sets flags, but discards result).
581 CmpAndReg, // Compare A to a value from a register with bitwise AND (sets flags, but discards result).
582 ShiftL, // Shift B left by 1.
583 ShiftR, // Shift B right by 1.
584 RotateL, // Rotate B left by 1.
585 RotateR, // Rotate B right by 1.
586 AddI, // Add a constant value to A.
587 SubI, // Subtract a constant value from A.
588 AndI, // Bitwise-AND A with a constant value.
589 AddINF, // Add a constant value to A, without updating flags.
590 SubINF, // Subtract a constant value from A, without updating flags.
591 AndINF, // Bitwise-AND A with a constant value, without updating flags.
592 AccumulateAdd, // Add register B to A -> A.
593 AccumulateSub, // Subtract register B from A -> A.
594 AccumulateAnd, // A & B -> A.
595 OrI, // Bitwise OR B with A -> A.
596 XorI, // Bitwise OR a constant value with A -> A.
597 Not, // Invert register A.
598 Add, // Add a value from memory to A.
599 Sub, // Subtract a value from memory from A.
600 And, // AND A with a value from memory.
601 Or, // OR A with a value from memory.
602 Xor, // XOR A with a value from memory.
603 Nop, // No-op.
604 Nop1, // No-op with 1 extra clock cycle.
605 Nop2, // No-op with 2 extra clock cycles.
606 Halt, // Halt - stop the program forever (until reset).
607};
608
609const Register = enum(u8) {
610 A,
611 B,
612 C,
613 PCnt,
614 MAdr,
615 Stack,
616 OutA,
617 Shift,
618 Swap,
619};
src/codegen/spork8/Mir.zig+72-1
......@@ -24,7 +24,7 @@ pub const Inst = struct {
2424 /// imm8
2525 set_addr_i = 0x09,
2626 /// imm8
27 load_i = 0x11,
27 load_i_outa = 0x15,
2828 /// index
2929 jump = 0x68,
3030 /// nothing
......@@ -52,3 +52,74 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
5252 mir.instructions.deinit(gpa);
5353 mir.* = undefined;
5454}
55
56const AsmInstType = enum(u8) {
57 SetPageReg, // Set the memory address high byte to a register value.
58 SetPageI, // Set the memory address high byte to a constant value.
59 SetAddrReg, // Set the memory address low byte to a register value.
60 SetAddrI, // Set the memory address low byte to a constant value.
61 Load, // Load a value from a constant address into a register.
62 LoadI, // Load a constant value into a register.
63 LoadP, // Load a value from a constant address (setting low byte only) into a register.
64 LoadInc, // Load a value from the currently set memory address into a register, and increment the address n times.
65 LoadStck, // Load a value from an offset on the current stack frame into a register.
66 Store, // Store a value to a constant address from a register.
67 StoreI, // Store a constant value into a constant address.
68 StoreP, // Store a value to a constant address (low byte only) from a register.
69 StoreInc, // Store a value from the currently set memory address from a register, and increment the address n times.
70 StoreStck, // Store a value to an offset on the current stack frame, from a register.
71 StoreNStck, // Store a value to an offset on the next stack frame, from a register.
72 StorePStck, // Store a value to an offset on the previous stack frame, from a register.
73 StoreStckI, // Store a constant value to an offset on the current stack frame.
74 StoreNStckI, // Store a constant value to an offset on the next stack frame.
75 StorePStckI, // Store a constant value to an offset on the previous stack frame.
76 Copy, // Copy a value from one register to another register.
77 Jump, // Jump to a constant location.
78 JumpReg, // Jump to a register A (high byte) + register B (low byte).
79 JumpMem, // Jump to a location pointed to by memory at the current memory address (high byte first).
80 Call, // Call a function.
81 Return, // Return from a function.
82 CmpI, // Compare A to a constant value (sets flags, but discards result).
83 CmpAndI, // Compare A to a constant value with bitwise AND (sets flags, but discards result).
84 Cmp, // Compare A to a value from memory (sets flags, but discards result).
85 CmpAnd, // Compare A to a value in memory with bitwise AND (sets flags, but discards result).
86 CmpReg, // Compare A to a value from a register (sets flags, but discards result).
87 CmpAndReg, // Compare A to a value from a register with bitwise AND (sets flags, but discards result).
88 ShiftL, // Shift B left by 1.
89 ShiftR, // Shift B right by 1.
90 RotateL, // Rotate B left by 1.
91 RotateR, // Rotate B right by 1.
92 AddI, // Add a constant value to A.
93 SubI, // Subtract a constant value from A.
94 AndI, // Bitwise-AND A with a constant value.
95 AddINF, // Add a constant value to A, without updating flags.
96 SubINF, // Subtract a constant value from A, without updating flags.
97 AndINF, // Bitwise-AND A with a constant value, without updating flags.
98 AccumulateAdd, // Add register B to A -> A.
99 AccumulateSub, // Subtract register B from A -> A.
100 AccumulateAnd, // A & B -> A.
101 OrI, // Bitwise OR B with A -> A.
102 XorI, // Bitwise OR a constant value with A -> A.
103 Not, // Invert register A.
104 Add, // Add a value from memory to A.
105 Sub, // Subtract a value from memory from A.
106 And, // AND A with a value from memory.
107 Or, // OR A with a value from memory.
108 Xor, // XOR A with a value from memory.
109 Nop, // No-op.
110 Nop1, // No-op with 1 extra clock cycle.
111 Nop2, // No-op with 2 extra clock cycles.
112 Halt, // Halt - stop the program forever (until reset).
113};
114
115const Registers = enum(u8) {
116 A,
117 B,
118 C,
119 PCnt,
120 MAdr,
121 Stack,
122 OutA,
123 Shift,
124 Swap,
125};
src/link/Spork8.zig+5-2
......@@ -249,11 +249,14 @@ pub fn flush(
249249}
250250
251251fn mirToMC(spork8: *Spork8, w: *Io.Writer) !void {
252 for (spork8.mir_instructions.items(.tag)) |tag| {
252 for (spork8.mir_instructions.items(.tag), spork8.mir_instructions.items(.data)) |tag, data| {
253253 switch (tag) {
254254 .set_page_i => @panic("TODO"),
255255 .set_addr_i => @panic("TODO"),
256 .load_i => @panic("TODO"),
256 .load_i_outa => {
257 try w.writeByte(@backingInt(tag));
258 try w.writeByte(data.imm8);
259 },
257260 .jump => @panic("TODO"),
258261 .halt => try w.writeByte(@backingInt(tag)),
259262 }