diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index a737b1360dab282128883b472b036cdb0a2cdf57..8ff0d32461ade669218bfb75b23760f629d13546 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 0000000000000000000000000000000000000000..b44d4123bd02f1e6a540abf0c58fcd8264ad3fd1 --- /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{};