SPU-II: Implement function calls

This commit is contained in:
Noam Preil 2020-07-21 21:33:53 -04:00 committed by Andrew Kelley
parent d005ff16c6
commit cdefc6acba
2 changed files with 212 additions and 14 deletions

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

View File

@ -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{};