| author | |
| committer | |
| log | 54f3b0a560f17effbb582f86ebf83c53916fc697 |
| tree | 7f428fe750a9248cad7db55f8d889a02e474869e |
| parent | 24efbf5ddfd7037eb5e0ec32fe73e10631add788 |
* 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 code11 files changed, 467 insertions(+), 439 deletions(-)
lib/std/spu.zig deleted-3| ... | @@ -1,3 +0,0 @@ | ||
| 1 | pub usingnamespace @import("spu/defines.zig"); | ||
| 2 | |||
| 3 | pub const interpreter = @import("spu/interpreter.zig").Interpreter; | ||
lib/std/spu/defines.zig deleted-158| ... | @@ -1,158 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const ExecutionCondition = enum(u3) { | ||
| 4 | always = 0, | ||
| 5 | when_zero = 1, | ||
| 6 | not_zero = 2, | ||
| 7 | greater_zero = 3, | ||
| 8 | less_than_zero = 4, | ||
| 9 | greater_or_equal_zero = 5, | ||
| 10 | less_or_equal_zero = 6, | ||
| 11 | overflow = 7, | ||
| 12 | }; | ||
| 13 | |||
| 14 | pub const InputBehaviour = enum(u2) { | ||
| 15 | zero = 0, | ||
| 16 | immediate = 1, | ||
| 17 | peek = 2, | ||
| 18 | pop = 3, | ||
| 19 | }; | ||
| 20 | |||
| 21 | pub const OutputBehaviour = enum(u2) { | ||
| 22 | discard = 0, | ||
| 23 | push = 1, | ||
| 24 | jump = 2, | ||
| 25 | jump_relative = 3, | ||
| 26 | }; | ||
| 27 | |||
| 28 | pub const Command = enum(u5) { | ||
| 29 | copy = 0, | ||
| 30 | ipget = 1, | ||
| 31 | get = 2, | ||
| 32 | set = 3, | ||
| 33 | store8 = 4, | ||
| 34 | store16 = 5, | ||
| 35 | load8 = 6, | ||
| 36 | load16 = 7, | ||
| 37 | undefined0 = 8, | ||
| 38 | undefined1 = 9, | ||
| 39 | frget = 10, | ||
| 40 | frset = 11, | ||
| 41 | bpget = 12, | ||
| 42 | bpset = 13, | ||
| 43 | spget = 14, | ||
| 44 | spset = 15, | ||
| 45 | add = 16, | ||
| 46 | sub = 17, | ||
| 47 | mul = 18, | ||
| 48 | div = 19, | ||
| 49 | mod = 20, | ||
| 50 | @"and" = 21, | ||
| 51 | @"or" = 22, | ||
| 52 | xor = 23, | ||
| 53 | not = 24, | ||
| 54 | signext = 25, | ||
| 55 | rol = 26, | ||
| 56 | ror = 27, | ||
| 57 | bswap = 28, | ||
| 58 | asr = 29, | ||
| 59 | lsl = 30, | ||
| 60 | lsr = 31, | ||
| 61 | }; | ||
| 62 | |||
| 63 | pub const Instruction = packed struct { | ||
| 64 | condition: ExecutionCondition, | ||
| 65 | input0: InputBehaviour, | ||
| 66 | input1: InputBehaviour, | ||
| 67 | modify_flags: bool, | ||
| 68 | output: OutputBehaviour, | ||
| 69 | command: Command, | ||
| 70 | reserved: u1 = 0, | ||
| 71 | |||
| 72 | pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void { | ||
| 73 | try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)}); | ||
| 74 | try out.writeAll(switch (instr.condition) { | ||
| 75 | .always => " ", | ||
| 76 | .when_zero => "== 0", | ||
| 77 | .not_zero => "!= 0", | ||
| 78 | .greater_zero => " > 0", | ||
| 79 | .less_than_zero => " < 0", | ||
| 80 | .greater_or_equal_zero => ">= 0", | ||
| 81 | .less_or_equal_zero => "<= 0", | ||
| 82 | .overflow => "ovfl", | ||
| 83 | }); | ||
| 84 | try out.writeAll(" "); | ||
| 85 | try out.writeAll(switch (instr.input0) { | ||
| 86 | .zero => "zero", | ||
| 87 | .immediate => "imm ", | ||
| 88 | .peek => "peek", | ||
| 89 | .pop => "pop ", | ||
| 90 | }); | ||
| 91 | try out.writeAll(" "); | ||
| 92 | try out.writeAll(switch (instr.input1) { | ||
| 93 | .zero => "zero", | ||
| 94 | .immediate => "imm ", | ||
| 95 | .peek => "peek", | ||
| 96 | .pop => "pop ", | ||
| 97 | }); | ||
| 98 | try out.writeAll(" "); | ||
| 99 | try out.writeAll(switch (instr.command) { | ||
| 100 | .copy => "copy ", | ||
| 101 | .ipget => "ipget ", | ||
| 102 | .get => "get ", | ||
| 103 | .set => "set ", | ||
| 104 | .store8 => "store8 ", | ||
| 105 | .store16 => "store16 ", | ||
| 106 | .load8 => "load8 ", | ||
| 107 | .load16 => "load16 ", | ||
| 108 | .undefined0 => "undefined", | ||
| 109 | .undefined1 => "undefined", | ||
| 110 | .frget => "frget ", | ||
| 111 | .frset => "frset ", | ||
| 112 | .bpget => "bpget ", | ||
| 113 | .bpset => "bpset ", | ||
| 114 | .spget => "spget ", | ||
| 115 | .spset => "spset ", | ||
| 116 | .add => "add ", | ||
| 117 | .sub => "sub ", | ||
| 118 | .mul => "mul ", | ||
| 119 | .div => "div ", | ||
| 120 | .mod => "mod ", | ||
| 121 | .@"and" => "and ", | ||
| 122 | .@"or" => "or ", | ||
| 123 | .xor => "xor ", | ||
| 124 | .not => "not ", | ||
| 125 | .signext => "signext ", | ||
| 126 | .rol => "rol ", | ||
| 127 | .ror => "ror ", | ||
| 128 | .bswap => "bswap ", | ||
| 129 | .asr => "asr ", | ||
| 130 | .lsl => "lsl ", | ||
| 131 | .lsr => "lsr ", | ||
| 132 | }); | ||
| 133 | try out.writeAll(" "); | ||
| 134 | try out.writeAll(switch (instr.output) { | ||
| 135 | .discard => "discard", | ||
| 136 | .push => "push ", | ||
| 137 | .jump => "jmp ", | ||
| 138 | .jump_relative => "rjmp ", | ||
| 139 | }); | ||
| 140 | try out.writeAll(" "); | ||
| 141 | try out.writeAll(if (instr.modify_flags) | ||
| 142 | "+ flags" | ||
| 143 | else | ||
| 144 | " "); | ||
| 145 | } | ||
| 146 | }; | ||
| 147 | |||
| 148 | pub const FlagRegister = packed struct { | ||
| 149 | zero: bool, | ||
| 150 | negative: bool, | ||
| 151 | carry: bool, | ||
| 152 | carry_enabled: bool, | ||
| 153 | interrupt0_enabled: bool, | ||
| 154 | interrupt1_enabled: bool, | ||
| 155 | interrupt2_enabled: bool, | ||
| 156 | interrupt3_enabled: bool, | ||
| 157 | reserved: u8 = 0, | ||
| 158 | }; | ||
lib/std/spu/interpreter.zig deleted-163| ... | @@ -1,163 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const log = std.log.scoped(.SPU_2_Interpreter); | ||
| 3 | usingnamespace @import("defines.zig"); | ||
| 4 | |||
| 5 | pub fn Interpreter(comptime Bus: type) type { | ||
| 6 | return struct { | ||
| 7 | ip: u16 = 0, | ||
| 8 | sp: u16 = undefined, | ||
| 9 | bp: u16 = undefined, | ||
| 10 | fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)), | ||
| 11 | /// This is set to true when we hit an undefined0 instruction, allowing it to | ||
| 12 | /// be used as a trap for testing purposes | ||
| 13 | undefined0: bool = false, | ||
| 14 | /// This is set to true when we hit an undefined1 instruction, allowing it to | ||
| 15 | /// be used as a trap for testing purposes. undefined1 is used as a breakpoint. | ||
| 16 | undefined1: bool = false, | ||
| 17 | bus: Bus, | ||
| 18 | |||
| 19 | pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void { | ||
| 20 | var count: usize = 0; | ||
| 21 | while (size == null or count < size.?) { | ||
| 22 | count += 1; | ||
| 23 | var instruction = @bitCast(Instruction, self.bus.read16(self.ip)); | ||
| 24 | |||
| 25 | log.debug("Executing {}\n", .{instruction}); | ||
| 26 | |||
| 27 | self.ip +%= 2; | ||
| 28 | |||
| 29 | const execute = switch (instruction.condition) { | ||
| 30 | .always => true, | ||
| 31 | .not_zero => !self.fr.zero, | ||
| 32 | .when_zero => self.fr.zero, | ||
| 33 | .overflow => self.fr.carry, | ||
| 34 | ExecutionCondition.greater_or_equal_zero => !self.fr.negative, | ||
| 35 | else => return error.unimplemented, | ||
| 36 | }; | ||
| 37 | |||
| 38 | if (execute) { | ||
| 39 | const val0 = switch (instruction.input0) { | ||
| 40 | .zero => @as(u16, 0), | ||
| 41 | .immediate => i: { | ||
| 42 | const val = self.bus.read16(@intCast(u16, self.ip)); | ||
| 43 | self.ip +%= 2; | ||
| 44 | break :i val; | ||
| 45 | }, | ||
| 46 | else => |e| e: { | ||
| 47 | // peek or pop; show value at current SP, and if pop, increment sp | ||
| 48 | const val = self.bus.read16(self.sp); | ||
| 49 | if (e == .pop) { | ||
| 50 | self.sp +%= 2; | ||
| 51 | } | ||
| 52 | break :e val; | ||
| 53 | }, | ||
| 54 | }; | ||
| 55 | const val1 = switch (instruction.input1) { | ||
| 56 | .zero => @as(u16, 0), | ||
| 57 | .immediate => i: { | ||
| 58 | const val = self.bus.read16(@intCast(u16, self.ip)); | ||
| 59 | self.ip +%= 2; | ||
| 60 | break :i val; | ||
| 61 | }, | ||
| 62 | else => |e| e: { | ||
| 63 | // peek or pop; show value at current SP, and if pop, increment sp | ||
| 64 | const val = self.bus.read16(self.sp); | ||
| 65 | if (e == .pop) { | ||
| 66 | self.sp +%= 2; | ||
| 67 | } | ||
| 68 | break :e val; | ||
| 69 | }, | ||
| 70 | }; | ||
| 71 | |||
| 72 | const output: u16 = switch (instruction.command) { | ||
| 73 | .get => self.bus.read16(self.bp +% (2 *% val0)), | ||
| 74 | .set => a: { | ||
| 75 | self.bus.write16(self.bp +% 2 *% val0, val1); | ||
| 76 | break :a val1; | ||
| 77 | }, | ||
| 78 | .load8 => self.bus.read8(val0), | ||
| 79 | .load16 => self.bus.read16(val0), | ||
| 80 | .store8 => a: { | ||
| 81 | const val = @truncate(u8, val1); | ||
| 82 | self.bus.write8(val0, val); | ||
| 83 | break :a val; | ||
| 84 | }, | ||
| 85 | .store16 => a: { | ||
| 86 | self.bus.write16(val0, val1); | ||
| 87 | break :a val1; | ||
| 88 | }, | ||
| 89 | .copy => val0, | ||
| 90 | .add => a: { | ||
| 91 | var val: u16 = undefined; | ||
| 92 | self.fr.carry = @addWithOverflow(u16, val0, val1, &val); | ||
| 93 | break :a val; | ||
| 94 | }, | ||
| 95 | .sub => a: { | ||
| 96 | var val: u16 = undefined; | ||
| 97 | self.fr.carry = @subWithOverflow(u16, val0, val1, &val); | ||
| 98 | break :a val; | ||
| 99 | }, | ||
| 100 | .spset => a: { | ||
| 101 | self.sp = val0; | ||
| 102 | break :a val0; | ||
| 103 | }, | ||
| 104 | .bpset => a: { | ||
| 105 | self.bp = val0; | ||
| 106 | break :a val0; | ||
| 107 | }, | ||
| 108 | .frset => a: { | ||
| 109 | const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1); | ||
| 110 | self.fr = @bitCast(FlagRegister, val); | ||
| 111 | break :a val; | ||
| 112 | }, | ||
| 113 | .bswap => (val0 >> 8) | (val0 << 8), | ||
| 114 | .bpget => self.bp, | ||
| 115 | .spget => self.sp, | ||
| 116 | .ipget => self.ip +% (2 *% val0), | ||
| 117 | .lsl => val0 << 1, | ||
| 118 | .lsr => val0 >> 1, | ||
| 119 | .@"and" => val0 & val1, | ||
| 120 | .@"or" => val0 | val1, | ||
| 121 | .xor => val0 ^ val1, | ||
| 122 | .not => ~val0, | ||
| 123 | .undefined0 => { | ||
| 124 | self.undefined0 = true; | ||
| 125 | // Break out of the loop, and let the caller decide what to do | ||
| 126 | return; | ||
| 127 | }, | ||
| 128 | .undefined1 => { | ||
| 129 | self.undefined1 = true; | ||
| 130 | // Break out of the loop, and let the caller decide what to do | ||
| 131 | return; | ||
| 132 | }, | ||
| 133 | .signext => if ((val0 & 0x80) != 0) | ||
| 134 | (val0 & 0xFF) | 0xFF00 | ||
| 135 | else | ||
| 136 | (val0 & 0xFF), | ||
| 137 | else => return error.unimplemented, | ||
| 138 | }; | ||
| 139 | |||
| 140 | switch (instruction.output) { | ||
| 141 | .discard => {}, | ||
| 142 | .push => { | ||
| 143 | self.sp -%= 2; | ||
| 144 | self.bus.write16(self.sp, output); | ||
| 145 | }, | ||
| 146 | .jump => { | ||
| 147 | self.ip = output; | ||
| 148 | }, | ||
| 149 | else => return error.unimplemented, | ||
| 150 | } | ||
| 151 | if (instruction.modify_flags) { | ||
| 152 | self.fr.negative = (output & 0x8000) != 0; | ||
| 153 | self.fr.zero = (output == 0x0000); | ||
| 154 | } | ||
| 155 | } else { | ||
| 156 | if (instruction.input0 == .immediate) self.ip +%= 2; | ||
| 157 | if (instruction.input1 == .immediate) self.ip +%= 2; | ||
| 158 | break; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | } | ||
lib/std/std.zig-3| ... | @@ -80,9 +80,6 @@ pub const valgrind = @import("valgrind.zig"); | ... | @@ -80,9 +80,6 @@ pub const valgrind = @import("valgrind.zig"); |
| 80 | pub const zig = @import("zig.zig"); | 80 | pub const zig = @import("zig.zig"); |
| 81 | pub const start = @import("start.zig"); | 81 | pub const start = @import("start.zig"); |
| 82 | 82 | ||
| 83 | // TODO move this | ||
| 84 | pub const spu = @import("spu.zig"); | ||
| 85 | |||
| 86 | // This forces the start.zig file to be imported, and the comptime logic inside that | 83 | // This forces the start.zig file to be imported, and the comptime logic inside that |
| 87 | // file decides whether to export any appropriate start symbols. | 84 | // file decides whether to export any appropriate start symbols. |
| 88 | comptime { | 85 | comptime { |
lib/std/target.zig+2-1| ... | @@ -663,7 +663,8 @@ pub const Target = struct { | ... | @@ -663,7 +663,8 @@ pub const Target = struct { |
| 663 | renderscript32, | 663 | renderscript32, |
| 664 | renderscript64, | 664 | renderscript64, |
| 665 | ve, | 665 | ve, |
| 666 | // Non-LLVM targets go here | 666 | // Stage1 currently assumes that architectures above this comment |
| 667 | // map one-to-one with the ZigLLVM_ArchType enum. | ||
| 667 | spu_2, | 668 | spu_2, |
| 668 | 669 | ||
| 669 | pub fn isARM(arch: Arch) bool { | 670 | pub fn isARM(arch: Arch) bool { |
src-self-hosted/codegen/spu-mk2.zig+160-1| ... | @@ -1,4 +1,163 @@ | ... | @@ -1,4 +1,163 @@ |
| 1 | pub usingnamespace @import("std").spu; | 1 | const std = @import("std"); |
| 2 | |||
| 3 | pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter; | ||
| 4 | |||
| 5 | pub 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 | |||
| 16 | pub const InputBehaviour = enum(u2) { | ||
| 17 | zero = 0, | ||
| 18 | immediate = 1, | ||
| 19 | peek = 2, | ||
| 20 | pop = 3, | ||
| 21 | }; | ||
| 22 | |||
| 23 | pub const OutputBehaviour = enum(u2) { | ||
| 24 | discard = 0, | ||
| 25 | push = 1, | ||
| 26 | jump = 2, | ||
| 27 | jump_relative = 3, | ||
| 28 | }; | ||
| 29 | |||
| 30 | pub 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 | |||
| 65 | pub 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 | |||
| 150 | pub 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 | }; | ||
| 2 | 161 | ||
| 3 | pub const Register = enum { | 162 | pub const Register = enum { |
| 4 | dummy, | 163 | dummy, |
src-self-hosted/codegen/spu-mk2/interpreter.zig created+166| ... | @@ -0,0 +1,166 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const log = std.log.scoped(.SPU_2_Interpreter); | ||
| 3 | const spu = @import("../spu-mk2.zig"); | ||
| 4 | const FlagRegister = spu.FlagRegister; | ||
| 5 | const Instruction = spu.Instruction; | ||
| 6 | const ExecutionCondition = spu.ExecutionCondition; | ||
| 7 | |||
| 8 | pub 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+1-1| ... | @@ -7,7 +7,7 @@ const Package = @import("Package.zig"); | ... | @@ -7,7 +7,7 @@ const Package = @import("Package.zig"); |
| 7 | const Type = @import("type.zig").Type; | 7 | const Type = @import("type.zig").Type; |
| 8 | const build_options = @import("build_options"); | 8 | const build_options = @import("build_options"); |
| 9 | 9 | ||
| 10 | const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; | 10 | pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; |
| 11 | 11 | ||
| 12 | pub const Options = struct { | 12 | pub const Options = struct { |
| 13 | target: std.Target, | 13 | target: std.Target, |
src-self-hosted/link/Elf.zig+21-18| ... | @@ -14,12 +14,10 @@ const leb128 = std.debug.leb; | ... | @@ -14,12 +14,10 @@ const leb128 = std.debug.leb; |
| 14 | const Package = @import("../Package.zig"); | 14 | const Package = @import("../Package.zig"); |
| 15 | const Value = @import("../value.zig").Value; | 15 | const Value = @import("../value.zig").Value; |
| 16 | const Type = @import("../type.zig").Type; | 16 | const Type = @import("../type.zig").Type; |
| 17 | const build_options = @import("build_options"); | ||
| 18 | const link = @import("../link.zig"); | 17 | const link = @import("../link.zig"); |
| 19 | const File = link.File; | 18 | const File = link.File; |
| 20 | const Elf = @This(); | 19 | const Elf = @This(); |
| 21 | 20 | ||
| 22 | const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; | ||
| 23 | const default_entry_addr = 0x8000000; | 21 | const default_entry_addr = 0x8000000; |
| 24 | 22 | ||
| 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 | 16, 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 | 16, 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 { |
| 346 | 344 | ||
| 347 | /// Returns end pos of collision, if any. | 345 | /// Returns end pos of collision, if any. |
| 348 | fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { | 346 | fn 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; |
| ... | @@ -487,7 +485,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { | ... | @@ -487,7 +485,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { |
| 487 | // 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. |
| 488 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something | 486 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something |
| 489 | // else in virtual memory. | 487 | // else in virtual memory. |
| 490 | const got_addr = if (self.base.options.target.cpu.arch.ptrBitWidth() == 16) @as(u32, 0x8000) else 0x4000000; | 488 | const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000; |
| 491 | try self.program_headers.append(self.base.allocator, .{ | 489 | try self.program_headers.append(self.base.allocator, .{ |
| 492 | .p_type = elf.PT_LOAD, | 490 | .p_type = elf.PT_LOAD, |
| 493 | .p_offset = off, | 491 | .p_offset = off, |
| ... | @@ -864,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void { | ... | @@ -864,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void { |
| 864 | // 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. |
| 865 | 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); |
| 866 | 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); |
| 867 | const producer_strp = try self.makeDebugString(producer_string); | 865 | const producer_strp = try self.makeDebugString(link.producer_string); |
| 868 | // Currently only one compilation unit is supported, so the address range is simply | 866 | // Currently only one compilation unit is supported, so the address range is simply |
| 869 | // identical to the main program header virtual address and memory size. | 867 | // identical to the main program header virtual address and memory size. |
| 870 | 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.?]; |
| ... | @@ -2152,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void { | ... | @@ -2152,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void { |
| 2152 | fn writeProgHeader(self: *Elf, index: usize) !void { | 2150 | fn writeProgHeader(self: *Elf, index: usize) !void { |
| 2153 | 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(); |
| 2154 | const offset = self.program_headers.items[index].p_offset; | 2152 | const offset = self.program_headers.items[index].p_offset; |
| 2155 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { | 2153 | switch (self.ptr_width) { |
| 2156 | 32 => { | 2154 | .p32 => { |
| 2157 | 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])}; |
| 2158 | if (foreign_endian) { | 2156 | if (foreign_endian) { |
| 2159 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); | 2157 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); |
| 2160 | } | 2158 | } |
| 2161 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); | 2159 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); |
| 2162 | }, | 2160 | }, |
| 2163 | 64 => { | 2161 | .p64 => { |
| 2164 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; | 2162 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; |
| 2165 | if (foreign_endian) { | 2163 | if (foreign_endian) { |
| 2166 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); | 2164 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); |
| 2167 | } | 2165 | } |
| 2168 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); | 2166 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); |
| 2169 | }, | 2167 | }, |
| 2170 | else => return error.UnsupportedArchitecture, | ||
| 2171 | } | 2168 | } |
| 2172 | } | 2169 | } |
| 2173 | 2170 | ||
| 2174 | fn writeSectHeader(self: *Elf, index: usize) !void { | 2171 | fn writeSectHeader(self: *Elf, index: usize) !void { |
| 2175 | 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(); |
| 2176 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { | 2173 | switch (self.ptr_width) { |
| 2177 | 32 => { | 2174 | .p32 => { |
| 2178 | var shdr: [1]elf.Elf32_Shdr = undefined; | 2175 | var shdr: [1]elf.Elf32_Shdr = undefined; |
| 2179 | shdr[0] = sectHeaderTo32(self.sections.items[index]); | 2176 | shdr[0] = sectHeaderTo32(self.sections.items[index]); |
| 2180 | if (foreign_endian) { | 2177 | if (foreign_endian) { |
| ... | @@ -2183,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void { | ... | @@ -2183,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void { |
| 2183 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); | 2180 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); |
| 2184 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 2181 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 2185 | }, | 2182 | }, |
| 2186 | 64 => { | 2183 | .p64 => { |
| 2187 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; | 2184 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; |
| 2188 | if (foreign_endian) { | 2185 | if (foreign_endian) { |
| 2189 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); | 2186 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); |
| ... | @@ -2191,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void { | ... | @@ -2191,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void { |
| 2191 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); | 2188 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); |
| 2192 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 2189 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 2193 | }, | 2190 | }, |
| 2194 | else => return error.UnsupportedArchitecture, | ||
| 2195 | } | 2191 | } |
| 2196 | } | 2192 | } |
| 2197 | 2193 | ||
| 2198 | fn writeOffsetTableEntry(self: *Elf, index: usize) !void { | 2194 | fn writeOffsetTableEntry(self: *Elf, index: usize) !void { |
| 2199 | const shdr = &self.sections.items[self.got_section_index.?]; | 2195 | const shdr = &self.sections.items[self.got_section_index.?]; |
| 2200 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; | 2196 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; |
| 2201 | const entry_size: u16 = self.base.options.target.cpu.arch.ptrBitWidth() / 8; | 2197 | const entry_size: u16 = self.archPtrWidthBytes(); |
| 2202 | if (self.offset_table_count_dirty) { | 2198 | if (self.offset_table_count_dirty) { |
| 2203 | // TODO Also detect virtual address collisions. | 2199 | // TODO Also detect virtual address collisions. |
| 2204 | const allocated_size = self.allocatedSize(shdr.sh_offset); | 2200 | const allocated_size = self.allocatedSize(shdr.sh_offset); |
| ... | @@ -2351,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void { | ... | @@ -2351,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void { |
| 2351 | } | 2347 | } |
| 2352 | } | 2348 | } |
| 2353 | 2349 | ||
| 2350 | /// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF. | ||
| 2354 | fn ptrWidthBytes(self: Elf) u8 { | 2351 | fn ptrWidthBytes(self: Elf) u8 { |
| 2355 | return switch (self.ptr_width) { | 2352 | return switch (self.ptr_width) { |
| 2356 | .p32 => 4, | 2353 | .p32 => 4, |
| ... | @@ -2358,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 { | ... | @@ -2358,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 { |
| 2358 | }; | 2355 | }; |
| 2359 | } | 2356 | } |
| 2360 | 2357 | ||
| 2358 | /// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes | ||
| 2359 | /// in a 32-bit ELF file. | ||
| 2360 | fn archPtrWidthBytes(self: Elf) u8 { | ||
| 2361 | return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8); | ||
| 2362 | } | ||
| 2363 | |||
| 2361 | /// 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. |
| 2362 | /// Size is a virtual address integer. | 2365 | /// Size is a virtual address integer. |
| 2363 | const dbg_line_vaddr_reloc_index = 3; | 2366 | const dbg_line_vaddr_reloc_index = 3; |
src-self-hosted/test.zig+115-90| ... | @@ -571,95 +571,6 @@ pub const TestContext = struct { | ... | @@ -571,95 +571,6 @@ pub const TestContext = struct { |
| 571 | std.debug.assert(!case.cbe); | 571 | std.debug.assert(!case.cbe); |
| 572 | 572 | ||
| 573 | update_node.estimated_total_items = 4; | 573 | update_node.estimated_total_items = 4; |
| 574 | if (case.target.cpu_arch) |arch| { | ||
| 575 | if (arch == .spu_2) { | ||
| 576 | if (case.target.os_tag) |os| { | ||
| 577 | if (os != .freestanding) { | ||
| 578 | std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{}); | ||
| 579 | } | ||
| 580 | } else { | ||
| 581 | std.debug.panic("SPU_2 has no native OS, check the test!", .{}); | ||
| 582 | } | ||
| 583 | |||
| 584 | var interpreter = std.spu.interpreter(struct { | ||
| 585 | RAM: [0x10000]u8 = undefined, | ||
| 586 | |||
| 587 | pub fn read8(bus: @This(), addr: u16) u8 { | ||
| 588 | return bus.RAM[addr]; | ||
| 589 | } | ||
| 590 | pub fn read16(bus: @This(), addr: u16) u16 { | ||
| 591 | return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]); | ||
| 592 | } | ||
| 593 | |||
| 594 | pub fn write8(bus: *@This(), addr: u16, val: u8) void { | ||
| 595 | bus.RAM[addr] = val; | ||
| 596 | } | ||
| 597 | |||
| 598 | pub fn write16(bus: *@This(), addr: u16, val: u16) void { | ||
| 599 | std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val); | ||
| 600 | } | ||
| 601 | }){ | ||
| 602 | .bus = .{}, | ||
| 603 | }; | ||
| 604 | |||
| 605 | { | ||
| 606 | var load_node = update_node.start("load", null); | ||
| 607 | load_node.activate(); | ||
| 608 | defer load_node.end(); | ||
| 609 | |||
| 610 | var file = try tmp.dir.openFile(bin_name, .{ .read = true }); | ||
| 611 | defer file.close(); | ||
| 612 | |||
| 613 | const header = try std.elf.readHeader(file); | ||
| 614 | var iterator = header.program_header_iterator(file); | ||
| 615 | |||
| 616 | var none_loaded = true; | ||
| 617 | |||
| 618 | while (try iterator.next()) |phdr| { | ||
| 619 | if (phdr.p_type != std.elf.PT_LOAD) { | ||
| 620 | std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type}); | ||
| 621 | std.process.exit(1); | ||
| 622 | } | ||
| 623 | if (phdr.p_paddr != phdr.p_vaddr) { | ||
| 624 | std.debug.print("Physical address does not match virtual address in ELF header!\n", .{}); | ||
| 625 | std.process.exit(1); | ||
| 626 | } | ||
| 627 | if (phdr.p_filesz != phdr.p_memsz) { | ||
| 628 | std.debug.print("Physical size does not match virtual size in ELF header!\n", .{}); | ||
| 629 | std.process.exit(1); | ||
| 630 | } | ||
| 631 | if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) { | ||
| 632 | std.debug.print("Read less than expected from ELF file!", .{}); | ||
| 633 | std.process.exit(1); | ||
| 634 | } | ||
| 635 | std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr }); | ||
| 636 | none_loaded = false; | ||
| 637 | } | ||
| 638 | if (none_loaded) { | ||
| 639 | std.debug.print("No data found in ELF file!\n", .{}); | ||
| 640 | std.process.exit(1); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | var exec_node = update_node.start("execute", null); | ||
| 645 | exec_node.activate(); | ||
| 646 | defer exec_node.end(); | ||
| 647 | |||
| 648 | var blocks: u16 = 1000; | ||
| 649 | const block_size = 1000; | ||
| 650 | while (!interpreter.undefined0) { | ||
| 651 | const pre_ip = interpreter.ip; | ||
| 652 | if (blocks > 0) { | ||
| 653 | blocks -= 1; | ||
| 654 | try interpreter.ExecuteBlock(block_size); | ||
| 655 | if (pre_ip == interpreter.ip) { | ||
| 656 | std.debug.print("Infinite loop detected in SPU II test!\n", .{}); | ||
| 657 | std.process.exit(1); | ||
| 658 | } | ||
| 659 | } | ||
| 660 | } | ||
| 661 | } | ||
| 662 | } | ||
| 663 | var exec_result = x: { | 574 | var exec_result = x: { |
| 664 | var exec_node = update_node.start("execute", null); | 575 | var exec_node = update_node.start("execute", null); |
| 665 | exec_node.activate(); | 576 | exec_node.activate(); |
| ... | @@ -672,7 +583,10 @@ pub const TestContext = struct { | ... | @@ -672,7 +583,10 @@ pub const TestContext = struct { |
| 672 | 583 | ||
| 673 | switch (case.target.getExternalExecutor()) { | 584 | switch (case.target.getExternalExecutor()) { |
| 674 | .native => try argv.append(exe_path), | 585 | .native => try argv.append(exe_path), |
| 675 | .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 | }, | ||
| 676 | 590 | ||
| 677 | .qemu => |qemu_bin_name| if (enable_qemu) { | 591 | .qemu => |qemu_bin_name| if (enable_qemu) { |
| 678 | // TODO Ability for test cases to specify whether to link libc. | 592 | // TODO Ability for test cases to specify whether to link libc. |
| ... | @@ -745,4 +659,115 @@ pub const TestContext = struct { | ... | @@ -745,4 +659,115 @@ pub const TestContext = struct { |
| 745 | } | 659 | } |
| 746 | } | 660 | } |
| 747 | } | 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 | } | ||
| 748 | }; | 773 | }; |
test/stage2/test.zig+2-1| ... | @@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{ | ... | @@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{ |
| 26 | pub fn addCases(ctx: *TestContext) !void { | 26 | pub 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); |
| 31 | 33 | ||
| ... | @@ -785,5 +787,4 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -785,5 +787,4 @@ pub fn addCases(ctx: *TestContext) !void { |
| 785 | \\} | 787 | \\} |
| 786 | \\extern var foo; | 788 | \\extern var foo; |
| 787 | , &[_][]const u8{":4:1: error: unable to infer variable type"}); | 789 | , &[_][]const u8{":4:1: error: unable to infer variable type"}); |
| 788 | try @import("spu-ii.zig").addCases(ctx); | ||
| 789 | } | 790 | } |