Commit 54f3b0a560

Andrew Kelley <andrew@ziglang.org>
2020-08-22 22:36:08
stage2: clean up SPU Mk II code
* move SPU code from std to self hosted compiler * change std lib comments to be descriptive rather than prescriptive * avoid usingnamespace * fix case style of error codes * remove duplication of producer_string * generalize handling of less than 64 bit arch pointers * clean up SPU II related test harness code
1 parent 24efbf5
Changed files (10)
lib/std/spu/defines.zig
@@ -1,158 +0,0 @@
-const std = @import("std");
-
-pub const ExecutionCondition = enum(u3) {
-    always = 0,
-    when_zero = 1,
-    not_zero = 2,
-    greater_zero = 3,
-    less_than_zero = 4,
-    greater_or_equal_zero = 5,
-    less_or_equal_zero = 6,
-    overflow = 7,
-};
-
-pub const InputBehaviour = enum(u2) {
-    zero = 0,
-    immediate = 1,
-    peek = 2,
-    pop = 3,
-};
-
-pub const OutputBehaviour = enum(u2) {
-    discard = 0,
-    push = 1,
-    jump = 2,
-    jump_relative = 3,
-};
-
-pub const Command = enum(u5) {
-    copy = 0,
-    ipget = 1,
-    get = 2,
-    set = 3,
-    store8 = 4,
-    store16 = 5,
-    load8 = 6,
-    load16 = 7,
-    undefined0 = 8,
-    undefined1 = 9,
-    frget = 10,
-    frset = 11,
-    bpget = 12,
-    bpset = 13,
-    spget = 14,
-    spset = 15,
-    add = 16,
-    sub = 17,
-    mul = 18,
-    div = 19,
-    mod = 20,
-    @"and" = 21,
-    @"or" = 22,
-    xor = 23,
-    not = 24,
-    signext = 25,
-    rol = 26,
-    ror = 27,
-    bswap = 28,
-    asr = 29,
-    lsl = 30,
-    lsr = 31,
-};
-
-pub const Instruction = packed struct {
-    condition: ExecutionCondition,
-    input0: InputBehaviour,
-    input1: InputBehaviour,
-    modify_flags: bool,
-    output: OutputBehaviour,
-    command: Command,
-    reserved: u1 = 0,
-
-    pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
-        try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
-        try out.writeAll(switch (instr.condition) {
-            .always => "    ",
-            .when_zero => "== 0",
-            .not_zero => "!= 0",
-            .greater_zero => " > 0",
-            .less_than_zero => " < 0",
-            .greater_or_equal_zero => ">= 0",
-            .less_or_equal_zero => "<= 0",
-            .overflow => "ovfl",
-        });
-        try out.writeAll(" ");
-        try out.writeAll(switch (instr.input0) {
-            .zero => "zero",
-            .immediate => "imm ",
-            .peek => "peek",
-            .pop => "pop ",
-        });
-        try out.writeAll(" ");
-        try out.writeAll(switch (instr.input1) {
-            .zero => "zero",
-            .immediate => "imm ",
-            .peek => "peek",
-            .pop => "pop ",
-        });
-        try out.writeAll(" ");
-        try out.writeAll(switch (instr.command) {
-            .copy => "copy     ",
-            .ipget => "ipget    ",
-            .get => "get      ",
-            .set => "set      ",
-            .store8 => "store8   ",
-            .store16 => "store16  ",
-            .load8 => "load8    ",
-            .load16 => "load16   ",
-            .undefined0 => "undefined",
-            .undefined1 => "undefined",
-            .frget => "frget    ",
-            .frset => "frset    ",
-            .bpget => "bpget    ",
-            .bpset => "bpset    ",
-            .spget => "spget    ",
-            .spset => "spset    ",
-            .add => "add      ",
-            .sub => "sub      ",
-            .mul => "mul      ",
-            .div => "div      ",
-            .mod => "mod      ",
-            .@"and" => "and      ",
-            .@"or" => "or       ",
-            .xor => "xor      ",
-            .not => "not      ",
-            .signext => "signext  ",
-            .rol => "rol      ",
-            .ror => "ror      ",
-            .bswap => "bswap    ",
-            .asr => "asr      ",
-            .lsl => "lsl      ",
-            .lsr => "lsr      ",
-        });
-        try out.writeAll(" ");
-        try out.writeAll(switch (instr.output) {
-            .discard => "discard",
-            .push => "push   ",
-            .jump => "jmp    ",
-            .jump_relative => "rjmp   ",
-        });
-        try out.writeAll(" ");
-        try out.writeAll(if (instr.modify_flags)
-            "+ flags"
-        else
-            "       ");
-    }
-};
-
-pub const FlagRegister = packed struct {
-    zero: bool,
-    negative: bool,
-    carry: bool,
-    carry_enabled: bool,
-    interrupt0_enabled: bool,
-    interrupt1_enabled: bool,
-    interrupt2_enabled: bool,
-    interrupt3_enabled: bool,
-    reserved: u8 = 0,
-};
lib/std/spu.zig
@@ -1,3 +0,0 @@
-pub usingnamespace @import("spu/defines.zig");
-
-pub const interpreter = @import("spu/interpreter.zig").Interpreter;
lib/std/std.zig
@@ -80,9 +80,6 @@ pub const valgrind = @import("valgrind.zig");
 pub const zig = @import("zig.zig");
 pub const start = @import("start.zig");
 
-// TODO move this
-pub const spu = @import("spu.zig");
-
 // This forces the start.zig file to be imported, and the comptime logic inside that
 // file decides whether to export any appropriate start symbols.
 comptime {
lib/std/target.zig
@@ -663,7 +663,8 @@ pub const Target = struct {
             renderscript32,
             renderscript64,
             ve,
-            // Non-LLVM targets go here
+            // Stage1 currently assumes that architectures above this comment
+            // map one-to-one with the ZigLLVM_ArchType enum.
             spu_2,
 
             pub fn isARM(arch: Arch) bool {
lib/std/spu/interpreter.zig → src-self-hosted/codegen/spu-mk2/interpreter.zig
@@ -1,6 +1,9 @@
 const std = @import("std");
 const log = std.log.scoped(.SPU_2_Interpreter);
-usingnamespace @import("defines.zig");
+const spu = @import("../spu-mk2.zig");
+const FlagRegister = spu.FlagRegister;
+const Instruction = spu.Instruction;
+const ExecutionCondition = spu.ExecutionCondition;
 
 pub fn Interpreter(comptime Bus: type) type {
     return struct {
@@ -32,7 +35,7 @@ pub fn Interpreter(comptime Bus: type) type {
                     .when_zero => self.fr.zero,
                     .overflow => self.fr.carry,
                     ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
-                    else => return error.unimplemented,
+                    else => return error.Unimplemented,
                 };
 
                 if (execute) {
@@ -134,7 +137,7 @@ pub fn Interpreter(comptime Bus: type) type {
                             (val0 & 0xFF) | 0xFF00
                         else
                             (val0 & 0xFF),
-                        else => return error.unimplemented,
+                        else => return error.Unimplemented,
                     };
 
                     switch (instruction.output) {
@@ -146,7 +149,7 @@ pub fn Interpreter(comptime Bus: type) type {
                         .jump => {
                             self.ip = output;
                         },
-                        else => return error.unimplemented,
+                        else => return error.Unimplemented,
                     }
                     if (instruction.modify_flags) {
                         self.fr.negative = (output & 0x8000) != 0;
src-self-hosted/codegen/spu-mk2.zig
@@ -1,4 +1,163 @@
-pub usingnamespace @import("std").spu;
+const std = @import("std");
+
+pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter;
+
+pub const ExecutionCondition = enum(u3) {
+    always = 0,
+    when_zero = 1,
+    not_zero = 2,
+    greater_zero = 3,
+    less_than_zero = 4,
+    greater_or_equal_zero = 5,
+    less_or_equal_zero = 6,
+    overflow = 7,
+};
+
+pub const InputBehaviour = enum(u2) {
+    zero = 0,
+    immediate = 1,
+    peek = 2,
+    pop = 3,
+};
+
+pub const OutputBehaviour = enum(u2) {
+    discard = 0,
+    push = 1,
+    jump = 2,
+    jump_relative = 3,
+};
+
+pub const Command = enum(u5) {
+    copy = 0,
+    ipget = 1,
+    get = 2,
+    set = 3,
+    store8 = 4,
+    store16 = 5,
+    load8 = 6,
+    load16 = 7,
+    undefined0 = 8,
+    undefined1 = 9,
+    frget = 10,
+    frset = 11,
+    bpget = 12,
+    bpset = 13,
+    spget = 14,
+    spset = 15,
+    add = 16,
+    sub = 17,
+    mul = 18,
+    div = 19,
+    mod = 20,
+    @"and" = 21,
+    @"or" = 22,
+    xor = 23,
+    not = 24,
+    signext = 25,
+    rol = 26,
+    ror = 27,
+    bswap = 28,
+    asr = 29,
+    lsl = 30,
+    lsr = 31,
+};
+
+pub const Instruction = packed struct {
+    condition: ExecutionCondition,
+    input0: InputBehaviour,
+    input1: InputBehaviour,
+    modify_flags: bool,
+    output: OutputBehaviour,
+    command: Command,
+    reserved: u1 = 0,
+
+    pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
+        try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
+        try out.writeAll(switch (instr.condition) {
+            .always => "    ",
+            .when_zero => "== 0",
+            .not_zero => "!= 0",
+            .greater_zero => " > 0",
+            .less_than_zero => " < 0",
+            .greater_or_equal_zero => ">= 0",
+            .less_or_equal_zero => "<= 0",
+            .overflow => "ovfl",
+        });
+        try out.writeAll(" ");
+        try out.writeAll(switch (instr.input0) {
+            .zero => "zero",
+            .immediate => "imm ",
+            .peek => "peek",
+            .pop => "pop ",
+        });
+        try out.writeAll(" ");
+        try out.writeAll(switch (instr.input1) {
+            .zero => "zero",
+            .immediate => "imm ",
+            .peek => "peek",
+            .pop => "pop ",
+        });
+        try out.writeAll(" ");
+        try out.writeAll(switch (instr.command) {
+            .copy => "copy     ",
+            .ipget => "ipget    ",
+            .get => "get      ",
+            .set => "set      ",
+            .store8 => "store8   ",
+            .store16 => "store16  ",
+            .load8 => "load8    ",
+            .load16 => "load16   ",
+            .undefined0 => "undefined",
+            .undefined1 => "undefined",
+            .frget => "frget    ",
+            .frset => "frset    ",
+            .bpget => "bpget    ",
+            .bpset => "bpset    ",
+            .spget => "spget    ",
+            .spset => "spset    ",
+            .add => "add      ",
+            .sub => "sub      ",
+            .mul => "mul      ",
+            .div => "div      ",
+            .mod => "mod      ",
+            .@"and" => "and      ",
+            .@"or" => "or       ",
+            .xor => "xor      ",
+            .not => "not      ",
+            .signext => "signext  ",
+            .rol => "rol      ",
+            .ror => "ror      ",
+            .bswap => "bswap    ",
+            .asr => "asr      ",
+            .lsl => "lsl      ",
+            .lsr => "lsr      ",
+        });
+        try out.writeAll(" ");
+        try out.writeAll(switch (instr.output) {
+            .discard => "discard",
+            .push => "push   ",
+            .jump => "jmp    ",
+            .jump_relative => "rjmp   ",
+        });
+        try out.writeAll(" ");
+        try out.writeAll(if (instr.modify_flags)
+            "+ flags"
+        else
+            "       ");
+    }
+};
+
+pub const FlagRegister = packed struct {
+    zero: bool,
+    negative: bool,
+    carry: bool,
+    carry_enabled: bool,
+    interrupt0_enabled: bool,
+    interrupt1_enabled: bool,
+    interrupt2_enabled: bool,
+    interrupt3_enabled: bool,
+    reserved: u8 = 0,
+};
 
 pub const Register = enum {
     dummy,
src-self-hosted/link/Elf.zig
@@ -14,12 +14,10 @@ const leb128 = std.debug.leb;
 const Package = @import("../Package.zig");
 const Value = @import("../value.zig").Value;
 const Type = @import("../type.zig").Type;
-const build_options = @import("build_options");
 const link = @import("../link.zig");
 const File = link.File;
 const Elf = @This();
 
-const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
 const default_entry_addr = 0x8000000;
 
 // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
@@ -249,8 +247,8 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {
             .allocator = allocator,
         },
         .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
-            16, 32 => .p32,
-            64 => .p64,
+            0 ... 32 => .p32,
+            33 ... 64 => .p64,
             else => return error.UnsupportedELFArchitecture,
         },
     };
@@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf
             .file = file,
         },
         .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
-            16, 32 => .p32,
-            64 => .p64,
+            0 ... 32 => .p32,
+            33 ... 64 => .p64,
             else => return error.UnsupportedELFArchitecture,
         },
         .shdr_table_dirty = true,
@@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 {
 
 /// Returns end pos of collision, if any.
 fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
-    const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;
+    const small_ptr = self.ptr_width == .p32;
     const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
     if (start < ehdr_size)
         return ehdr_size;
@@ -487,7 +485,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
         // we'll need to re-use that function anyway, in case the GOT grows and overlaps something
         // else in virtual memory.
-        const got_addr = if (self.base.options.target.cpu.arch.ptrBitWidth() == 16) @as(u32, 0x8000) else 0x4000000;
+        const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000;
         try self.program_headers.append(self.base.allocator, .{
             .p_type = elf.PT_LOAD,
             .p_offset = off,
@@ -864,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void {
         // Write the form for the compile unit, which must match the abbrev table above.
         const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);
         const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);
-        const producer_strp = try self.makeDebugString(producer_string);
+        const producer_strp = try self.makeDebugString(link.producer_string);
         // Currently only one compilation unit is supported, so the address range is simply
         // identical to the main program header virtual address and memory size.
         const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
@@ -2152,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void {
 fn writeProgHeader(self: *Elf, index: usize) !void {
     const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
     const offset = self.program_headers.items[index].p_offset;
-    switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
-        32 => {
+    switch (self.ptr_width) {
+        .p32 => {
             var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
             if (foreign_endian) {
                 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
             }
             return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
         },
-        64 => {
+        .p64 => {
             var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};
             if (foreign_endian) {
                 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
             }
             return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
         },
-        else => return error.UnsupportedArchitecture,
     }
 }
 
 fn writeSectHeader(self: *Elf, index: usize) !void {
     const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
-    switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
-        32 => {
+    switch (self.ptr_width) {
+        .p32 => {
             var shdr: [1]elf.Elf32_Shdr = undefined;
             shdr[0] = sectHeaderTo32(self.sections.items[index]);
             if (foreign_endian) {
@@ -2183,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
             const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);
             return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
         },
-        64 => {
+        .p64 => {
             var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};
             if (foreign_endian) {
                 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
@@ -2191,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
             const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);
             return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
         },
-        else => return error.UnsupportedArchitecture,
     }
 }
 
 fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
     const shdr = &self.sections.items[self.got_section_index.?];
     const phdr = &self.program_headers.items[self.phdr_got_index.?];
-    const entry_size: u16 = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
+    const entry_size: u16 = self.archPtrWidthBytes();
     if (self.offset_table_count_dirty) {
         // TODO Also detect virtual address collisions.
         const allocated_size = self.allocatedSize(shdr.sh_offset);
@@ -2351,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {
     }
 }
 
+/// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF.
 fn ptrWidthBytes(self: Elf) u8 {
     return switch (self.ptr_width) {
         .p32 => 4,
@@ -2358,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 {
     };
 }
 
+/// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes
+/// in a 32-bit ELF file.
+fn archPtrWidthBytes(self: Elf) u8 {
+    return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8);
+}
+
 /// The reloc offset for the virtual address of a function in its Line Number Program.
 /// Size is a virtual address integer.
 const dbg_line_vaddr_reloc_index = 3;
src-self-hosted/link.zig
@@ -7,7 +7,7 @@ const Package = @import("Package.zig");
 const Type = @import("type.zig").Type;
 const build_options = @import("build_options");
 
-const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
+pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
 
 pub const Options = struct {
     target: std.Target,
src-self-hosted/test.zig
@@ -571,95 +571,6 @@ pub const TestContext = struct {
                     std.debug.assert(!case.cbe);
 
                     update_node.estimated_total_items = 4;
-                    if (case.target.cpu_arch) |arch| {
-                        if (arch == .spu_2) {
-                            if (case.target.os_tag) |os| {
-                                if (os != .freestanding) {
-                                    std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
-                                }
-                            } else {
-                                std.debug.panic("SPU_2 has no native OS, check the test!", .{});
-                            }
-
-                            var interpreter = std.spu.interpreter(struct {
-                                    RAM: [0x10000]u8 = undefined,
-
-                                    pub fn read8(bus: @This(), addr: u16) u8 {
-                                        return bus.RAM[addr];
-                                    }
-                                    pub fn read16(bus: @This(), addr: u16) u16 {
-                                        return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
-                                    }
-
-                                    pub fn write8(bus: *@This(), addr: u16, val: u8) void {
-                                        bus.RAM[addr] = val;
-                                    }
-
-                                    pub fn write16(bus: *@This(), addr: u16, val: u16) void {
-                                        std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
-                                    }
-                                }){
-                                .bus = .{},
-                            };
-
-                            {
-                                var load_node = update_node.start("load", null);
-                                load_node.activate();
-                                defer load_node.end();
-
-                                var file = try tmp.dir.openFile(bin_name, .{ .read = true });
-                                defer file.close();
-
-                                const header = try std.elf.readHeader(file);
-                                var iterator = header.program_header_iterator(file);
-
-                                var none_loaded = true;
-
-                                while (try iterator.next()) |phdr| {
-                                    if (phdr.p_type != std.elf.PT_LOAD) {
-                                        std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type});
-                                        std.process.exit(1);
-                                    }
-                                    if (phdr.p_paddr != phdr.p_vaddr) {
-                                        std.debug.print("Physical address does not match virtual address in ELF header!\n", .{});
-                                        std.process.exit(1);
-                                    }
-                                    if (phdr.p_filesz != phdr.p_memsz) {
-                                        std.debug.print("Physical size does not match virtual size in ELF header!\n", .{});
-                                        std.process.exit(1);
-                                    }
-                                    if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) {
-                                        std.debug.print("Read less than expected from ELF file!", .{});
-                                        std.process.exit(1);
-                                    }
-                                    std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr });
-                                    none_loaded = false;
-                                }
-                                if (none_loaded) {
-                                    std.debug.print("No data found in ELF file!\n", .{});
-                                    std.process.exit(1);
-                                }
-                            }
-
-                            var exec_node = update_node.start("execute", null);
-                            exec_node.activate();
-                            defer exec_node.end();
-
-                            var blocks: u16 = 1000;
-                            const block_size = 1000;
-                            while (!interpreter.undefined0) {
-                                const pre_ip = interpreter.ip;
-                                if (blocks > 0) {
-                                    blocks -= 1;
-                                    try interpreter.ExecuteBlock(block_size);
-                                    if (pre_ip == interpreter.ip) {
-                                        std.debug.print("Infinite loop detected in SPU II test!\n", .{});
-                                        std.process.exit(1);
-                                    }
-                                }
-                            }
-                        }
-                    }
                     var exec_result = x: {
                         var exec_node = update_node.start("execute", null);
                         exec_node.activate();
@@ -672,7 +583,10 @@ pub const TestContext = struct {
 
                         switch (case.target.getExternalExecutor()) {
                             .native => try argv.append(exe_path),
-                            .unavailable => return, // No executor available; pass test.
+                            .unavailable => {
+                                try self.runInterpreterIfAvailable(allocator, &exec_node, case, tmp.dir, bin_name);
+                                return; // Pass test.
+                            },
 
                             .qemu => |qemu_bin_name| if (enable_qemu) {
                                 // TODO Ability for test cases to specify whether to link libc.
@@ -745,4 +659,115 @@ pub const TestContext = struct {
             }
         }
     }
+
+    fn runInterpreterIfAvailable(
+        self: *TestContext,
+        gpa: *Allocator,
+        node: *std.Progress.Node,
+        case: Case,
+        tmp_dir: std.fs.Dir,
+        bin_name: []const u8,
+    ) !void {
+        const arch = case.target.cpu_arch orelse return;
+        switch (arch) {
+            .spu_2 => return self.runSpu2Interpreter(gpa, node, case, tmp_dir, bin_name),
+            else => return,
+        }
+    }
+
+    fn runSpu2Interpreter(
+        self: *TestContext,
+        gpa: *Allocator,
+        update_node: *std.Progress.Node,
+        case: Case,
+        tmp_dir: std.fs.Dir,
+        bin_name: []const u8,
+    ) !void {
+        const spu = @import("codegen/spu-mk2.zig");
+        if (case.target.os_tag) |os| {
+            if (os != .freestanding) {
+                std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
+            }
+        } else {
+            std.debug.panic("SPU_2 has no native OS, check the test!", .{});
+        }
+
+        var interpreter = spu.Interpreter(struct {
+                RAM: [0x10000]u8 = undefined,
+
+                pub fn read8(bus: @This(), addr: u16) u8 {
+                    return bus.RAM[addr];
+                }
+                pub fn read16(bus: @This(), addr: u16) u16 {
+                    return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
+                }
+
+                pub fn write8(bus: *@This(), addr: u16, val: u8) void {
+                    bus.RAM[addr] = val;
+                }
+
+                pub fn write16(bus: *@This(), addr: u16, val: u16) void {
+                    std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
+                }
+            }){
+            .bus = .{},
+        };
+
+        {
+            var load_node = update_node.start("load", null);
+            load_node.activate();
+            defer load_node.end();
+
+            var file = try tmp_dir.openFile(bin_name, .{ .read = true });
+            defer file.close();
+
+            const header = try std.elf.readHeader(file);
+            var iterator = header.program_header_iterator(file);
+
+            var none_loaded = true;
+
+            while (try iterator.next()) |phdr| {
+                if (phdr.p_type != std.elf.PT_LOAD) {
+                    std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type});
+                    std.process.exit(1);
+                }
+                if (phdr.p_paddr != phdr.p_vaddr) {
+                    std.debug.print("Physical address does not match virtual address in ELF header!\n", .{});
+                    std.process.exit(1);
+                }
+                if (phdr.p_filesz != phdr.p_memsz) {
+                    std.debug.print("Physical size does not match virtual size in ELF header!\n", .{});
+                    std.process.exit(1);
+                }
+                if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) {
+                    std.debug.print("Read less than expected from ELF file!", .{});
+                    std.process.exit(1);
+                }
+                std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr });
+                none_loaded = false;
+            }
+            if (none_loaded) {
+                std.debug.print("No data found in ELF file!\n", .{});
+                std.process.exit(1);
+            }
+        }
+
+        var exec_node = update_node.start("execute", null);
+        exec_node.activate();
+        defer exec_node.end();
+
+        var blocks: u16 = 1000;
+        const block_size = 1000;
+        while (!interpreter.undefined0) {
+            const pre_ip = interpreter.ip;
+            if (blocks > 0) {
+                blocks -= 1;
+                try interpreter.ExecuteBlock(block_size);
+                if (pre_ip == interpreter.ip) {
+                    std.debug.print("Infinite loop detected in SPU II test!\n", .{});
+                    std.process.exit(1);
+                }
+            }
+        }
+    }
 };
test/stage2/test.zig
@@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{
 pub fn addCases(ctx: *TestContext) !void {
     try @import("zir.zig").addCases(ctx);
     try @import("cbe.zig").addCases(ctx);
+    try @import("spu-ii.zig").addCases(ctx);
+
     {
         var case = ctx.exe("hello world with updates", linux_x64);
 
@@ -785,5 +787,4 @@ pub fn addCases(ctx: *TestContext) !void {
         \\}
         \\extern var foo;
     , &[_][]const u8{":4:1: error: unable to infer variable type"});
-    try @import("spu-ii.zig").addCases(ctx);
 }