authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-22 13:38:54-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-22 13:38:54-07:00
log4e63cae369ec6e333e4aecb3d749bedff6484ee3
tree7f428fe750a9248cad7db55f8d889a02e474869e
parent29051a0674800881957479c423a1feb043391671
parent54f3b0a560f17effbb582f86ebf83c53916fc697

Merge branch 'pixelherodev-spu_ii'

closes #6096

10 files changed, 586 insertions(+), 29 deletions(-)

lib/std/elf.zig+3
...@@ -976,6 +976,9 @@ pub const EM = extern enum(u16) {...@@ -976,6 +976,9 @@ pub const EM = extern enum(u16) {
976 /// MIPS RS3000 Little-endian976 /// MIPS RS3000 Little-endian
977 _MIPS_RS3_LE = 10,977 _MIPS_RS3_LE = 10,
978978
979 /// SPU Mark II
980 _SPU_2 = 13,
981
979 /// Hewlett-Packard PA-RISC982 /// Hewlett-Packard PA-RISC
980 _PARISC = 15,983 _PARISC = 15,
981984
lib/std/target.zig+8-1
...@@ -663,6 +663,9 @@ pub const Target = struct {...@@ -663,6 +663,9 @@ pub const Target = struct {
663 renderscript32,663 renderscript32,
664 renderscript64,664 renderscript64,
665 ve,665 ve,
666 // Stage1 currently assumes that architectures above this comment
667 // map one-to-one with the ZigLLVM_ArchType enum.
668 spu_2,
666669
667 pub fn isARM(arch: Arch) bool {670 pub fn isARM(arch: Arch) bool {
668 return switch (arch) {671 return switch (arch) {
...@@ -761,6 +764,7 @@ pub const Target = struct {...@@ -761,6 +764,7 @@ pub const Target = struct {
761 .sparcv9 => ._SPARCV9,764 .sparcv9 => ._SPARCV9,
762 .s390x => ._S390,765 .s390x => ._S390,
763 .ve => ._NONE,766 .ve => ._NONE,
767 .spu_2 => ._SPU_2,
764 };768 };
765 }769 }
766770
...@@ -803,6 +807,7 @@ pub const Target = struct {...@@ -803,6 +807,7 @@ pub const Target = struct {
803 .renderscript64,807 .renderscript64,
804 .shave,808 .shave,
805 .ve,809 .ve,
810 .spu_2,
806 => .Little,811 => .Little,
807812
808 .arc,813 .arc,
...@@ -827,6 +832,7 @@ pub const Target = struct {...@@ -827,6 +832,7 @@ pub const Target = struct {
827 switch (arch) {832 switch (arch) {
828 .avr,833 .avr,
829 .msp430,834 .msp430,
835 .spu_2,
830 => return 16,836 => return 16,
831837
832 .arc,838 .arc,
...@@ -1317,12 +1323,13 @@ pub const Target = struct {...@@ -1317,12 +1323,13 @@ pub const Target = struct {
1317 .bpfeb,1323 .bpfeb,
1318 .nvptx,1324 .nvptx,
1319 .nvptx64,1325 .nvptx64,
1326 .spu_2,
1327 .avr,
1320 => return result,1328 => return result,
13211329
1322 // TODO go over each item in this list and either move it to the above list, or1330 // TODO go over each item in this list and either move it to the above list, or
1323 // implement the standard dynamic linker path code for it.1331 // implement the standard dynamic linker path code for it.
1324 .arc,1332 .arc,
1325 .avr,
1326 .hexagon,1333 .hexagon,
1327 .msp430,1334 .msp430,
1328 .r600,1335 .r600,
src-self-hosted/codegen.zig+60-1
...@@ -102,6 +102,7 @@ pub fn generateSymbol(...@@ -102,6 +102,7 @@ pub fn generateSymbol(
102 //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),102 //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
103 //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),103 //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
104 //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),104 //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
105 .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
105 //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),106 //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
106 //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),107 //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
107 //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),108 //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs),
...@@ -1264,6 +1265,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1264,6 +1265,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1264 .riscv64 => {1265 .riscv64 => {
1265 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32());1266 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32());
1266 },1267 },
1268 .spu_2 => {
1269 try self.code.resize(self.code.items.len + 2);
1270 var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined1 };
1271 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr));
1272 },
1267 else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),1273 else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
1268 }1274 }
1269 return .none;1275 return .none;
...@@ -1349,6 +1355,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1349,6 +1355,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1349 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});1355 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
1350 }1356 }
1351 },1357 },
1358 .spu_2 => {
1359 if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
1360 if (info.args.len != 0) {
1361 return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});
1362 }
1363 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
1364 const func = func_val.func;
1365 const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
1366 const got_addr = @intCast(u16, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * 2);
1367 const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType();
1368 // First, push the return address, then jump; if noreturn, don't bother with the first step
1369 // TODO: implement packed struct -> u16 at comptime and move the bitcast here
1370 var instr = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .jump, .command = .load16 };
1371 if (return_type.zigTypeTag() == .NoReturn) {
1372 try self.code.resize(self.code.items.len + 4);
1373 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr));
1374 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr);
1375 return MCValue.unreach;
1376 } else {
1377 try self.code.resize(self.code.items.len + 8);
1378 var push = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .push, .command = .ipget };
1379 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 8 ..][0..2], @bitCast(u16, push));
1380 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 6 ..][0..2], @as(u16, 4));
1381 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr));
1382 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr);
1383 switch (return_type.zigTypeTag()) {
1384 .Void => return MCValue{ .none = {} },
1385 .NoReturn => unreachable,
1386 else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
1387 }
1388 }
1389 } else {
1390 return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
1391 }
1392 } else {
1393 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
1394 }
1395 },
1352 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),1396 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
1353 }1397 }
1354 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {1398 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
...@@ -1642,6 +1686,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1642,6 +1686,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1642 if (!inst.is_volatile and inst.base.isUnused())1686 if (!inst.is_volatile and inst.base.isUnused())
1643 return MCValue.dead;1687 return MCValue.dead;
1644 switch (arch) {1688 switch (arch) {
1689 .spu_2 => {
1690 if (inst.inputs.len > 0 or inst.output != null) {
1691 return self.fail(inst.base.src, "TODO implement inline asm inputs / outputs for SPU Mark II", .{});
1692 }
1693 if (mem.eql(u8, inst.asm_source, "undefined0")) {
1694 try self.code.resize(self.code.items.len + 2);
1695 var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined0 };
1696 mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr));
1697 return MCValue.none;
1698 } else {
1699 return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{});
1700 }
1701 },
1645 .riscv64 => {1702 .riscv64 => {
1646 for (inst.inputs) |input, i| {1703 for (inst.inputs) |input, i| {
1647 if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {1704 if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
...@@ -1719,6 +1776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1719,6 +1776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1719 /// X => extension to the SIB.index field1776 /// X => extension to the SIB.index field
1720 /// B => extension to the MODRM.rm field or the SIB.base field1777 /// B => extension to the MODRM.rm field or the SIB.base field
1721 fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void {1778 fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void {
1779 comptime assert(arch == .x86_64);
1722 // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB.1780 // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB.
1723 var value: u8 = 0x40;1781 var value: u8 = 0x40;
1724 if (arg.b) {1782 if (arg.b) {
...@@ -2266,7 +2324,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2266,7 +2324,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2266 result.stack_byte_count = next_stack_offset;2324 result.stack_byte_count = next_stack_offset;
2267 result.stack_align = 16;2325 result.stack_align = 16;
2268 },2326 },
2269 else => return self.fail(src, "TODO implement function parameters for {}", .{cc}),2327 else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}),
2270 }2328 }
2271 },2329 },
2272 else => if (param_types.len != 0)2330 else => if (param_types.len != 0)
...@@ -2313,6 +2371,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2313,6 +2371,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2313 .i386 => @import("codegen/x86.zig"),2371 .i386 => @import("codegen/x86.zig"),
2314 .x86_64 => @import("codegen/x86_64.zig"),2372 .x86_64 => @import("codegen/x86_64.zig"),
2315 .riscv64 => @import("codegen/riscv64.zig"),2373 .riscv64 => @import("codegen/riscv64.zig"),
2374 .spu_2 => @import("codegen/spu-mk2.zig"),
2316 else => struct {2375 else => struct {
2317 pub const Register = enum {2376 pub const Register = enum {
2318 dummy,2377 dummy,
src-self-hosted/codegen/spu-mk2.zig created+170
...@@ -0,0 +1,170 @@
1const std = @import("std");
2
3pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter;
4
5pub const ExecutionCondition = enum(u3) {
6 always = 0,
7 when_zero = 1,
8 not_zero = 2,
9 greater_zero = 3,
10 less_than_zero = 4,
11 greater_or_equal_zero = 5,
12 less_or_equal_zero = 6,
13 overflow = 7,
14};
15
16pub const InputBehaviour = enum(u2) {
17 zero = 0,
18 immediate = 1,
19 peek = 2,
20 pop = 3,
21};
22
23pub const OutputBehaviour = enum(u2) {
24 discard = 0,
25 push = 1,
26 jump = 2,
27 jump_relative = 3,
28};
29
30pub const Command = enum(u5) {
31 copy = 0,
32 ipget = 1,
33 get = 2,
34 set = 3,
35 store8 = 4,
36 store16 = 5,
37 load8 = 6,
38 load16 = 7,
39 undefined0 = 8,
40 undefined1 = 9,
41 frget = 10,
42 frset = 11,
43 bpget = 12,
44 bpset = 13,
45 spget = 14,
46 spset = 15,
47 add = 16,
48 sub = 17,
49 mul = 18,
50 div = 19,
51 mod = 20,
52 @"and" = 21,
53 @"or" = 22,
54 xor = 23,
55 not = 24,
56 signext = 25,
57 rol = 26,
58 ror = 27,
59 bswap = 28,
60 asr = 29,
61 lsl = 30,
62 lsr = 31,
63};
64
65pub const Instruction = packed struct {
66 condition: ExecutionCondition,
67 input0: InputBehaviour,
68 input1: InputBehaviour,
69 modify_flags: bool,
70 output: OutputBehaviour,
71 command: Command,
72 reserved: u1 = 0,
73
74 pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void {
75 try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)});
76 try out.writeAll(switch (instr.condition) {
77 .always => " ",
78 .when_zero => "== 0",
79 .not_zero => "!= 0",
80 .greater_zero => " > 0",
81 .less_than_zero => " < 0",
82 .greater_or_equal_zero => ">= 0",
83 .less_or_equal_zero => "<= 0",
84 .overflow => "ovfl",
85 });
86 try out.writeAll(" ");
87 try out.writeAll(switch (instr.input0) {
88 .zero => "zero",
89 .immediate => "imm ",
90 .peek => "peek",
91 .pop => "pop ",
92 });
93 try out.writeAll(" ");
94 try out.writeAll(switch (instr.input1) {
95 .zero => "zero",
96 .immediate => "imm ",
97 .peek => "peek",
98 .pop => "pop ",
99 });
100 try out.writeAll(" ");
101 try out.writeAll(switch (instr.command) {
102 .copy => "copy ",
103 .ipget => "ipget ",
104 .get => "get ",
105 .set => "set ",
106 .store8 => "store8 ",
107 .store16 => "store16 ",
108 .load8 => "load8 ",
109 .load16 => "load16 ",
110 .undefined0 => "undefined",
111 .undefined1 => "undefined",
112 .frget => "frget ",
113 .frset => "frset ",
114 .bpget => "bpget ",
115 .bpset => "bpset ",
116 .spget => "spget ",
117 .spset => "spset ",
118 .add => "add ",
119 .sub => "sub ",
120 .mul => "mul ",
121 .div => "div ",
122 .mod => "mod ",
123 .@"and" => "and ",
124 .@"or" => "or ",
125 .xor => "xor ",
126 .not => "not ",
127 .signext => "signext ",
128 .rol => "rol ",
129 .ror => "ror ",
130 .bswap => "bswap ",
131 .asr => "asr ",
132 .lsl => "lsl ",
133 .lsr => "lsr ",
134 });
135 try out.writeAll(" ");
136 try out.writeAll(switch (instr.output) {
137 .discard => "discard",
138 .push => "push ",
139 .jump => "jmp ",
140 .jump_relative => "rjmp ",
141 });
142 try out.writeAll(" ");
143 try out.writeAll(if (instr.modify_flags)
144 "+ flags"
145 else
146 " ");
147 }
148};
149
150pub const FlagRegister = packed struct {
151 zero: bool,
152 negative: bool,
153 carry: bool,
154 carry_enabled: bool,
155 interrupt0_enabled: bool,
156 interrupt1_enabled: bool,
157 interrupt2_enabled: bool,
158 interrupt3_enabled: bool,
159 reserved: u8 = 0,
160};
161
162pub const Register = enum {
163 dummy,
164
165 pub fn allocIndex(self: Register) ?u4 {
166 return null;
167 }
168};
169
170pub const callee_preserved_regs = [_]Register{};
src-self-hosted/codegen/spu-mk2/interpreter.zig created+166
...@@ -0,0 +1,166 @@
1const std = @import("std");
2const log = std.log.scoped(.SPU_2_Interpreter);
3const spu = @import("../spu-mk2.zig");
4const FlagRegister = spu.FlagRegister;
5const Instruction = spu.Instruction;
6const ExecutionCondition = spu.ExecutionCondition;
7
8pub fn Interpreter(comptime Bus: type) type {
9 return struct {
10 ip: u16 = 0,
11 sp: u16 = undefined,
12 bp: u16 = undefined,
13 fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)),
14 /// This is set to true when we hit an undefined0 instruction, allowing it to
15 /// be used as a trap for testing purposes
16 undefined0: bool = false,
17 /// This is set to true when we hit an undefined1 instruction, allowing it to
18 /// be used as a trap for testing purposes. undefined1 is used as a breakpoint.
19 undefined1: bool = false,
20 bus: Bus,
21
22 pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void {
23 var count: usize = 0;
24 while (size == null or count < size.?) {
25 count += 1;
26 var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
27
28 log.debug("Executing {}\n", .{instruction});
29
30 self.ip +%= 2;
31
32 const execute = switch (instruction.condition) {
33 .always => true,
34 .not_zero => !self.fr.zero,
35 .when_zero => self.fr.zero,
36 .overflow => self.fr.carry,
37 ExecutionCondition.greater_or_equal_zero => !self.fr.negative,
38 else => return error.Unimplemented,
39 };
40
41 if (execute) {
42 const val0 = switch (instruction.input0) {
43 .zero => @as(u16, 0),
44 .immediate => i: {
45 const val = self.bus.read16(@intCast(u16, self.ip));
46 self.ip +%= 2;
47 break :i val;
48 },
49 else => |e| e: {
50 // peek or pop; show value at current SP, and if pop, increment sp
51 const val = self.bus.read16(self.sp);
52 if (e == .pop) {
53 self.sp +%= 2;
54 }
55 break :e val;
56 },
57 };
58 const val1 = switch (instruction.input1) {
59 .zero => @as(u16, 0),
60 .immediate => i: {
61 const val = self.bus.read16(@intCast(u16, self.ip));
62 self.ip +%= 2;
63 break :i val;
64 },
65 else => |e| e: {
66 // peek or pop; show value at current SP, and if pop, increment sp
67 const val = self.bus.read16(self.sp);
68 if (e == .pop) {
69 self.sp +%= 2;
70 }
71 break :e val;
72 },
73 };
74
75 const output: u16 = switch (instruction.command) {
76 .get => self.bus.read16(self.bp +% (2 *% val0)),
77 .set => a: {
78 self.bus.write16(self.bp +% 2 *% val0, val1);
79 break :a val1;
80 },
81 .load8 => self.bus.read8(val0),
82 .load16 => self.bus.read16(val0),
83 .store8 => a: {
84 const val = @truncate(u8, val1);
85 self.bus.write8(val0, val);
86 break :a val;
87 },
88 .store16 => a: {
89 self.bus.write16(val0, val1);
90 break :a val1;
91 },
92 .copy => val0,
93 .add => a: {
94 var val: u16 = undefined;
95 self.fr.carry = @addWithOverflow(u16, val0, val1, &val);
96 break :a val;
97 },
98 .sub => a: {
99 var val: u16 = undefined;
100 self.fr.carry = @subWithOverflow(u16, val0, val1, &val);
101 break :a val;
102 },
103 .spset => a: {
104 self.sp = val0;
105 break :a val0;
106 },
107 .bpset => a: {
108 self.bp = val0;
109 break :a val0;
110 },
111 .frset => a: {
112 const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1);
113 self.fr = @bitCast(FlagRegister, val);
114 break :a val;
115 },
116 .bswap => (val0 >> 8) | (val0 << 8),
117 .bpget => self.bp,
118 .spget => self.sp,
119 .ipget => self.ip +% (2 *% val0),
120 .lsl => val0 << 1,
121 .lsr => val0 >> 1,
122 .@"and" => val0 & val1,
123 .@"or" => val0 | val1,
124 .xor => val0 ^ val1,
125 .not => ~val0,
126 .undefined0 => {
127 self.undefined0 = true;
128 // Break out of the loop, and let the caller decide what to do
129 return;
130 },
131 .undefined1 => {
132 self.undefined1 = true;
133 // Break out of the loop, and let the caller decide what to do
134 return;
135 },
136 .signext => if ((val0 & 0x80) != 0)
137 (val0 & 0xFF) | 0xFF00
138 else
139 (val0 & 0xFF),
140 else => return error.Unimplemented,
141 };
142
143 switch (instruction.output) {
144 .discard => {},
145 .push => {
146 self.sp -%= 2;
147 self.bus.write16(self.sp, output);
148 },
149 .jump => {
150 self.ip = output;
151 },
152 else => return error.Unimplemented,
153 }
154 if (instruction.modify_flags) {
155 self.fr.negative = (output & 0x8000) != 0;
156 self.fr.zero = (output == 0x0000);
157 }
158 } else {
159 if (instruction.input0 == .immediate) self.ip +%= 2;
160 if (instruction.input1 == .immediate) self.ip +%= 2;
161 break;
162 }
163 }
164 }
165 };
166}
src-self-hosted/link.zig+4
...@@ -5,6 +5,9 @@ const fs = std.fs;...@@ -5,6 +5,9 @@ const fs = std.fs;
5const trace = @import("tracy.zig").trace;5const trace = @import("tracy.zig").trace;
6const Package = @import("Package.zig");6const Package = @import("Package.zig");
7const Type = @import("type.zig").Type;7const Type = @import("type.zig").Type;
8const build_options = @import("build_options");
9
10pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
811
9pub const Options = struct {12pub const Options = struct {
10 target: std.Target,13 target: std.Target,
...@@ -20,6 +23,7 @@ pub const Options = struct {...@@ -20,6 +23,7 @@ pub const Options = struct {
20 /// Used for calculating how much space to reserve for executable program code in case23 /// Used for calculating how much space to reserve for executable program code in case
21 /// the binary file deos not already have such a section.24 /// the binary file deos not already have such a section.
22 program_code_size_hint: u64 = 256 * 1024,25 program_code_size_hint: u64 = 256 * 1024,
26 entry_addr: ?u64 = null,
23};27};
2428
25pub const File = struct {29pub const File = struct {
src-self-hosted/link/Elf.zig+35-25
...@@ -14,12 +14,10 @@ const leb128 = std.debug.leb;...@@ -14,12 +14,10 @@ const leb128 = std.debug.leb;
14const Package = @import("../Package.zig");14const Package = @import("../Package.zig");
15const Value = @import("../value.zig").Value;15const Value = @import("../value.zig").Value;
16const Type = @import("../type.zig").Type;16const Type = @import("../type.zig").Type;
17const build_options = @import("build_options");
18const link = @import("../link.zig");17const link = @import("../link.zig");
19const File = link.File;18const File = link.File;
20const Elf = @This();19const Elf = @This();
2120
22const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
23const default_entry_addr = 0x8000000;21const default_entry_addr = 0x8000000;
2422
25// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.23// 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 {...@@ -249,8 +247,8 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {
249 .allocator = allocator,247 .allocator = allocator,
250 },248 },
251 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {249 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
252 32 => .p32,250 0 ... 32 => .p32,
253 64 => .p64,251 33 ... 64 => .p64,
254 else => return error.UnsupportedELFArchitecture,252 else => return error.UnsupportedELFArchitecture,
255 },253 },
256 };254 };
...@@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf...@@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf
278 .file = file,276 .file = file,
279 },277 },
280 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {278 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
281 32 => .p32,279 0 ... 32 => .p32,
282 64 => .p64,280 33 ... 64 => .p64,
283 else => return error.UnsupportedELFArchitecture,281 else => return error.UnsupportedELFArchitecture,
284 },282 },
285 .shdr_table_dirty = true,283 .shdr_table_dirty = true,
...@@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 {...@@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 {
346344
347/// Returns end pos of collision, if any.345/// Returns end pos of collision, if any.
348fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {346fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
349 const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;347 const small_ptr = self.ptr_width == .p32;
350 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);348 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
351 if (start < ehdr_size)349 if (start < ehdr_size)
352 return ehdr_size;350 return ehdr_size;
...@@ -462,12 +460,13 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -462,12 +460,13 @@ pub fn populateMissingMetadata(self: *Elf) !void {
462 const p_align = 0x1000;460 const p_align = 0x1000;
463 const off = self.findFreeSpace(file_size, p_align);461 const off = self.findFreeSpace(file_size, p_align);
464 log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });462 log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
463 const entry_addr: u64 = self.entry_addr orelse if (self.base.options.target.cpu.arch == .spu_2) @as(u64, 0) else default_entry_addr;
465 try self.program_headers.append(self.base.allocator, .{464 try self.program_headers.append(self.base.allocator, .{
466 .p_type = elf.PT_LOAD,465 .p_type = elf.PT_LOAD,
467 .p_offset = off,466 .p_offset = off,
468 .p_filesz = file_size,467 .p_filesz = file_size,
469 .p_vaddr = default_entry_addr,468 .p_vaddr = entry_addr,
470 .p_paddr = default_entry_addr,469 .p_paddr = entry_addr,
471 .p_memsz = file_size,470 .p_memsz = file_size,
472 .p_align = p_align,471 .p_align = p_align,
473 .p_flags = elf.PF_X | elf.PF_R,472 .p_flags = elf.PF_X | elf.PF_R,
...@@ -486,13 +485,13 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -486,13 +485,13 @@ pub fn populateMissingMetadata(self: *Elf) !void {
486 // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.485 // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
487 // we'll need to re-use that function anyway, in case the GOT grows and overlaps something486 // we'll need to re-use that function anyway, in case the GOT grows and overlaps something
488 // else in virtual memory.487 // else in virtual memory.
489 const default_got_addr = if (ptr_size == 2) @as(u32, 0x8000) else 0x4000000;488 const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000;
490 try self.program_headers.append(self.base.allocator, .{489 try self.program_headers.append(self.base.allocator, .{
491 .p_type = elf.PT_LOAD,490 .p_type = elf.PT_LOAD,
492 .p_offset = off,491 .p_offset = off,
493 .p_filesz = file_size,492 .p_filesz = file_size,
494 .p_vaddr = default_got_addr,493 .p_vaddr = got_addr,
495 .p_paddr = default_got_addr,494 .p_paddr = got_addr,
496 .p_memsz = file_size,495 .p_memsz = file_size,
497 .p_align = p_align,496 .p_align = p_align,
498 .p_flags = elf.PF_R,497 .p_flags = elf.PF_R,
...@@ -863,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void {...@@ -863,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void {
863 // Write the form for the compile unit, which must match the abbrev table above.862 // Write the form for the compile unit, which must match the abbrev table above.
864 const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);863 const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);
865 const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);864 const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);
866 const producer_strp = try self.makeDebugString(producer_string);865 const producer_strp = try self.makeDebugString(link.producer_string);
867 // Currently only one compilation unit is supported, so the address range is simply866 // Currently only one compilation unit is supported, so the address range is simply
868 // identical to the main program header virtual address and memory size.867 // identical to the main program header virtual address and memory size.
869 const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];868 const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
...@@ -2151,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void {...@@ -2151,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void {
2151fn writeProgHeader(self: *Elf, index: usize) !void {2150fn writeProgHeader(self: *Elf, index: usize) !void {
2152 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();2151 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
2153 const offset = self.program_headers.items[index].p_offset;2152 const offset = self.program_headers.items[index].p_offset;
2154 switch (self.base.options.target.cpu.arch.ptrBitWidth()) {2153 switch (self.ptr_width) {
2155 32 => {2154 .p32 => {
2156 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};2155 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
2157 if (foreign_endian) {2156 if (foreign_endian) {
2158 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);2157 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
2159 }2158 }
2160 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);2159 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
2161 },2160 },
2162 64 => {2161 .p64 => {
2163 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};2162 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};
2164 if (foreign_endian) {2163 if (foreign_endian) {
2165 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);2164 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
2166 }2165 }
2167 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);2166 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
2168 },2167 },
2169 else => return error.UnsupportedArchitecture,
2170 }2168 }
2171}2169}
21722170
2173fn writeSectHeader(self: *Elf, index: usize) !void {2171fn writeSectHeader(self: *Elf, index: usize) !void {
2174 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();2172 const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
2175 switch (self.base.options.target.cpu.arch.ptrBitWidth()) {2173 switch (self.ptr_width) {
2176 32 => {2174 .p32 => {
2177 var shdr: [1]elf.Elf32_Shdr = undefined;2175 var shdr: [1]elf.Elf32_Shdr = undefined;
2178 shdr[0] = sectHeaderTo32(self.sections.items[index]);2176 shdr[0] = sectHeaderTo32(self.sections.items[index]);
2179 if (foreign_endian) {2177 if (foreign_endian) {
...@@ -2182,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {...@@ -2182,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
2182 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);2180 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);
2183 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);2181 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
2184 },2182 },
2185 64 => {2183 .p64 => {
2186 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};2184 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};
2187 if (foreign_endian) {2185 if (foreign_endian) {
2188 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);2186 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
...@@ -2190,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void {...@@ -2190,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
2190 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);2188 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);
2191 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);2189 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
2192 },2190 },
2193 else => return error.UnsupportedArchitecture,
2194 }2191 }
2195}2192}
21962193
2197fn writeOffsetTableEntry(self: *Elf, index: usize) !void {2194fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
2198 const shdr = &self.sections.items[self.got_section_index.?];2195 const shdr = &self.sections.items[self.got_section_index.?];
2199 const phdr = &self.program_headers.items[self.phdr_got_index.?];2196 const phdr = &self.program_headers.items[self.phdr_got_index.?];
2200 const entry_size: u16 = self.ptrWidthBytes();2197 const entry_size: u16 = self.archPtrWidthBytes();
2201 if (self.offset_table_count_dirty) {2198 if (self.offset_table_count_dirty) {
2202 // TODO Also detect virtual address collisions.2199 // TODO Also detect virtual address collisions.
2203 const allocated_size = self.allocatedSize(shdr.sh_offset);2200 const allocated_size = self.allocatedSize(shdr.sh_offset);
...@@ -2221,17 +2218,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {...@@ -2221,17 +2218,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
2221 }2218 }
2222 const endian = self.base.options.target.cpu.arch.endian();2219 const endian = self.base.options.target.cpu.arch.endian();
2223 const off = shdr.sh_offset + @as(u64, entry_size) * index;2220 const off = shdr.sh_offset + @as(u64, entry_size) * index;
2224 switch (self.ptr_width) {2221 switch (entry_size) {
2225 .p32 => {2222 2 => {
2223 var buf: [2]u8 = undefined;
2224 mem.writeInt(u16, &buf, @intCast(u16, self.offset_table.items[index]), endian);
2225 try self.base.file.?.pwriteAll(&buf, off);
2226 },
2227 4 => {
2226 var buf: [4]u8 = undefined;2228 var buf: [4]u8 = undefined;
2227 mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian);2229 mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian);
2228 try self.base.file.?.pwriteAll(&buf, off);2230 try self.base.file.?.pwriteAll(&buf, off);
2229 },2231 },
2230 .p64 => {2232 8 => {
2231 var buf: [8]u8 = undefined;2233 var buf: [8]u8 = undefined;
2232 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);2234 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
2233 try self.base.file.?.pwriteAll(&buf, off);2235 try self.base.file.?.pwriteAll(&buf, off);
2234 },2236 },
2237 else => unreachable,
2235 }2238 }
2236}2239}
22372240
...@@ -2344,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {...@@ -2344,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {
2344 }2347 }
2345}2348}
23462349
2350/// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF.
2347fn ptrWidthBytes(self: Elf) u8 {2351fn ptrWidthBytes(self: Elf) u8 {
2348 return switch (self.ptr_width) {2352 return switch (self.ptr_width) {
2349 .p32 => 4,2353 .p32 => 4,
...@@ -2351,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 {...@@ -2351,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 {
2351 };2355 };
2352}2356}
23532357
2358/// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes
2359/// in a 32-bit ELF file.
2360fn archPtrWidthBytes(self: Elf) u8 {
2361 return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8);
2362}
2363
2354/// The reloc offset for the virtual address of a function in its Line Number Program.2364/// The reloc offset for the virtual address of a function in its Line Number Program.
2355/// Size is a virtual address integer.2365/// Size is a virtual address integer.
2356const dbg_line_vaddr_reloc_index = 3;2366const dbg_line_vaddr_reloc_index = 3;
src-self-hosted/test.zig+115-2
...@@ -583,7 +583,10 @@ pub const TestContext = struct {...@@ -583,7 +583,10 @@ pub const TestContext = struct {
583583
584 switch (case.target.getExternalExecutor()) {584 switch (case.target.getExternalExecutor()) {
585 .native => try argv.append(exe_path),585 .native => try argv.append(exe_path),
586 .unavailable => return, // No executor available; pass test.586 .unavailable => {
587 try self.runInterpreterIfAvailable(allocator, &exec_node, case, tmp.dir, bin_name);
588 return; // Pass test.
589 },
587590
588 .qemu => |qemu_bin_name| if (enable_qemu) {591 .qemu => |qemu_bin_name| if (enable_qemu) {
589 // TODO Ability for test cases to specify whether to link libc.592 // TODO Ability for test cases to specify whether to link libc.
...@@ -635,7 +638,6 @@ pub const TestContext = struct {...@@ -635,7 +638,6 @@ pub const TestContext = struct {
635 var test_node = update_node.start("test", null);638 var test_node = update_node.start("test", null);
636 test_node.activate();639 test_node.activate();
637 defer test_node.end();640 defer test_node.end();
638
639 defer allocator.free(exec_result.stdout);641 defer allocator.free(exec_result.stdout);
640 defer allocator.free(exec_result.stderr);642 defer allocator.free(exec_result.stderr);
641 switch (exec_result.term) {643 switch (exec_result.term) {
...@@ -657,4 +659,115 @@ pub const TestContext = struct {...@@ -657,4 +659,115 @@ pub const TestContext = struct {
657 }659 }
658 }660 }
659 }661 }
662
663 fn runInterpreterIfAvailable(
664 self: *TestContext,
665 gpa: *Allocator,
666 node: *std.Progress.Node,
667 case: Case,
668 tmp_dir: std.fs.Dir,
669 bin_name: []const u8,
670 ) !void {
671 const arch = case.target.cpu_arch orelse return;
672 switch (arch) {
673 .spu_2 => return self.runSpu2Interpreter(gpa, node, case, tmp_dir, bin_name),
674 else => return,
675 }
676 }
677
678 fn runSpu2Interpreter(
679 self: *TestContext,
680 gpa: *Allocator,
681 update_node: *std.Progress.Node,
682 case: Case,
683 tmp_dir: std.fs.Dir,
684 bin_name: []const u8,
685 ) !void {
686 const spu = @import("codegen/spu-mk2.zig");
687 if (case.target.os_tag) |os| {
688 if (os != .freestanding) {
689 std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{});
690 }
691 } else {
692 std.debug.panic("SPU_2 has no native OS, check the test!", .{});
693 }
694
695 var interpreter = spu.Interpreter(struct {
696 RAM: [0x10000]u8 = undefined,
697
698 pub fn read8(bus: @This(), addr: u16) u8 {
699 return bus.RAM[addr];
700 }
701 pub fn read16(bus: @This(), addr: u16) u16 {
702 return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]);
703 }
704
705 pub fn write8(bus: *@This(), addr: u16, val: u8) void {
706 bus.RAM[addr] = val;
707 }
708
709 pub fn write16(bus: *@This(), addr: u16, val: u16) void {
710 std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val);
711 }
712 }){
713 .bus = .{},
714 };
715
716 {
717 var load_node = update_node.start("load", null);
718 load_node.activate();
719 defer load_node.end();
720
721 var file = try tmp_dir.openFile(bin_name, .{ .read = true });
722 defer file.close();
723
724 const header = try std.elf.readHeader(file);
725 var iterator = header.program_header_iterator(file);
726
727 var none_loaded = true;
728
729 while (try iterator.next()) |phdr| {
730 if (phdr.p_type != std.elf.PT_LOAD) {
731 std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type});
732 std.process.exit(1);
733 }
734 if (phdr.p_paddr != phdr.p_vaddr) {
735 std.debug.print("Physical address does not match virtual address in ELF header!\n", .{});
736 std.process.exit(1);
737 }
738 if (phdr.p_filesz != phdr.p_memsz) {
739 std.debug.print("Physical size does not match virtual size in ELF header!\n", .{});
740 std.process.exit(1);
741 }
742 if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) {
743 std.debug.print("Read less than expected from ELF file!", .{});
744 std.process.exit(1);
745 }
746 std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr });
747 none_loaded = false;
748 }
749 if (none_loaded) {
750 std.debug.print("No data found in ELF file!\n", .{});
751 std.process.exit(1);
752 }
753 }
754
755 var exec_node = update_node.start("execute", null);
756 exec_node.activate();
757 defer exec_node.end();
758
759 var blocks: u16 = 1000;
760 const block_size = 1000;
761 while (!interpreter.undefined0) {
762 const pre_ip = interpreter.ip;
763 if (blocks > 0) {
764 blocks -= 1;
765 try interpreter.ExecuteBlock(block_size);
766 if (pre_ip == interpreter.ip) {
767 std.debug.print("Infinite loop detected in SPU II test!\n", .{});
768 std.process.exit(1);
769 }
770 }
771 }
772 }
660};773};
test/stage2/spu-ii.zig created+23
...@@ -0,0 +1,23 @@
1const std = @import("std");
2const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
3
4const spu = std.zig.CrossTarget{
5 .cpu_arch = .spu_2,
6 .os_tag = .freestanding,
7};
8
9pub fn addCases(ctx: *TestContext) !void {
10 {
11 var case = ctx.exe("SPU-II Basic Test", spu);
12 case.addCompareOutput(
13 \\fn killEmulator() noreturn {
14 \\ asm volatile ("undefined0");
15 \\ unreachable;
16 \\}
17 \\
18 \\export fn _start() noreturn {
19 \\ killEmulator();
20 \\}
21 , "");
22 }
23}
test/stage2/test.zig+2
...@@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{...@@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{
26pub fn addCases(ctx: *TestContext) !void {26pub fn addCases(ctx: *TestContext) !void {
27 try @import("zir.zig").addCases(ctx);27 try @import("zir.zig").addCases(ctx);
28 try @import("cbe.zig").addCases(ctx);28 try @import("cbe.zig").addCases(ctx);
29 try @import("spu-ii.zig").addCases(ctx);
30
29 {31 {
30 var case = ctx.exe("hello world with updates", linux_x64);32 var case = ctx.exe("hello world with updates", linux_x64);
3133