Merge branch 'pixelherodev-spu_ii'

closes #6096
This commit is contained in:
Andrew Kelley 2020-08-22 13:38:54 -07:00
commit 4e63cae369
10 changed files with 586 additions and 29 deletions

View File

@ -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,

View File

@ -663,6 +663,9 @@ pub const Target = struct {
renderscript32,
renderscript64,
ve,
// 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 {
return switch (arch) {
@ -761,6 +764,7 @@ pub const Target = struct {
.sparcv9 => ._SPARCV9,
.s390x => ._S390,
.ve => ._NONE,
.spu_2 => ._SPU_2,
};
}
@ -803,6 +807,7 @@ pub const Target = struct {
.renderscript64,
.shave,
.ve,
.spu_2,
=> .Little,
.arc,
@ -827,6 +832,7 @@ pub const Target = struct {
switch (arch) {
.avr,
.msp430,
.spu_2,
=> return 16,
.arc,
@ -1317,12 +1323,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,

View File

@ -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),
@ -1264,6 +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 => {
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;
@ -1349,6 +1355,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| {
@ -1642,6 +1686,19 @@ 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")) {
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", .{});
}
},
.riscv64 => {
for (inst.inputs) |input, i| {
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 {
/// 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 {
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) {
@ -2266,7 +2324,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)
@ -2313,6 +2371,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,

View File

@ -0,0 +1,170 @@
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,
pub fn allocIndex(self: Register) ?u4 {
return null;
}
};
pub const callee_preserved_regs = [_]Register{};

View File

@ -0,0 +1,166 @@
const std = @import("std");
const log = std.log.scoped(.SPU_2_Interpreter);
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 {
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,
/// 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 {
var count: usize = 0;
while (size == null or count < size.?) {
count += 1;
var instruction = @bitCast(Instruction, self.bus.read16(self.ip));
log.debug("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 => {
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
(val0 & 0xFF),
else => return error.Unimplemented,
};
switch (instruction.output) {
.discard => {},
.push => {
self.sp -%= 2;
self.bus.write16(self.sp, output);
},
.jump => {
self.ip = output;
},
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;
}
}
}
};
}

View File

@ -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");
pub 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,
entry_addr: ?u64 = null,
};
pub const File = struct {

View File

@ -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()) {
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()) {
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;
@ -462,12 +460,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 = default_entry_addr,
.p_paddr = 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,
@ -486,13 +485,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 (ptr_size == 2) @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,
.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,
@ -863,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.?];
@ -2151,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) {
@ -2182,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]);
@ -2190,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.ptrWidthBytes();
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);
@ -2221,17 +2218,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,
}
}
@ -2344,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,
@ -2351,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;

View File

@ -583,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.
@ -635,7 +638,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) {
@ -657,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);
}
}
}
}
};

23
test/stage2/spu-ii.zig Normal file
View File

@ -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();
\\}
, "");
}
}

View File

@ -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);