From 803a1025bbfb8757589068ae29f6f2fc57a4f566 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 17:52:03 -0400 Subject: [PATCH 01/15] Targets: add SPU Mark II architecture --- lib/std/elf.zig | 3 +++ lib/std/target.zig | 8 +++++++- src-self-hosted/link/Elf.zig | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 79303e7163..cd2b5fcd06 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -976,6 +976,9 @@ pub const EM = extern enum(u16) { /// MIPS RS3000 Little-endian _MIPS_RS3_LE = 10, + /// SPU Mark II + _SPU_2 = 13, + /// Hewlett-Packard PA-RISC _PARISC = 15, diff --git a/lib/std/target.zig b/lib/std/target.zig index b162bdd0c3..fcd90357fb 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -663,6 +663,8 @@ pub const Target = struct { renderscript32, renderscript64, ve, + // Non-LLVM targets go here + spu_2, pub fn isARM(arch: Arch) bool { return switch (arch) { @@ -761,6 +763,7 @@ pub const Target = struct { .sparcv9 => ._SPARCV9, .s390x => ._S390, .ve => ._NONE, + .spu_2 => ._SPU_2, }; } @@ -803,6 +806,7 @@ pub const Target = struct { .renderscript64, .shave, .ve, + .spu_2, => .Little, .arc, @@ -827,6 +831,7 @@ pub const Target = struct { switch (arch) { .avr, .msp430, + .spu_2, => return 16, .arc, @@ -1317,12 +1322,13 @@ pub const Target = struct { .bpfeb, .nvptx, .nvptx64, + .spu_2, + .avr, => return result, // TODO go over each item in this list and either move it to the above list, or // implement the standard dynamic linker path code for it. .arc, - .avr, .hexagon, .msp430, .r600, diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 3751411297..861c313eaa 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -249,7 +249,7 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf { .allocator = allocator, }, .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { - 32 => .p32, + 16, 32 => .p32, 64 => .p64, else => return error.UnsupportedELFArchitecture, }, @@ -278,7 +278,7 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf .file = file, }, .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { - 32 => .p32, + 16, 32 => .p32, 64 => .p64, else => return error.UnsupportedELFArchitecture, }, From d005ff16c6fe1a0ddb3997552d6cccf314e6d94f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 10 Jul 2020 20:29:55 -0400 Subject: [PATCH 02/15] SPU-II: undefined0 inline asm --- src-self-hosted/codegen.zig | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 4ce2e041c0..a737b1360d 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1642,6 +1642,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (!inst.is_volatile and inst.base.isUnused()) return MCValue.dead; switch (arch) { + .spu_2 => { + if (inst.inputs.len > 0 or inst.output != null) { + return self.fail(inst.base.src, "TODO implement inline asm inputs / outputs for SPU Mark II", .{}); + } + if (mem.eql(u8, inst.asm_source, "undefined0")) { + // Instructions are 16-bits, plus up to two sixteen bit immediates. + // Upper three bits of first byte are the execution + // condition; for now, only always (0b000) is supported. + // Next, there are two two-bit sequences indicating inputs; + // we only care to use zero (0b00). + // The lowest bit of byte one indicates whether flags + // should be updated; TODO: support that somehow. + // In all, we use a zero byte for the first half of the + // instruction. + // The second byte is 0bOOCCCCCR; OO is output behavior (we + // use zero, which discards the output), CCCCC is the + // command (8 for undefined0), R is reserved. + try self.code.appendSlice(&[_]u8{ 0x00, 0b00010000 }); + return MCValue.none; + } else { + return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{}); + } + }, .riscv64 => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { From cdefc6acbaf7f582c6689d1e003405819f797181 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 21 Jul 2020 21:33:53 -0400 Subject: [PATCH 03/15] SPU-II: Implement function calls --- src-self-hosted/codegen.zig | 59 +++++++--- src-self-hosted/codegen/spu-mk2.zig | 167 ++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 14 deletions(-) create mode 100644 src-self-hosted/codegen/spu-mk2.zig diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index a737b1360d..8ff0d32461 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -102,6 +102,7 @@ pub fn generateSymbol( //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), @@ -1349,6 +1350,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); } }, + .spu_2 => { + if (inst.func.cast(ir.Inst.Constant)) |func_inst| { + if (info.args.len != 0) { + return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); + } + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + const got_addr = @intCast(u16, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * 2); + const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType(); + // First, push the return address, then jump; if noreturn, don't bother with the first step + // TODO: implement packed struct -> u16 at comptime and move the bitcast here + var instr = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .jump, .command = .load16 }; + if (return_type.zigTypeTag() == .NoReturn) { + try self.code.resize(self.code.items.len + 4); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr); + return MCValue.unreach; + } else { + try self.code.resize(self.code.items.len + 8); + var push = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .push, .command = .ipget }; + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 8 ..][0..2], @bitCast(u16, push)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 6 ..][0..2], @as(u16, 4)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr); + switch (return_type.zigTypeTag()) { + .Void => return MCValue{ .none = {} }, + .NoReturn => unreachable, + else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), + } + } + } else { + return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); + } + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); + } + }, else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), } } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { @@ -1647,19 +1686,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail(inst.base.src, "TODO implement inline asm inputs / outputs for SPU Mark II", .{}); } if (mem.eql(u8, inst.asm_source, "undefined0")) { - // Instructions are 16-bits, plus up to two sixteen bit immediates. - // Upper three bits of first byte are the execution - // condition; for now, only always (0b000) is supported. - // Next, there are two two-bit sequences indicating inputs; - // we only care to use zero (0b00). - // The lowest bit of byte one indicates whether flags - // should be updated; TODO: support that somehow. - // In all, we use a zero byte for the first half of the - // instruction. - // The second byte is 0bOOCCCCCR; OO is output behavior (we - // use zero, which discards the output), CCCCC is the - // command (8 for undefined0), R is reserved. - try self.code.appendSlice(&[_]u8{ 0x00, 0b00010000 }); + try self.code.resize(self.code.items.len + 2); + var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined0 }; + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); return MCValue.none; } else { return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{}); @@ -1742,6 +1771,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// X => extension to the SIB.index field /// B => extension to the MODRM.rm field or the SIB.base field fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { + std.debug.assert(arch == .x86_64); // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. var value: u8 = 0x40; if (arg.b) { @@ -2289,7 +2319,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { result.stack_byte_count = next_stack_offset; result.stack_align = 16; }, - else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), + else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}), } }, else => if (param_types.len != 0) @@ -2336,6 +2366,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .i386 => @import("codegen/x86.zig"), .x86_64 => @import("codegen/x86_64.zig"), .riscv64 => @import("codegen/riscv64.zig"), + .spu_2 => @import("codegen/spu-mk2.zig"), else => struct { pub const Register = enum { dummy, diff --git a/src-self-hosted/codegen/spu-mk2.zig b/src-self-hosted/codegen/spu-mk2.zig new file mode 100644 index 0000000000..b44d4123bd --- /dev/null +++ b/src-self-hosted/codegen/spu-mk2.zig @@ -0,0 +1,167 @@ +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, +}; + +pub const Register = enum { + dummy, + + pub fn allocIndex(self: Register) ?u4 { + return null; + } +}; +pub const callee_preserved_regs = [_]Register{}; From 8c321f0cf5f38e9286476170f51608dbc7b92c50 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 19 Aug 2020 11:42:19 -0400 Subject: [PATCH 04/15] SPU-II: Fix linking --- src-self-hosted/link.zig | 4 ++++ src-self-hosted/link/Elf.zig | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 1e650d5e2c..e8604038c2 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -5,6 +5,9 @@ const fs = std.fs; const trace = @import("tracy.zig").trace; 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 Options = struct { target: std.Target, @@ -20,6 +23,7 @@ pub const Options = struct { /// Used for calculating how much space to reserve for executable program code in case /// the binary file deos not already have such a section. program_code_size_hint: u64 = 256 * 1024, + default_entry_addr: u64 = 0x8000000, }; pub const File = struct { diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 861c313eaa..f197ebb6d2 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -286,6 +286,10 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf }; errdefer self.deinit(); + if (options.target.cpu.arch == .spu_2) { + self.base.options.default_entry_addr = 0; + } + // Index 0 is always a null symbol. try self.local_symbols.append(allocator, .{ .st_name = 0, @@ -466,8 +470,8 @@ pub fn populateMissingMetadata(self: *Elf) !void { .p_type = elf.PT_LOAD, .p_offset = off, .p_filesz = file_size, - .p_vaddr = default_entry_addr, - .p_paddr = default_entry_addr, + .p_vaddr = self.base.options.default_entry_addr, + .p_paddr = self.base.options.default_entry_addr, .p_memsz = file_size, .p_align = p_align, .p_flags = elf.PF_X | elf.PF_R, @@ -486,7 +490,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 default_got_addr = if (ptr_size == 2) @as(u32, 0x8000) else 0x4000000; + const default_got_addr = if (self.base.options.target.cpu.arch.ptrBitWidth() == 16) @as(u32, 0x8000) else 0x4000000; try self.program_headers.append(self.base.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, From f18636fa58905ec85d246cb14c18418f2ce74317 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 19 Aug 2020 12:48:10 -0400 Subject: [PATCH 05/15] SPU-II: Add common definitions --- lib/std/spu.zig | 3 + lib/std/spu/defines.zig | 158 +++++++++++++++++++++++++++ lib/std/std.zig | 3 + src-self-hosted/codegen/spu-mk2.zig | 160 +--------------------------- 4 files changed, 166 insertions(+), 158 deletions(-) create mode 100644 lib/std/spu.zig create mode 100644 lib/std/spu/defines.zig diff --git a/lib/std/spu.zig b/lib/std/spu.zig new file mode 100644 index 0000000000..275ef698ea --- /dev/null +++ b/lib/std/spu.zig @@ -0,0 +1,3 @@ +pub usingnamespace @import("spu/defines.zig"); + +pub const interpreter = @import("spu/interpreter.zig").Interpreter; diff --git a/lib/std/spu/defines.zig b/lib/std/spu/defines.zig new file mode 100644 index 0000000000..0f7e6b03be --- /dev/null +++ b/lib/std/spu/defines.zig @@ -0,0 +1,158 @@ +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, +}; diff --git a/lib/std/std.zig b/lib/std/std.zig index 2ff44f5e41..e6287ac5b7 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -80,6 +80,9 @@ 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 { diff --git a/src-self-hosted/codegen/spu-mk2.zig b/src-self-hosted/codegen/spu-mk2.zig index b44d4123bd..0a95dce663 100644 --- a/src-self-hosted/codegen/spu-mk2.zig +++ b/src-self-hosted/codegen/spu-mk2.zig @@ -1,161 +1,4 @@ -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, -}; +pub usingnamespace @import("std").spu; pub const Register = enum { dummy, @@ -164,4 +7,5 @@ pub const Register = enum { return null; } }; + pub const callee_preserved_regs = [_]Register{}; From f2fef240a1a1d7caa06daa8e1bdf58206b42a73c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 19 Aug 2020 12:45:34 -0400 Subject: [PATCH 06/15] SPU-II: Test harness skeleton --- lib/std/spu/interpreter.zig | 159 ++++++++++++++++++++++++++++++++++++ src-self-hosted/test.zig | 51 +++++++++++- test/stage2/spu-ii.zig | 23 ++++++ test/stage2/test.zig | 1 + 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 lib/std/spu/interpreter.zig create mode 100644 test/stage2/spu-ii.zig diff --git a/lib/std/spu/interpreter.zig b/lib/std/spu/interpreter.zig new file mode 100644 index 0000000000..ed22b13099 --- /dev/null +++ b/lib/std/spu/interpreter.zig @@ -0,0 +1,159 @@ +const std = @import("std"); +usingnamespace @import("defines.zig"); + +pub fn Interpreter(comptime Bus: type) type { + return struct { + ip: u16 = 0, + sp: u16 = undefined, + bp: u16 = undefined, + fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)), + /// This is set to true when we hit an undefined0 instruction, allowing it to + /// be used as a trap for testing purposes + undefined0: bool = false, + bus: Bus, + + pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void { + var count: usize = 0; + while (size == null or count < size.?) { + count += 1; + var instruction = @bitCast(Instruction, self.bus.read16(self.ip)); + + std.log.debug(.SPU_2_Interpreter, "Executing {}\n", .{instruction}); + + self.ip +%= 2; + + const execute = switch (instruction.condition) { + .always => true, + .not_zero => !self.fr.zero, + .when_zero => self.fr.zero, + .overflow => self.fr.carry, + ExecutionCondition.greater_or_equal_zero => !self.fr.negative, + else => return error.unimplemented, + }; + + if (execute) { + const val0 = switch (instruction.input0) { + .zero => @as(u16, 0), + .immediate => i: { + const val = self.bus.read16(@intCast(u16, self.ip)); + self.ip +%= 2; + break :i val; + }, + else => |e| e: { + // peek or pop; show value at current SP, and if pop, increment sp + const val = self.bus.read16(self.sp); + if (e == .pop) { + self.sp +%= 2; + } + break :e val; + }, + }; + const val1 = switch (instruction.input1) { + .zero => @as(u16, 0), + .immediate => i: { + const val = self.bus.read16(@intCast(u16, self.ip)); + self.ip +%= 2; + break :i val; + }, + else => |e| e: { + // peek or pop; show value at current SP, and if pop, increment sp + const val = self.bus.read16(self.sp); + if (e == .pop) { + self.sp +%= 2; + } + break :e val; + }, + }; + + const output: u16 = switch (instruction.command) { + .get => self.bus.read16(self.bp +% (2 *% val0)), + .set => a: { + self.bus.write16(self.bp +% 2 *% val0, val1); + break :a val1; + }, + .load8 => self.bus.read8(val0), + .load16 => self.bus.read16(val0), + .store8 => a: { + const val = @truncate(u8, val1); + self.bus.write8(val0, val); + break :a val; + }, + .store16 => a: { + self.bus.write16(val0, val1); + break :a val1; + }, + .copy => val0, + .add => a: { + var val: u16 = undefined; + self.fr.carry = @addWithOverflow(u16, val0, val1, &val); + break :a val; + }, + .sub => a: { + var val: u16 = undefined; + self.fr.carry = @subWithOverflow(u16, val0, val1, &val); + break :a val; + }, + .spset => a: { + self.sp = val0; + break :a val0; + }, + .bpset => a: { + self.bp = val0; + break :a val0; + }, + .frset => a: { + const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1); + self.fr = @bitCast(FlagRegister, val); + break :a val; + }, + .bswap => (val0 >> 8) | (val0 << 8), + .bpget => self.bp, + .spget => self.sp, + .ipget => self.ip +% (2 *% val0), + .lsl => val0 << 1, + .lsr => val0 >> 1, + .@"and" => val0 & val1, + .@"or" => val0 | val1, + .xor => val0 ^ val1, + .not => ~val0, + .undefined0 => { + self.undefined0 = true; + // Break out of the loop, and let the caller decide what to do + return; + }, + .undefined1 => return error.BadInstruction, + .signext => if ((val0 & 0x80) != 0) + (val0 & 0xFF) | 0xFF00 + else + (val0 & 0xFF), + else => return error.unimplemented, + }; + + switch (instruction.output) { + .discard => {}, + .push => { + self.sp -%= 2; + self.bus.write16(self.sp, output); + }, + .jump => { + self.ip = output; + if (!(instruction.command == .copy and instruction.input0 == .immediate)) { + // Not absolute. Break, for compatibility with JIT. + break; + } + }, + else => return error.unimplemented, + } + if (instruction.modify_flags) { + self.fr.negative = (output & 0x8000) != 0; + self.fr.zero = (output == 0x0000); + } + } else { + if (instruction.input0 == .immediate) self.ip +%= 2; + if (instruction.input1 == .immediate) self.ip +%= 2; + break; + } + } + } + }; +} diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 9e88466cf7..db2c4a8838 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -571,6 +571,56 @@ 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 output = std.ArrayList(u8).init(allocator); + // defer output.deinit(); + const uart = struct { + fn in(_out: usize) !u8 { + return error.not_implemented; + } + fn out(_output: usize, val: u8) !void { + try @intToPtr(*std.ArrayList(u8), _output).append(val); + } + }; + 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 = .{}, + }; + + //defer interpreter.deinit(allocator); + std.debug.print("TODO implement SPU-II test loader\n", .{}); + std.process.exit(1); + // TODO: loop detection? Solve the halting problem? fork() and limit by wall clock? + // Limit by emulated cycles? + // while (!interpreter.undefined0) { + // try interpreter.ExecuteBlock(100); + // } + } + } var exec_result = x: { var exec_node = update_node.start("execute", null); exec_node.activate(); @@ -635,7 +685,6 @@ pub const TestContext = struct { var test_node = update_node.start("test", null); test_node.activate(); defer test_node.end(); - defer allocator.free(exec_result.stdout); defer allocator.free(exec_result.stderr); switch (exec_result.term) { diff --git a/test/stage2/spu-ii.zig b/test/stage2/spu-ii.zig new file mode 100644 index 0000000000..1316f19d0d --- /dev/null +++ b/test/stage2/spu-ii.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const TestContext = @import("../../src-self-hosted/test.zig").TestContext; + +const spu = std.zig.CrossTarget{ + .cpu_arch = .spu_2, + .os_tag = .freestanding, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exe("SPU-II Basic Test", spu); + case.addCompareOutput( + \\fn killEmulator() noreturn { + \\ asm volatile ("undefined0"); + \\ unreachable; + \\} + \\ + \\export fn _start() noreturn { + \\ killEmulator(); + \\} + , ""); + } +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 791a073393..832d9a44db 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -785,4 +785,5 @@ 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); } From 3a9af0c88be324dd049d85da0600230a03404946 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 19 Aug 2020 12:26:19 -0400 Subject: [PATCH 07/15] SPU-II: Ignore @breakpoint for now --- src-self-hosted/codegen.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 8ff0d32461..aec4ea81e6 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1265,6 +1265,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32()); }, + .spu_2 => {}, else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), } return .none; From f2796239baf1b5883e007729e6064e144c443b56 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 07:20:25 -0400 Subject: [PATCH 08/15] SPU-II: Fix logging in interp, remove JIT-compat code --- lib/std/spu/interpreter.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/std/spu/interpreter.zig b/lib/std/spu/interpreter.zig index ed22b13099..66d75a5676 100644 --- a/lib/std/spu/interpreter.zig +++ b/lib/std/spu/interpreter.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const log = std.log.scoped(.SPU_2_Interpreter); usingnamespace @import("defines.zig"); pub fn Interpreter(comptime Bus: type) type { @@ -18,7 +19,7 @@ pub fn Interpreter(comptime Bus: type) type { count += 1; var instruction = @bitCast(Instruction, self.bus.read16(self.ip)); - std.log.debug(.SPU_2_Interpreter, "Executing {}\n", .{instruction}); + log.debug("Executing {}\n", .{instruction}); self.ip +%= 2; @@ -137,10 +138,6 @@ pub fn Interpreter(comptime Bus: type) type { }, .jump => { self.ip = output; - if (!(instruction.command == .copy and instruction.input0 == .immediate)) { - // Not absolute. Break, for compatibility with JIT. - break; - } }, else => return error.unimplemented, } From fa1d18a15583253d457c39fc38e5bdda8b3f2cf2 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 07:20:51 -0400 Subject: [PATCH 09/15] Linker: fix GOT production on 16-bit targets --- src-self-hosted/link/Elf.zig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index f197ebb6d2..34f2243718 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -2201,7 +2201,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void { 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.ptrWidthBytes(); + const entry_size: u16 = self.base.options.target.cpu.arch.ptrBitWidth() / 8; if (self.offset_table_count_dirty) { // TODO Also detect virtual address collisions. const allocated_size = self.allocatedSize(shdr.sh_offset); @@ -2225,17 +2225,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { } const endian = self.base.options.target.cpu.arch.endian(); const off = shdr.sh_offset + @as(u64, entry_size) * index; - switch (self.ptr_width) { - .p32 => { + switch (entry_size) { + 2 => { + var buf: [2]u8 = undefined; + mem.writeInt(u16, &buf, @intCast(u16, self.offset_table.items[index]), endian); + try self.base.file.?.pwriteAll(&buf, off); + }, + 4 => { var buf: [4]u8 = undefined; mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); try self.base.file.?.pwriteAll(&buf, off); }, - .p64 => { + 8 => { var buf: [8]u8 = undefined; mem.writeInt(u64, &buf, self.offset_table.items[index], endian); try self.base.file.?.pwriteAll(&buf, off); }, + else => unreachable, } } From 096c5d5e4bf6856fc2250c42a07d1df7c31a1775 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 07:21:11 -0400 Subject: [PATCH 10/15] Tests: implement SPU-II harness --- src-self-hosted/test.zig | 68 ++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index db2c4a8838..ebd050e80c 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -580,16 +580,7 @@ pub const TestContext = struct { } else { std.debug.panic("SPU_2 has no native OS, check the test!", .{}); } - var output = std.ArrayList(u8).init(allocator); - // defer output.deinit(); - const uart = struct { - fn in(_out: usize) !u8 { - return error.not_implemented; - } - fn out(_output: usize, val: u8) !void { - try @intToPtr(*std.ArrayList(u8), _output).append(val); - } - }; + var interpreter = std.spu.interpreter(struct { RAM: [0x10000]u8 = undefined, @@ -611,14 +602,59 @@ pub const TestContext = struct { .bus = .{}, }; - //defer interpreter.deinit(allocator); - std.debug.print("TODO implement SPU-II test loader\n", .{}); - std.process.exit(1); + { + 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(); + // TODO: loop detection? Solve the halting problem? fork() and limit by wall clock? // Limit by emulated cycles? - // while (!interpreter.undefined0) { - // try interpreter.ExecuteBlock(100); - // } + while (!interpreter.undefined0) { + const pre_ip = interpreter.ip; + try interpreter.ExecuteBlock(1); + if (pre_ip == interpreter.ip) { + std.debug.print("Infinite loop detected in SPU II test!\n", .{}); + std.process.exit(1); + } + } } } var exec_result = x: { From 222e23c6786e0ac307524c6c91b41782111af82a Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 07:25:21 -0400 Subject: [PATCH 11/15] Linker: make defaults read-only --- src-self-hosted/link.zig | 2 +- src-self-hosted/link/Elf.zig | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index e8604038c2..59b01dc762 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -23,7 +23,7 @@ pub const Options = struct { /// Used for calculating how much space to reserve for executable program code in case /// the binary file deos not already have such a section. program_code_size_hint: u64 = 256 * 1024, - default_entry_addr: u64 = 0x8000000, + entry_addr: ?u64 = null, }; pub const File = struct { diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 34f2243718..53798053c2 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -286,10 +286,6 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf }; errdefer self.deinit(); - if (options.target.cpu.arch == .spu_2) { - self.base.options.default_entry_addr = 0; - } - // Index 0 is always a null symbol. try self.local_symbols.append(allocator, .{ .st_name = 0, @@ -466,12 +462,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + const entry_addr: u64 = self.entry_addr orelse if (self.base.options.target.cpu.arch == .spu_2) @as(u64, 0) else default_entry_addr; try self.program_headers.append(self.base.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, .p_filesz = file_size, - .p_vaddr = self.base.options.default_entry_addr, - .p_paddr = self.base.options.default_entry_addr, + .p_vaddr = entry_addr, + .p_paddr = entry_addr, .p_memsz = file_size, .p_align = p_align, .p_flags = elf.PF_X | elf.PF_R, @@ -490,13 +487,13 @@ 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 default_got_addr = if (self.base.options.target.cpu.arch.ptrBitWidth() == 16) @as(u32, 0x8000) else 0x4000000; + const got_addr = if (self.base.options.target.cpu.arch.ptrBitWidth() == 16) @as(u32, 0x8000) else 0x4000000; try self.program_headers.append(self.base.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, .p_filesz = file_size, - .p_vaddr = default_got_addr, - .p_paddr = default_got_addr, + .p_vaddr = got_addr, + .p_paddr = got_addr, .p_memsz = file_size, .p_align = p_align, .p_flags = elf.PF_R, From f448b518f874929dd219ed957c314889d5dbf0cf Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 18:19:23 -0400 Subject: [PATCH 12/15] SPU-II: use undefined1 as breakpoint --- lib/std/spu/interpreter.zig | 9 ++++++++- src-self-hosted/codegen.zig | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/std/spu/interpreter.zig b/lib/std/spu/interpreter.zig index 66d75a5676..d008955a93 100644 --- a/lib/std/spu/interpreter.zig +++ b/lib/std/spu/interpreter.zig @@ -11,6 +11,9 @@ pub fn Interpreter(comptime Bus: type) type { /// This is set to true when we hit an undefined0 instruction, allowing it to /// be used as a trap for testing purposes undefined0: bool = false, + /// This is set to true when we hit an undefined1 instruction, allowing it to + /// be used as a trap for testing purposes. undefined1 is used as a breakpoint. + undefined1: bool = false, bus: Bus, pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void { @@ -122,7 +125,11 @@ pub fn Interpreter(comptime Bus: type) type { // Break out of the loop, and let the caller decide what to do return; }, - .undefined1 => return error.BadInstruction, + .undefined1 => { + self.undefined1 = true; + // Break out of the loop, and let the caller decide what to do + return; + }, .signext => if ((val0 & 0x80) != 0) (val0 & 0xFF) | 0xFF00 else diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index aec4ea81e6..c3cb1fb8e2 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1265,7 +1265,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32()); }, - .spu_2 => {}, + .spu_2 => { + try self.code.resize(self.code.items.len + 2); + var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined1 }; + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); + }, else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), } return .none; From ad9df43e4926247cff20a9d9851fffb2a99c8a30 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 18:24:00 -0400 Subject: [PATCH 13/15] Tests: limit SPU-II cycle count --- src-self-hosted/test.zig | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index ebd050e80c..3f02d78b9b 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -645,14 +645,17 @@ pub const TestContext = struct { exec_node.activate(); defer exec_node.end(); - // TODO: loop detection? Solve the halting problem? fork() and limit by wall clock? - // Limit by emulated cycles? + var blocks: u16 = 1000; + const block_size = 1000; while (!interpreter.undefined0) { const pre_ip = interpreter.ip; - try interpreter.ExecuteBlock(1); - if (pre_ip == interpreter.ip) { - std.debug.print("Infinite loop detected in SPU II test!\n", .{}); - std.process.exit(1); + 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); + } } } } From 24efbf5ddfd7037eb5e0ec32fe73e10631add788 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 21 Aug 2020 18:25:46 -0400 Subject: [PATCH 14/15] Codegen: Move REX assert to comptime --- src-self-hosted/codegen.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index c3cb1fb8e2..93305b70e3 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -1776,7 +1776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// X => extension to the SIB.index field /// B => extension to the MODRM.rm field or the SIB.base field fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { - std.debug.assert(arch == .x86_64); + comptime assert(arch == .x86_64); // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. var value: u8 = 0x40; if (arg.b) { From 54f3b0a560f17effbb582f86ebf83c53916fc697 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 22 Aug 2020 13:36:08 -0700 Subject: [PATCH 15/15] 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 --- lib/std/spu.zig | 3 - lib/std/spu/defines.zig | 158 -------------- lib/std/std.zig | 3 - lib/std/target.zig | 3 +- src-self-hosted/codegen/spu-mk2.zig | 161 +++++++++++++- .../codegen/spu-mk2}/interpreter.zig | 11 +- src-self-hosted/link.zig | 2 +- src-self-hosted/link/Elf.zig | 39 ++-- src-self-hosted/test.zig | 205 ++++++++++-------- test/stage2/test.zig | 3 +- 10 files changed, 308 insertions(+), 280 deletions(-) delete mode 100644 lib/std/spu.zig delete mode 100644 lib/std/spu/defines.zig rename {lib/std/spu => src-self-hosted/codegen/spu-mk2}/interpreter.zig (95%) diff --git a/lib/std/spu.zig b/lib/std/spu.zig deleted file mode 100644 index 275ef698ea..0000000000 --- a/lib/std/spu.zig +++ /dev/null @@ -1,3 +0,0 @@ -pub usingnamespace @import("spu/defines.zig"); - -pub const interpreter = @import("spu/interpreter.zig").Interpreter; diff --git a/lib/std/spu/defines.zig b/lib/std/spu/defines.zig deleted file mode 100644 index 0f7e6b03be..0000000000 --- a/lib/std/spu/defines.zig +++ /dev/null @@ -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, -}; diff --git a/lib/std/std.zig b/lib/std/std.zig index e6287ac5b7..2ff44f5e41 100644 --- a/lib/std/std.zig +++ b/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 { diff --git a/lib/std/target.zig b/lib/std/target.zig index fcd90357fb..080ac65f5c 100644 --- a/lib/std/target.zig +++ b/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 { diff --git a/src-self-hosted/codegen/spu-mk2.zig b/src-self-hosted/codegen/spu-mk2.zig index 0a95dce663..542862caca 100644 --- a/src-self-hosted/codegen/spu-mk2.zig +++ b/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, diff --git a/lib/std/spu/interpreter.zig b/src-self-hosted/codegen/spu-mk2/interpreter.zig similarity index 95% rename from lib/std/spu/interpreter.zig rename to src-self-hosted/codegen/spu-mk2/interpreter.zig index d008955a93..1ec99546c6 100644 --- a/lib/std/spu/interpreter.zig +++ b/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; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 59b01dc762..7a5680dfbf 100644 --- a/src-self-hosted/link.zig +++ b/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, diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 53798053c2..eeb0289b05 100644 --- a/src-self-hosted/link/Elf.zig +++ b/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; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 3f02d78b9b..f9c9121817 100644 --- a/src-self-hosted/test.zig +++ b/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); + } + } + } + } }; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 832d9a44db..627cfc7fe0 100644 --- a/test/stage2/test.zig +++ b/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); }