| author | |
| committer | |
| log | 4e63cae369ec6e333e4aecb3d749bedff6484ee3 |
| tree | 7f428fe750a9248cad7db55f8d889a02e474869e |
| parent | 29051a0674800881957479c423a1feb043391671 |
| parent | 54f3b0a560f17effbb582f86ebf83c53916fc697 |
closes #609610 files changed, 586 insertions(+), 29 deletions(-)
lib/std/elf.zig+3| ... | @@ -976,6 +976,9 @@ pub const EM = extern enum(u16) { | ... | @@ -976,6 +976,9 @@ pub const EM = extern enum(u16) { |
| 976 | /// MIPS RS3000 Little-endian | 976 | /// MIPS RS3000 Little-endian |
| 977 | _MIPS_RS3_LE = 10, | 977 | _MIPS_RS3_LE = 10, |
| 978 | 978 | ||
| 979 | /// SPU Mark II | ||
| 980 | _SPU_2 = 13, | ||
| 981 | |||
| 979 | /// Hewlett-Packard PA-RISC | 982 | /// Hewlett-Packard PA-RISC |
| 980 | _PARISC = 15, | 983 | _PARISC = 15, |
| 981 | 984 |
lib/std/target.zig+8-1| ... | @@ -663,6 +663,9 @@ pub const Target = struct { | ... | @@ -663,6 +663,9 @@ pub const Target = struct { |
| 663 | renderscript32, | 663 | renderscript32, |
| 664 | renderscript64, | 664 | renderscript64, |
| 665 | ve, | 665 | ve, |
| 666 | // Stage1 currently assumes that architectures above this comment | ||
| 667 | // map one-to-one with the ZigLLVM_ArchType enum. | ||
| 668 | spu_2, | ||
| 666 | 669 | ||
| 667 | pub fn isARM(arch: Arch) bool { | 670 | pub fn isARM(arch: Arch) bool { |
| 668 | return switch (arch) { | 671 | return switch (arch) { |
| ... | @@ -761,6 +764,7 @@ pub const Target = struct { | ... | @@ -761,6 +764,7 @@ pub const Target = struct { |
| 761 | .sparcv9 => ._SPARCV9, | 764 | .sparcv9 => ._SPARCV9, |
| 762 | .s390x => ._S390, | 765 | .s390x => ._S390, |
| 763 | .ve => ._NONE, | 766 | .ve => ._NONE, |
| 767 | .spu_2 => ._SPU_2, | ||
| 764 | }; | 768 | }; |
| 765 | } | 769 | } |
| 766 | 770 | ||
| ... | @@ -803,6 +807,7 @@ pub const Target = struct { | ... | @@ -803,6 +807,7 @@ pub const Target = struct { |
| 803 | .renderscript64, | 807 | .renderscript64, |
| 804 | .shave, | 808 | .shave, |
| 805 | .ve, | 809 | .ve, |
| 810 | .spu_2, | ||
| 806 | => .Little, | 811 | => .Little, |
| 807 | 812 | ||
| 808 | .arc, | 813 | .arc, |
| ... | @@ -827,6 +832,7 @@ pub const Target = struct { | ... | @@ -827,6 +832,7 @@ pub const Target = struct { |
| 827 | switch (arch) { | 832 | switch (arch) { |
| 828 | .avr, | 833 | .avr, |
| 829 | .msp430, | 834 | .msp430, |
| 835 | .spu_2, | ||
| 830 | => return 16, | 836 | => return 16, |
| 831 | 837 | ||
| 832 | .arc, | 838 | .arc, |
| ... | @@ -1317,12 +1323,13 @@ pub const Target = struct { | ... | @@ -1317,12 +1323,13 @@ pub const Target = struct { |
| 1317 | .bpfeb, | 1323 | .bpfeb, |
| 1318 | .nvptx, | 1324 | .nvptx, |
| 1319 | .nvptx64, | 1325 | .nvptx64, |
| 1326 | .spu_2, | ||
| 1327 | .avr, | ||
| 1320 | => return result, | 1328 | => return result, |
| 1321 | 1329 | ||
| 1322 | // TODO go over each item in this list and either move it to the above list, or | 1330 | // TODO go over each item in this list and either move it to the above list, or |
| 1323 | // implement the standard dynamic linker path code for it. | 1331 | // implement the standard dynamic linker path code for it. |
| 1324 | .arc, | 1332 | .arc, |
| 1325 | .avr, | ||
| 1326 | .hexagon, | 1333 | .hexagon, |
| 1327 | .msp430, | 1334 | .msp430, |
| 1328 | .r600, | 1335 | .r600, |
src-self-hosted/codegen.zig+60-1| ... | @@ -102,6 +102,7 @@ pub fn generateSymbol( | ... | @@ -102,6 +102,7 @@ pub fn generateSymbol( |
| 102 | //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 102 | //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 103 | //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 103 | //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 104 | //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 104 | //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 105 | .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | ||
| 105 | //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 106 | //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 106 | //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 107 | //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| 107 | //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), | 108 | //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), |
| ... | @@ -1264,6 +1265,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1264,6 +1265,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1264 | .riscv64 => { | 1265 | .riscv64 => { |
| 1265 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32()); | 1266 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32()); |
| 1266 | }, | 1267 | }, |
| 1268 | .spu_2 => { | ||
| 1269 | try self.code.resize(self.code.items.len + 2); | ||
| 1270 | var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined1 }; | ||
| 1271 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); | ||
| 1272 | }, | ||
| 1267 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), | 1273 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), |
| 1268 | } | 1274 | } |
| 1269 | return .none; | 1275 | return .none; |
| ... | @@ -1349,6 +1355,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1349,6 +1355,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1349 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | 1355 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| 1350 | } | 1356 | } |
| 1351 | }, | 1357 | }, |
| 1358 | .spu_2 => { | ||
| 1359 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { | ||
| 1360 | if (info.args.len != 0) { | ||
| 1361 | return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); | ||
| 1362 | } | ||
| 1363 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | ||
| 1364 | const func = func_val.func; | ||
| 1365 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; | ||
| 1366 | const got_addr = @intCast(u16, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * 2); | ||
| 1367 | const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType(); | ||
| 1368 | // First, push the return address, then jump; if noreturn, don't bother with the first step | ||
| 1369 | // TODO: implement packed struct -> u16 at comptime and move the bitcast here | ||
| 1370 | var instr = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .jump, .command = .load16 }; | ||
| 1371 | if (return_type.zigTypeTag() == .NoReturn) { | ||
| 1372 | try self.code.resize(self.code.items.len + 4); | ||
| 1373 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr)); | ||
| 1374 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr); | ||
| 1375 | return MCValue.unreach; | ||
| 1376 | } else { | ||
| 1377 | try self.code.resize(self.code.items.len + 8); | ||
| 1378 | var push = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .push, .command = .ipget }; | ||
| 1379 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 8 ..][0..2], @bitCast(u16, push)); | ||
| 1380 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 6 ..][0..2], @as(u16, 4)); | ||
| 1381 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr)); | ||
| 1382 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr); | ||
| 1383 | switch (return_type.zigTypeTag()) { | ||
| 1384 | .Void => return MCValue{ .none = {} }, | ||
| 1385 | .NoReturn => unreachable, | ||
| 1386 | else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), | ||
| 1387 | } | ||
| 1388 | } | ||
| 1389 | } else { | ||
| 1390 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | ||
| 1391 | } | ||
| 1392 | } else { | ||
| 1393 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | ||
| 1394 | } | ||
| 1395 | }, | ||
| 1352 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), | 1396 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), |
| 1353 | } | 1397 | } |
| 1354 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 1398 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| ... | @@ -1642,6 +1686,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1642,6 +1686,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1642 | if (!inst.is_volatile and inst.base.isUnused()) | 1686 | if (!inst.is_volatile and inst.base.isUnused()) |
| 1643 | return MCValue.dead; | 1687 | return MCValue.dead; |
| 1644 | switch (arch) { | 1688 | switch (arch) { |
| 1689 | .spu_2 => { | ||
| 1690 | if (inst.inputs.len > 0 or inst.output != null) { | ||
| 1691 | return self.fail(inst.base.src, "TODO implement inline asm inputs / outputs for SPU Mark II", .{}); | ||
| 1692 | } | ||
| 1693 | if (mem.eql(u8, inst.asm_source, "undefined0")) { | ||
| 1694 | try self.code.resize(self.code.items.len + 2); | ||
| 1695 | var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined0 }; | ||
| 1696 | mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); | ||
| 1697 | return MCValue.none; | ||
| 1698 | } else { | ||
| 1699 | return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{}); | ||
| 1700 | } | ||
| 1701 | }, | ||
| 1645 | .riscv64 => { | 1702 | .riscv64 => { |
| 1646 | for (inst.inputs) |input, i| { | 1703 | for (inst.inputs) |input, i| { |
| 1647 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | 1704 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { |
| ... | @@ -1719,6 +1776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1719,6 +1776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1719 | /// X => extension to the SIB.index field | 1776 | /// X => extension to the SIB.index field |
| 1720 | /// B => extension to the MODRM.rm field or the SIB.base field | 1777 | /// B => extension to the MODRM.rm field or the SIB.base field |
| 1721 | fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { | 1778 | fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { |
| 1779 | comptime assert(arch == .x86_64); | ||
| 1722 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. | 1780 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. |
| 1723 | var value: u8 = 0x40; | 1781 | var value: u8 = 0x40; |
| 1724 | if (arg.b) { | 1782 | if (arg.b) { |
| ... | @@ -2266,7 +2324,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -2266,7 +2324,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2266 | result.stack_byte_count = next_stack_offset; | 2324 | result.stack_byte_count = next_stack_offset; |
| 2267 | result.stack_align = 16; | 2325 | result.stack_align = 16; |
| 2268 | }, | 2326 | }, |
| 2269 | else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), | 2327 | else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}), |
| 2270 | } | 2328 | } |
| 2271 | }, | 2329 | }, |
| 2272 | else => if (param_types.len != 0) | 2330 | else => if (param_types.len != 0) |
| ... | @@ -2313,6 +2371,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -2313,6 +2371,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2313 | .i386 => @import("codegen/x86.zig"), | 2371 | .i386 => @import("codegen/x86.zig"), |
| 2314 | .x86_64 => @import("codegen/x86_64.zig"), | 2372 | .x86_64 => @import("codegen/x86_64.zig"), |
| 2315 | .riscv64 => @import("codegen/riscv64.zig"), | 2373 | .riscv64 => @import("codegen/riscv64.zig"), |
| 2374 | .spu_2 => @import("codegen/spu-mk2.zig"), | ||
| 2316 | else => struct { | 2375 | else => struct { |
| 2317 | pub const Register = enum { | 2376 | pub const Register = enum { |
| 2318 | dummy, | 2377 | dummy, |
src-self-hosted/codegen/spu-mk2.zig created+170| ... | @@ -0,0 +1,170 @@ | ||
| 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 | }; | ||
| 161 | |||
| 162 | pub const Register = enum { | ||
| 163 | dummy, | ||
| 164 | |||
| 165 | pub fn allocIndex(self: Register) ?u4 { | ||
| 166 | return null; | ||
| 167 | } | ||
| 168 | }; | ||
| 169 | |||
| 170 | pub const callee_preserved_regs = [_]Register{}; | ||
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+4| ... | @@ -5,6 +5,9 @@ const fs = std.fs; | ... | @@ -5,6 +5,9 @@ const fs = std.fs; |
| 5 | const trace = @import("tracy.zig").trace; | 5 | const trace = @import("tracy.zig").trace; |
| 6 | const Package = @import("Package.zig"); | 6 | 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"); | ||
| 9 | |||
| 10 | pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; | ||
| 8 | 11 | ||
| 9 | pub const Options = struct { | 12 | pub const Options = struct { |
| 10 | target: std.Target, | 13 | target: std.Target, |
| ... | @@ -20,6 +23,7 @@ pub const Options = struct { | ... | @@ -20,6 +23,7 @@ pub const Options = struct { |
| 20 | /// Used for calculating how much space to reserve for executable program code in case | 23 | /// Used for calculating how much space to reserve for executable program code in case |
| 21 | /// the binary file deos not already have such a section. | 24 | /// the binary file deos not already have such a section. |
| 22 | program_code_size_hint: u64 = 256 * 1024, | 25 | program_code_size_hint: u64 = 256 * 1024, |
| 26 | entry_addr: ?u64 = null, | ||
| 23 | }; | 27 | }; |
| 24 | 28 | ||
| 25 | pub const File = struct { | 29 | pub const File = struct { |
src-self-hosted/link/Elf.zig+35-25| ... | @@ -14,12 +14,10 @@ const leb128 = std.debug.leb; | ... | @@ -14,12 +14,10 @@ const leb128 = std.debug.leb; |
| 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 | 32 => .p32, | 250 | 0 ... 32 => .p32, |
| 253 | 64 => .p64, | 251 | 33 ... 64 => .p64, |
| 254 | else => return error.UnsupportedELFArchitecture, | 252 | else => return error.UnsupportedELFArchitecture, |
| 255 | }, | 253 | }, |
| 256 | }; | 254 | }; |
| ... | @@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf | ... | @@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf |
| 278 | .file = file, | 276 | .file = file, |
| 279 | }, | 277 | }, |
| 280 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | 278 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { |
| 281 | 32 => .p32, | 279 | 0 ... 32 => .p32, |
| 282 | 64 => .p64, | 280 | 33 ... 64 => .p64, |
| 283 | else => return error.UnsupportedELFArchitecture, | 281 | else => return error.UnsupportedELFArchitecture, |
| 284 | }, | 282 | }, |
| 285 | .shdr_table_dirty = true, | 283 | .shdr_table_dirty = true, |
| ... | @@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 { | ... | @@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 { |
| 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; |
| ... | @@ -462,12 +460,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { | ... | @@ -462,12 +460,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { |
| 462 | const p_align = 0x1000; | 460 | const p_align = 0x1000; |
| 463 | const off = self.findFreeSpace(file_size, p_align); | 461 | const off = self.findFreeSpace(file_size, p_align); |
| 464 | log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | 462 | log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 463 | const entry_addr: u64 = self.entry_addr orelse if (self.base.options.target.cpu.arch == .spu_2) @as(u64, 0) else default_entry_addr; | ||
| 465 | try self.program_headers.append(self.base.allocator, .{ | 464 | try self.program_headers.append(self.base.allocator, .{ |
| 466 | .p_type = elf.PT_LOAD, | 465 | .p_type = elf.PT_LOAD, |
| 467 | .p_offset = off, | 466 | .p_offset = off, |
| 468 | .p_filesz = file_size, | 467 | .p_filesz = file_size, |
| 469 | .p_vaddr = default_entry_addr, | 468 | .p_vaddr = entry_addr, |
| 470 | .p_paddr = default_entry_addr, | 469 | .p_paddr = entry_addr, |
| 471 | .p_memsz = file_size, | 470 | .p_memsz = file_size, |
| 472 | .p_align = p_align, | 471 | .p_align = p_align, |
| 473 | .p_flags = elf.PF_X | elf.PF_R, | 472 | .p_flags = elf.PF_X | elf.PF_R, |
| ... | @@ -486,13 +485,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { | ... | @@ -486,13 +485,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { |
| 486 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. | 485 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. |
| 487 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something | 486 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something |
| 488 | // else in virtual memory. | 487 | // else in virtual memory. |
| 489 | const default_got_addr = if (ptr_size == 2) @as(u32, 0x8000) else 0x4000000; | 488 | const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000; |
| 490 | try self.program_headers.append(self.base.allocator, .{ | 489 | try self.program_headers.append(self.base.allocator, .{ |
| 491 | .p_type = elf.PT_LOAD, | 490 | .p_type = elf.PT_LOAD, |
| 492 | .p_offset = off, | 491 | .p_offset = off, |
| 493 | .p_filesz = file_size, | 492 | .p_filesz = file_size, |
| 494 | .p_vaddr = default_got_addr, | 493 | .p_vaddr = got_addr, |
| 495 | .p_paddr = default_got_addr, | 494 | .p_paddr = got_addr, |
| 496 | .p_memsz = file_size, | 495 | .p_memsz = file_size, |
| 497 | .p_align = p_align, | 496 | .p_align = p_align, |
| 498 | .p_flags = elf.PF_R, | 497 | .p_flags = elf.PF_R, |
| ... | @@ -863,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void { | ... | @@ -863,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void { |
| 863 | // Write the form for the compile unit, which must match the abbrev table above. | 862 | // Write the form for the compile unit, which must match the abbrev table above. |
| 864 | const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path); | 863 | const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path); |
| 865 | const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path); | 864 | const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path); |
| 866 | const producer_strp = try self.makeDebugString(producer_string); | 865 | const producer_strp = try self.makeDebugString(link.producer_string); |
| 867 | // Currently only one compilation unit is supported, so the address range is simply | 866 | // Currently only one compilation unit is supported, so the address range is simply |
| 868 | // identical to the main program header virtual address and memory size. | 867 | // identical to the main program header virtual address and memory size. |
| 869 | const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | 868 | const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; |
| ... | @@ -2151,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void { | ... | @@ -2151,29 +2150,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void { |
| 2151 | fn writeProgHeader(self: *Elf, index: usize) !void { | 2150 | fn writeProgHeader(self: *Elf, index: usize) !void { |
| 2152 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 2151 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 2153 | const offset = self.program_headers.items[index].p_offset; | 2152 | const offset = self.program_headers.items[index].p_offset; |
| 2154 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { | 2153 | switch (self.ptr_width) { |
| 2155 | 32 => { | 2154 | .p32 => { |
| 2156 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; | 2155 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; |
| 2157 | if (foreign_endian) { | 2156 | if (foreign_endian) { |
| 2158 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); | 2157 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); |
| 2159 | } | 2158 | } |
| 2160 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); | 2159 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); |
| 2161 | }, | 2160 | }, |
| 2162 | 64 => { | 2161 | .p64 => { |
| 2163 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; | 2162 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; |
| 2164 | if (foreign_endian) { | 2163 | if (foreign_endian) { |
| 2165 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); | 2164 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); |
| 2166 | } | 2165 | } |
| 2167 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); | 2166 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); |
| 2168 | }, | 2167 | }, |
| 2169 | else => return error.UnsupportedArchitecture, | ||
| 2170 | } | 2168 | } |
| 2171 | } | 2169 | } |
| 2172 | 2170 | ||
| 2173 | fn writeSectHeader(self: *Elf, index: usize) !void { | 2171 | fn writeSectHeader(self: *Elf, index: usize) !void { |
| 2174 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 2172 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 2175 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { | 2173 | switch (self.ptr_width) { |
| 2176 | 32 => { | 2174 | .p32 => { |
| 2177 | var shdr: [1]elf.Elf32_Shdr = undefined; | 2175 | var shdr: [1]elf.Elf32_Shdr = undefined; |
| 2178 | shdr[0] = sectHeaderTo32(self.sections.items[index]); | 2176 | shdr[0] = sectHeaderTo32(self.sections.items[index]); |
| 2179 | if (foreign_endian) { | 2177 | if (foreign_endian) { |
| ... | @@ -2182,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void { | ... | @@ -2182,7 +2180,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void { |
| 2182 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); | 2180 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); |
| 2183 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 2181 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 2184 | }, | 2182 | }, |
| 2185 | 64 => { | 2183 | .p64 => { |
| 2186 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; | 2184 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; |
| 2187 | if (foreign_endian) { | 2185 | if (foreign_endian) { |
| 2188 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); | 2186 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); |
| ... | @@ -2190,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void { | ... | @@ -2190,14 +2188,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void { |
| 2190 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); | 2188 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); |
| 2191 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 2189 | return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 2192 | }, | 2190 | }, |
| 2193 | else => return error.UnsupportedArchitecture, | ||
| 2194 | } | 2191 | } |
| 2195 | } | 2192 | } |
| 2196 | 2193 | ||
| 2197 | fn writeOffsetTableEntry(self: *Elf, index: usize) !void { | 2194 | fn writeOffsetTableEntry(self: *Elf, index: usize) !void { |
| 2198 | const shdr = &self.sections.items[self.got_section_index.?]; | 2195 | const shdr = &self.sections.items[self.got_section_index.?]; |
| 2199 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; | 2196 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; |
| 2200 | const entry_size: u16 = self.ptrWidthBytes(); | 2197 | const entry_size: u16 = self.archPtrWidthBytes(); |
| 2201 | if (self.offset_table_count_dirty) { | 2198 | if (self.offset_table_count_dirty) { |
| 2202 | // TODO Also detect virtual address collisions. | 2199 | // TODO Also detect virtual address collisions. |
| 2203 | const allocated_size = self.allocatedSize(shdr.sh_offset); | 2200 | const allocated_size = self.allocatedSize(shdr.sh_offset); |
| ... | @@ -2221,17 +2218,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { | ... | @@ -2221,17 +2218,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { |
| 2221 | } | 2218 | } |
| 2222 | const endian = self.base.options.target.cpu.arch.endian(); | 2219 | const endian = self.base.options.target.cpu.arch.endian(); |
| 2223 | const off = shdr.sh_offset + @as(u64, entry_size) * index; | 2220 | const off = shdr.sh_offset + @as(u64, entry_size) * index; |
| 2224 | switch (self.ptr_width) { | 2221 | switch (entry_size) { |
| 2225 | .p32 => { | 2222 | 2 => { |
| 2223 | var buf: [2]u8 = undefined; | ||
| 2224 | mem.writeInt(u16, &buf, @intCast(u16, self.offset_table.items[index]), endian); | ||
| 2225 | try self.base.file.?.pwriteAll(&buf, off); | ||
| 2226 | }, | ||
| 2227 | 4 => { | ||
| 2226 | var buf: [4]u8 = undefined; | 2228 | var buf: [4]u8 = undefined; |
| 2227 | mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); | 2229 | mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); |
| 2228 | try self.base.file.?.pwriteAll(&buf, off); | 2230 | try self.base.file.?.pwriteAll(&buf, off); |
| 2229 | }, | 2231 | }, |
| 2230 | .p64 => { | 2232 | 8 => { |
| 2231 | var buf: [8]u8 = undefined; | 2233 | var buf: [8]u8 = undefined; |
| 2232 | mem.writeInt(u64, &buf, self.offset_table.items[index], endian); | 2234 | mem.writeInt(u64, &buf, self.offset_table.items[index], endian); |
| 2233 | try self.base.file.?.pwriteAll(&buf, off); | 2235 | try self.base.file.?.pwriteAll(&buf, off); |
| 2234 | }, | 2236 | }, |
| 2237 | else => unreachable, | ||
| 2235 | } | 2238 | } |
| 2236 | } | 2239 | } |
| 2237 | 2240 | ||
| ... | @@ -2344,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void { | ... | @@ -2344,6 +2347,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void { |
| 2344 | } | 2347 | } |
| 2345 | } | 2348 | } |
| 2346 | 2349 | ||
| 2350 | /// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF. | ||
| 2347 | fn ptrWidthBytes(self: Elf) u8 { | 2351 | fn ptrWidthBytes(self: Elf) u8 { |
| 2348 | return switch (self.ptr_width) { | 2352 | return switch (self.ptr_width) { |
| 2349 | .p32 => 4, | 2353 | .p32 => 4, |
| ... | @@ -2351,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 { | ... | @@ -2351,6 +2355,12 @@ fn ptrWidthBytes(self: Elf) u8 { |
| 2351 | }; | 2355 | }; |
| 2352 | } | 2356 | } |
| 2353 | 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 | |||
| 2354 | /// The reloc offset for the virtual address of a function in its Line Number Program. | 2364 | /// The reloc offset for the virtual address of a function in its Line Number Program. |
| 2355 | /// Size is a virtual address integer. | 2365 | /// Size is a virtual address integer. |
| 2356 | const dbg_line_vaddr_reloc_index = 3; | 2366 | const dbg_line_vaddr_reloc_index = 3; |
src-self-hosted/test.zig+115-2| ... | @@ -583,7 +583,10 @@ pub const TestContext = struct { | ... | @@ -583,7 +583,10 @@ pub const TestContext = struct { |
| 583 | 583 | ||
| 584 | switch (case.target.getExternalExecutor()) { | 584 | switch (case.target.getExternalExecutor()) { |
| 585 | .native => try argv.append(exe_path), | 585 | .native => try argv.append(exe_path), |
| 586 | .unavailable => return, // No executor available; pass test. | 586 | .unavailable => { |
| 587 | try self.runInterpreterIfAvailable(allocator, &exec_node, case, tmp.dir, bin_name); | ||
| 588 | return; // Pass test. | ||
| 589 | }, | ||
| 587 | 590 | ||
| 588 | .qemu => |qemu_bin_name| if (enable_qemu) { | 591 | .qemu => |qemu_bin_name| if (enable_qemu) { |
| 589 | // TODO Ability for test cases to specify whether to link libc. | 592 | // TODO Ability for test cases to specify whether to link libc. |
| ... | @@ -635,7 +638,6 @@ pub const TestContext = struct { | ... | @@ -635,7 +638,6 @@ pub const TestContext = struct { |
| 635 | var test_node = update_node.start("test", null); | 638 | var test_node = update_node.start("test", null); |
| 636 | test_node.activate(); | 639 | test_node.activate(); |
| 637 | defer test_node.end(); | 640 | defer test_node.end(); |
| 638 | |||
| 639 | defer allocator.free(exec_result.stdout); | 641 | defer allocator.free(exec_result.stdout); |
| 640 | defer allocator.free(exec_result.stderr); | 642 | defer allocator.free(exec_result.stderr); |
| 641 | switch (exec_result.term) { | 643 | switch (exec_result.term) { |
| ... | @@ -657,4 +659,115 @@ pub const TestContext = struct { | ... | @@ -657,4 +659,115 @@ pub const TestContext = struct { |
| 657 | } | 659 | } |
| 658 | } | 660 | } |
| 659 | } | 661 | } |
| 662 | |||
| 663 | fn runInterpreterIfAvailable( | ||
| 664 | self: *TestContext, | ||
| 665 | gpa: *Allocator, | ||
| 666 | node: *std.Progress.Node, | ||
| 667 | case: Case, | ||
| 668 | tmp_dir: std.fs.Dir, | ||
| 669 | bin_name: []const u8, | ||
| 670 | ) !void { | ||
| 671 | const arch = case.target.cpu_arch orelse return; | ||
| 672 | switch (arch) { | ||
| 673 | .spu_2 => return self.runSpu2Interpreter(gpa, node, case, tmp_dir, bin_name), | ||
| 674 | else => return, | ||
| 675 | } | ||
| 676 | } | ||
| 677 | |||
| 678 | fn runSpu2Interpreter( | ||
| 679 | self: *TestContext, | ||
| 680 | gpa: *Allocator, | ||
| 681 | update_node: *std.Progress.Node, | ||
| 682 | case: Case, | ||
| 683 | tmp_dir: std.fs.Dir, | ||
| 684 | bin_name: []const u8, | ||
| 685 | ) !void { | ||
| 686 | const spu = @import("codegen/spu-mk2.zig"); | ||
| 687 | if (case.target.os_tag) |os| { | ||
| 688 | if (os != .freestanding) { | ||
| 689 | std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{}); | ||
| 690 | } | ||
| 691 | } else { | ||
| 692 | std.debug.panic("SPU_2 has no native OS, check the test!", .{}); | ||
| 693 | } | ||
| 694 | |||
| 695 | var interpreter = spu.Interpreter(struct { | ||
| 696 | RAM: [0x10000]u8 = undefined, | ||
| 697 | |||
| 698 | pub fn read8(bus: @This(), addr: u16) u8 { | ||
| 699 | return bus.RAM[addr]; | ||
| 700 | } | ||
| 701 | pub fn read16(bus: @This(), addr: u16) u16 { | ||
| 702 | return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]); | ||
| 703 | } | ||
| 704 | |||
| 705 | pub fn write8(bus: *@This(), addr: u16, val: u8) void { | ||
| 706 | bus.RAM[addr] = val; | ||
| 707 | } | ||
| 708 | |||
| 709 | pub fn write16(bus: *@This(), addr: u16, val: u16) void { | ||
| 710 | std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val); | ||
| 711 | } | ||
| 712 | }){ | ||
| 713 | .bus = .{}, | ||
| 714 | }; | ||
| 715 | |||
| 716 | { | ||
| 717 | var load_node = update_node.start("load", null); | ||
| 718 | load_node.activate(); | ||
| 719 | defer load_node.end(); | ||
| 720 | |||
| 721 | var file = try tmp_dir.openFile(bin_name, .{ .read = true }); | ||
| 722 | defer file.close(); | ||
| 723 | |||
| 724 | const header = try std.elf.readHeader(file); | ||
| 725 | var iterator = header.program_header_iterator(file); | ||
| 726 | |||
| 727 | var none_loaded = true; | ||
| 728 | |||
| 729 | while (try iterator.next()) |phdr| { | ||
| 730 | if (phdr.p_type != std.elf.PT_LOAD) { | ||
| 731 | std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type}); | ||
| 732 | std.process.exit(1); | ||
| 733 | } | ||
| 734 | if (phdr.p_paddr != phdr.p_vaddr) { | ||
| 735 | std.debug.print("Physical address does not match virtual address in ELF header!\n", .{}); | ||
| 736 | std.process.exit(1); | ||
| 737 | } | ||
| 738 | if (phdr.p_filesz != phdr.p_memsz) { | ||
| 739 | std.debug.print("Physical size does not match virtual size in ELF header!\n", .{}); | ||
| 740 | std.process.exit(1); | ||
| 741 | } | ||
| 742 | if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) { | ||
| 743 | std.debug.print("Read less than expected from ELF file!", .{}); | ||
| 744 | std.process.exit(1); | ||
| 745 | } | ||
| 746 | std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr }); | ||
| 747 | none_loaded = false; | ||
| 748 | } | ||
| 749 | if (none_loaded) { | ||
| 750 | std.debug.print("No data found in ELF file!\n", .{}); | ||
| 751 | std.process.exit(1); | ||
| 752 | } | ||
| 753 | } | ||
| 754 | |||
| 755 | var exec_node = update_node.start("execute", null); | ||
| 756 | exec_node.activate(); | ||
| 757 | defer exec_node.end(); | ||
| 758 | |||
| 759 | var blocks: u16 = 1000; | ||
| 760 | const block_size = 1000; | ||
| 761 | while (!interpreter.undefined0) { | ||
| 762 | const pre_ip = interpreter.ip; | ||
| 763 | if (blocks > 0) { | ||
| 764 | blocks -= 1; | ||
| 765 | try interpreter.ExecuteBlock(block_size); | ||
| 766 | if (pre_ip == interpreter.ip) { | ||
| 767 | std.debug.print("Infinite loop detected in SPU II test!\n", .{}); | ||
| 768 | std.process.exit(1); | ||
| 769 | } | ||
| 770 | } | ||
| 771 | } | ||
| 772 | } | ||
| 660 | }; | 773 | }; |
test/stage2/spu-ii.zig created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const TestContext = @import("../../src-self-hosted/test.zig").TestContext; | ||
| 3 | |||
| 4 | const spu = std.zig.CrossTarget{ | ||
| 5 | .cpu_arch = .spu_2, | ||
| 6 | .os_tag = .freestanding, | ||
| 7 | }; | ||
| 8 | |||
| 9 | pub fn addCases(ctx: *TestContext) !void { | ||
| 10 | { | ||
| 11 | var case = ctx.exe("SPU-II Basic Test", spu); | ||
| 12 | case.addCompareOutput( | ||
| 13 | \\fn killEmulator() noreturn { | ||
| 14 | \\ asm volatile ("undefined0"); | ||
| 15 | \\ unreachable; | ||
| 16 | \\} | ||
| 17 | \\ | ||
| 18 | \\export fn _start() noreturn { | ||
| 19 | \\ killEmulator(); | ||
| 20 | \\} | ||
| 21 | , ""); | ||
| 22 | } | ||
| 23 | } | ||
test/stage2/test.zig+2| ... | @@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{ | ... | @@ -26,6 +26,8 @@ const wasi = std.zig.CrossTarget{ |
| 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 |