diff --git a/CMakeLists.txt b/CMakeLists.txt index 511a7379..41b06896 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,4 +26,4 @@ find_package(Qt6 COMPONENTS Core Gui Widgets) if (Qt6_FOUND) add_subdirectory(src/frontend) -endif() \ No newline at end of file +endif() diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index aebab03b..e0d59403 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -65,7 +65,7 @@ void Core::Run(float volumeL, float volumeR) { InterruptRaise(mmio.mi, regs, Interrupt::VI); } - for(; cycles < mem.mmio.vi.cyclesPerHalfline; cycles++, frameCycles++) { + for(; cycles < mem.mmio.vi.cyclesPerHalfline;) { u32 taken = cpu->Step(); taken += PopStalledCycles(); static u32 cpuSteps = 0; diff --git a/src/backend/core/JIT.cpp b/src/backend/core/JIT.cpp new file mode 100644 index 00000000..74946b01 --- /dev/null +++ b/src/backend/core/JIT.cpp @@ -0,0 +1,105 @@ +#include +#include + +namespace n64 { +using namespace Xbyak; +JIT::JIT() : CodeGenerator(32*1024*1024, AutoGrow) { } + +void JIT::Reset() { + reset(); + regs.Reset(); + mem.Reset(); +} + +bool JIT::ShouldServiceInterrupt() { + bool interrupts_pending = (regs.cop0.status.im & regs.cop0.cause.interruptPending) != 0; + bool interrupts_enabled = regs.cop0.status.ie == 1; + bool currently_handling_exception = regs.cop0.status.exl == 1; + bool currently_handling_error = regs.cop0.status.erl == 1; + + return interrupts_pending && interrupts_enabled && + !currently_handling_exception && !currently_handling_error; +} + +void JIT::CheckCompareInterrupt() { + regs.cop0.count++; + regs.cop0.count &= 0x1FFFFFFFF; + if(regs.cop0.count == (u64)regs.cop0.compare << 1) { + regs.cop0.cause.ip7 = 1; + UpdateInterrupt(mem.mmio.mi, regs); + } +} + +Fn JIT::Recompile() { + bool stable = true; + bool old_stable = stable; + cycles = 0; + //prologue(); + //mov(rbp, u64(this)); + //mov(rdi, u64(this) + THIS_OFFSET(regs)); + u64 pc = regs.pc; + while(old_stable) { + old_stable = stable; + + cycles++; + CheckCompareInterrupt(); + + // mov(rax, REG(byte, delaySlot)); + // mov(REG(byte, prevDelaySlot), rax); + // mov(REG(byte, delaySlot), 0); + + u32 paddr = 0; + if (!MapVAddr(regs, LOAD, pc, paddr)) { + //mov(rsi, regs.pc); + //emitCall(HandleTLBException); + //mov(rsi, u64(GetTLBExceptionCode(regs.cop0.tlbError, LOAD))); + //CodeGenerator::xor_(rdx, rdx); + //CodeGenerator::xor_(rcx, rcx); + //emitCall(FireException); + //goto _epilogue; + } + + pc += 4; + u32 instr = mem.Read(regs, paddr); + stable = isStable(instr); + Emit(instr); + + if (ShouldServiceInterrupt()) { + //mov(rsi, u64(ExceptionCode::Interrupt)); + //CodeGenerator::xor_(rdx, rdx); + //CodeGenerator::xor_(rcx, rcx); + //push(rax); + //call(FireException); + //goto _epilogue; + } + + //mov(rax, REG(qword, pc)); + //mov(REG(qword, oldPC), rax); + //mov(rax, REG(qword, nextPC)); + //mov(REG(qword, pc), rax); + //CodeGenerator::add(REG(qword, nextPC), 4); + } +_epilogue: + //epilogue(); + //ready(); + //return getCode(); + ir.optimize(); + ir.print(); + exit(1); + return nullptr; +} + +int JIT::Step() { + if(!blocks[BLOCKCACHE_OUTER_INDEX(regs.pc)]) { + blocks[BLOCKCACHE_OUTER_INDEX(regs.pc)] = (Fn*)calloc(BLOCKCACHE_INNER_SIZE, 1); + blocks[BLOCKCACHE_OUTER_INDEX(regs.pc)][BLOCKCACHE_INNER_INDEX(regs.pc)] = Recompile(); + } + + if (!blocks[BLOCKCACHE_OUTER_INDEX(regs.pc)][BLOCKCACHE_INNER_INDEX(regs.pc)]) { + blocks[BLOCKCACHE_OUTER_INDEX(regs.pc)][BLOCKCACHE_INNER_INDEX(regs.pc)] = Recompile(); + } + + //return blocks[BLOCKCACHE_OUTER_INDEX(regs.pc)][BLOCKCACHE_INNER_INDEX(regs.pc)](); + return 1; +} +} diff --git a/src/backend/core/JIT.hpp b/src/backend/core/JIT.hpp new file mode 100644 index 00000000..fdbca9dc --- /dev/null +++ b/src/backend/core/JIT.hpp @@ -0,0 +1,259 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace n64 { +using Fn = int(*)(); +#define THIS_OFFSET(x) ((uintptr_t)(&x) - (uintptr_t)this) +#define GPR_OFFSET(x) ((uintptr_t)®s.gpr[(x)] - (uintptr_t)this) +#define REG_OFFSET(member) ((uintptr_t)®s.member - (uintptr_t)this) +#define GPR(ptr, x) ptr[rdi + GPR_OFFSET(x)] +#define REG(ptr, member) ptr[rdi + REG_OFFSET(member)] +// 4KiB aligned pages +#define BLOCKCACHE_OUTER_SHIFT 12 +#define BLOCKCACHE_PAGE_SIZE (1 << BLOCKCACHE_OUTER_SHIFT) +#define BLOCKCACHE_OUTER_SIZE (0x80000000 >> BLOCKCACHE_OUTER_SHIFT) +// word aligned instructions +#define BLOCKCACHE_INNER_SIZE (BLOCKCACHE_PAGE_SIZE >> 2) +#define BLOCKCACHE_INNER_INDEX(physical) (((physical) & (BLOCKCACHE_PAGE_SIZE - 1)) >> 2) +#define BLOCKCACHE_OUTER_INDEX(physical) ((physical) >> BLOCKCACHE_OUTER_SHIFT) + +struct JIT : BaseCPU, Xbyak::CodeGenerator { + JIT(); + ~JIT() override = default; + int Step() override; + void Reset() override; + friend struct Cop1; + friend struct Cop0; +private: + IR ir{}; + int cycles = 0; + bool ShouldServiceInterrupt() override; + void CheckCompareInterrupt() override; + Fn Recompile(); + + template + void emitMemberCall(T func, void* thisObj) { + T* funcPtr; + auto thisPtr = reinterpret_cast(thisObj); +#ifdef ABI_WINDOWS + static_assert(sizeof(T) == 8, "[JIT]: Invalid size for member function pointer"); + std::memcpy(&funcPtr, &func, sizeof(T)); +#elif defined(ABI_UNIX) + static_assert(sizeof(T) == 16, "[JIT]: Invalid size for member function pointer"); + uintptr_t tmpArr[2]; + std::memcpy(tmpArr, &func, sizeof(T)); + funcPtr = reinterpret_cast(tmpArr[0]); + thisPtr += tmpArr[1]; +#else + Util::panic("Huh?!"); +#endif + + push(rdi); + if(thisPtr == reinterpret_cast(this)) { + mov(rdi, rbp); + } else { + mov(rdi, (uintptr_t)thisPtr); + } + call(funcPtr); + pop(rdi); + } + + template + void emitCall(T func) { + T* funcPtr; +#ifdef ABI_WINDOWS + std::memcpy(&funcPtr, &func, sizeof(T)); +#elif defined(ABI_UNIX) + uintptr_t tmpArr[2]; + std::memcpy(tmpArr, &func, sizeof(T)); + funcPtr = reinterpret_cast(tmpArr[0]); +#else + Util::panic("Huh?!"); +#endif + + push(rdi); + call(funcPtr); + pop(rdi); + } + + bool isStable(u32 instr) { + u8 mask = (instr >> 26) & 0x3f; + switch(mask) { + case SPECIAL: + mask = instr & 0x3f; + switch(mask) { + case JR ... JALR: + case SYSCALL: case BREAK: + case TGE ... TNE: + return false; + default: return true; + } + case REGIMM: + case J ... BGTZ: + case BEQL ... BGTZL: + return false; + case COP1: + mask = (instr >> 16) & 0x1f; + if(mask >= 0 && mask <= 3) { + return false; + } + default: return true; + } + } + + FORCE_INLINE void prologue() { + const Xbyak::Reg64 allRegs[]{rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15}; + for(auto r : allRegs) { + push(r); + } + } + + FORCE_INLINE void epilogue() { + const Xbyak::Reg64 allRegs[]{r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rbp, rsp, rbx, rdx, rcx, rax}; + for(auto r : allRegs) { + pop(r); + } + mov(rax, cycles); + ret(); + } + + Fn* blocks[BLOCKCACHE_OUTER_SIZE]{}; + + std::vector Serialize() override { return {}; } + void Deserialize(const std::vector&) override { } + + void cop2Decode(u32); + void special(u32); + void regimm(u32); + void Emit(u32); + void add(u32); + void addu(u32); + void addi(u32); + void addiu(u32); + void andi(u32); + void and_(u32); + Entry::Operand branch(u32); + void bltz(u32); + void bgez(u32); + void bltzl(u32); + void bgezl(u32); + void bltzal(u32); + void bgezal(u32); + void bltzall(u32); + void bgezall(u32); + void beq(u32); + void bne(u32); + void blez(u32); + void bgtz(u32); + void beql(u32); + void bnel(u32); + void blezl(u32); + void bgtzl(u32); + void dadd(u32); + void daddu(u32); + void daddi(u32); + void daddiu(u32); + void ddiv(u32); + void ddivu(u32); + void div(u32); + void divu(u32); + void dmult(u32); + void dmultu(u32); + void dsll(u32); + void dsllv(u32); + void dsll32(u32); + void dsra(u32); + void dsrav(u32); + void dsra32(u32); + void dsrl(u32); + void dsrlv(u32); + void dsrl32(u32); + void dsub(u32); + void dsubu(u32); + void j(u32); + void jr(u32); + void jal(u32); + void jalr(u32); + void lui(u32); + void lbu(u32); + void lb(u32); + void ld(u32); + void ldl(u32); + void ldr(u32); + void lh(u32); + void lhu(u32); + void ll(u32); + void lld(u32); + void lw(u32); + void lwl(u32); + void lwu(u32); + void lwr(u32); + void mfhi(u32); + void mflo(u32); + void mult(u32); + void multu(u32); + void mthi(u32); + void mtlo(u32); + void nor(u32); + void sb(u32); + void sc(u32); + void scd(u32); + void sd(u32); + void sdl(u32); + void sdr(u32); + void sh(u32); + void sw(u32); + void swl(u32); + void swr(u32); + void slti(u32); + void sltiu(u32); + void slt(u32); + void sltu(u32); + void sll(u32); + void sllv(u32); + void sub(u32); + void subu(u32); + void sra(u32); + void srav(u32); + void srl(u32); + void srlv(u32); + void tgei(u32); + void tgeiu(u32); + void tlti(u32); + void tltiu(u32); + void teqi(u32); + void tnei(u32); + void tge(u32); + void tgeu(u32); + void tlt(u32); + void tltu(u32); + void teq(u32); + void tne(u32); + void or_(u32); + void ori(u32); + void xor_(u32); + void xori(u32); + + void mtc0(u32); + void dmtc0(u32); + void mfc0(u32); + void dmfc0(u32); + void eret(); + + void tlbr(); + void tlbw(int); + void tlbp(); + + void mtc2(u32); + void mfc2(u32); + void dmtc2(u32); + void dmfc2(u32); + void ctc2(u32); + void cfc2(u32); +}; +} diff --git a/src/backend/core/JIT/IR.cpp b/src/backend/core/JIT/IR.cpp new file mode 100644 index 00000000..81aab37d --- /dev/null +++ b/src/backend/core/JIT/IR.cpp @@ -0,0 +1,241 @@ +#include +#include +#include +#include + +namespace n64 { +template <> struct fmt::formatter : formatter { + auto format(Entry e, format_context& ctx) const { + std::string op = "Unknown"; + switch (e.op) { + case Entry::MOV: op = "MOV"; break; + case Entry::ADD: op = "ADD"; break; + case Entry::SUB: op = "SUB"; break; + case Entry::UMUL: op = "UMUL"; break; + case Entry::SMUL: op = "SMUL"; break; + case Entry::DIV: op = "DIV"; break; + case Entry::AND: op = "AND"; break; + case Entry::NOR: op = "NOR"; break; + case Entry::XOR: op = "XOR"; break; + case Entry::OR: op = "OR"; break; + case Entry::SRL: op = "SRL"; break; + case Entry::SLL: op = "SLL"; break; + case Entry::SRA: op = "SRA"; break; + case Entry::LOADS8: op = "LOADS8"; break; + case Entry::LOADS8_SHIFT: op = "LOADS8_SHIFT"; break; + case Entry::STORE8: op = "STORE8"; break; + case Entry::STORE8_SHIFT: op = "STORE8_SHIFT"; break; + case Entry::LOADS16: op = "LOADS16"; break; + case Entry::LOADS16_SHIFT: op = "LOADS16_SHIFT"; break; + case Entry::STORE16: op = "STORE16"; break; + case Entry::STORE16_SHIFT: op = "STORE16_SHIFT"; break; + case Entry::LOADS32: op = "LOADS32"; break; + case Entry::LOADS32_SHIFT: op = "LOADS32_SHIFT"; break; + case Entry::STORE32: op = "STORE32"; break; + case Entry::STORE32_SHIFT: op = "STORE32_SHIFT"; break; + case Entry::LOADS64: op = "LOADS64"; break; + case Entry::LOADS64_SHIFT: op = "LOADS64_SHIFT"; break; + case Entry::STORE64: op = "STORE64"; break; + case Entry::STORE64_SHIFT: op = "STORE64_SHIFT"; break; + case Entry::LOADU8: op = "LOADU8"; break; + case Entry::LOADU8_SHIFT: op = "LOADU8_SHIFT"; break; + case Entry::LOADU16: op = "LOADU16"; break; + case Entry::LOADU16_SHIFT: op = "LOADU16_SHIFT"; break; + case Entry::LOADU32: op = "LOADU32"; break; + case Entry::LOADU32_SHIFT: op = "LOADU32_SHIFT"; break; + case Entry::LOADU64: op = "LOADU64"; break; + case Entry::LOADU64_SHIFT: op = "LOADU64_SHIFT"; break; + case Entry::BRANCH: op = "BRANCH"; break; + case Entry::JUMP: op = "JUMP"; break; + case Entry::MTC0: op = "MTC0"; break; + case Entry::MFC0: op = "MFC0"; break; + case Entry::SLT: op = "SLT"; break; + } + + bool put_comma = false; + op += " "; + if (e.dst.isReg()) { + if (e.dst.index_or_imm.has_value()) { + std::string dst = fmt::format("R{}", e.dst.index_or_imm.value()); + op += dst; + put_comma = true; + } + } else if(e.dst.isImm()) { + std::string dst = fmt::format("0x{:0X}", e.dst.index_or_imm.value()); + op += dst; + put_comma = true; + } else if(e.dst.type == Entry::Operand::PC64) { + std::string dst = fmt::format("PC"); + op += dst; + put_comma = true; + } else { + if(e.dst.type != Entry::Operand::NONE) { + std::string dst = fmt::format("(0x{:0X})", e.dst.index_or_imm.value()); + op += dst; + put_comma = true; + } + } + + if (e.bOffs.index_or_imm.has_value()) { + std::string dst; + if (e.bOffs.isReg()) { + dst = fmt::format("R{}", e.bOffs.index_or_imm.value()); + } else if (e.bOffs.isImm()) { + dst = fmt::format("0x{:0X}", e.bOffs.index_or_imm.value()); + } + op += dst; + put_comma = true; + } + + if (e.op1.isReg()) { + if (e.op1.index_or_imm.has_value()) { + std::string op1 = fmt::format("R{}", e.op1.index_or_imm.value()); + if(put_comma) { + op += ", "; + } + op += op1; + put_comma = true; + } + } else if(e.op1.isImm()) { + if (e.op1.index_or_imm.has_value()) { + std::string op1 = fmt::format("0x{:0X}", e.op1.index_or_imm.value()); + if(put_comma) { + op += ", "; + } + op += op1; + put_comma = true; + } + } else if (e.dst.type == Entry::Operand::PC64) { + std::string dst = fmt::format("PC"); + if (put_comma) { + op += ", "; + } + op += dst; + put_comma = true; + } else { + if (e.op1.index_or_imm.has_value()) { + std::string op1 = fmt::format("(0x{:0X})", e.op1.index_or_imm.value()); + if(put_comma) { + op += ", "; + } + op += op1; + put_comma = false; + } + } + + if (e.branchCond.has_value()) { + put_comma = false; + op += " "; + switch (e.branchCond.value()) { + case Entry::AL: op += " "; break; + case Entry::EQ: op += "== "; break; + case Entry::NE: op += "!= "; break; + case Entry::LT: op += "< "; break; + case Entry::GT: op += "> "; break; + case Entry::LE: op += "<= "; break; + case Entry::GE: op += ">= "; break; + } + } + + if (e.op2.isReg()) { + if (e.op2.index_or_imm.has_value()) { + std::string op2 = fmt::format("R{}", e.op2.index_or_imm.value()); + if(put_comma) { + op += ", "; + } + op += op2; + } + } else { + if (e.op2.index_or_imm.has_value()) { + std::string op2 = fmt::format("0x{:0X}", e.op2.index_or_imm.value()); + if(put_comma) { + op += ", "; + } + op += op2; + } + } + + op += '\n'; + return formatter::format(op, ctx); + } +}; + +Entry::Entry(Opcode op, Operand dst, Operand op1, Operand op2) + : op(op), dst(dst), op1(op1), op2(op2) {} + +Entry::Entry(Opcode op, Operand op1, Operand op2) + : op(op), op1(op1), op2(op2) {} + +Entry::Entry(Opcode op, Operand bOffs, Operand op1, std::optional bc, Operand op2) + : op(op), bOffs(bOffs), op1(op1), branchCond(bc), op2(op2) {} + +Entry::Entry(Opcode op, Operand bOffs) +: op(op), bOffs(bOffs) {} + +Entry::Entry(Opcode op, Operand dst, Operand op1, Operand op2, Shift s) + : op(op), dst(dst), op1(op1), op2(op2), shift(s) {} + +void IR::push(const Entry& e) { + code.push_back(e); +} + +std::vector IR::constant_propagation(std::vector& code_) { + std::vector optimized{}; + + return optimized; +} + +std::vector IR::dead_code_elimination(std::vector& code_) { + std::vector optimized{}; + for(const auto& i : code_) { + bool isOp1Reg = i.op1.isReg(); + bool isOp2Reg = i.op2.isReg(); + bool isDstReg = i.dst.isReg(); + + // check for operations like "add rx, rx, 0" or "add r0, anything" + if(isDstReg) { + bool isDstR0 = i.dst.isReg() && i.dst.index_or_imm.has_value() && i.dst.index_or_imm.value() == 0; + bool areDstAndOp1Same = i.dst.isReg() && i.op1.isReg() && i.dst.index_or_imm.has_value() && i.op1.index_or_imm.has_value() && i.op1.index_or_imm.value() == i.dst.index_or_imm.value(); + bool areDstAndOp2Same = i.dst.isReg() && i.op2.isReg() && i.dst.index_or_imm.has_value() && i.op2.index_or_imm.has_value() && i.op2.index_or_imm.value() == i.dst.index_or_imm.value(); + if (isDstR0) continue; + if (i.canDoDCE()) { + if (areDstAndOp1Same) { + if (i.op2.isImm() && i.op2.index_or_imm.value() == 0) continue; + } + + if (areDstAndOp2Same) { + if (i.op1.isImm() && i.op1.index_or_imm.value() == 0) continue; + } + } + } + + optimized.push_back(i); + } + + return optimized; +} + +void IR::optimize() { + std::vector optimized{}; + + while (optimized.size() < code.size()) { + optimized = dead_code_elimination(code); + //optimized = constant_propagation(optimized); + code = optimized; + } +} + +void IR::print() { + for(auto e : code) { + fmt::print("{}", e); + } +} + +auto IR::begin() { + return code.begin(); +} + +auto IR::end() { + return code.end(); +} +} \ No newline at end of file diff --git a/src/backend/core/JIT/IR.hpp b/src/backend/core/JIT/IR.hpp new file mode 100644 index 00000000..4cd60a82 --- /dev/null +++ b/src/backend/core/JIT/IR.hpp @@ -0,0 +1,98 @@ +#pragma once +#include +#include +#include +#include + +namespace n64 { +struct Entry { + enum : u16 { + LINK = 0x100, + LIKELY = 0x200, + REGISTER = 0x400, + SET_LLBIT = 0x800, + UNSET_LLBIT = 0x1000, + }; + + enum Shift { + LEFT, RIGHT + }; + + enum Opcode : u16 { + MOV, SLT, ADD, SUB, UMUL, SMUL, DIV, AND, NOR, + XOR, OR, SRL, SLL, SRA, + LOADS8, LOADS8_SHIFT, STORE8, STORE8_SHIFT, + LOADS16, LOADS16_SHIFT, STORE16, STORE16_SHIFT, + LOADS32, LOADS32_SHIFT, STORE32, STORE32_SHIFT, + LOADS64, LOADS64_SHIFT, STORE64, STORE64_SHIFT, + LOADU8, LOADU8_SHIFT, + LOADU16, LOADU16_SHIFT, + LOADU32, LOADU32_SHIFT, + LOADU64, LOADU64_SHIFT, + BRANCH, JUMP, MTC0, MFC0 + } op; + + struct Operand { + enum Type { + NONE, REG_F64, REG_F32, IMM_F64, IMM_F32, + REG_S64, REG_S32, REG_U64, REG_U32, REG_U5, IMM_S16, + IMM_S32, IMM_S64, IMM_U16, IMM_U32, IMM_U64, IMM_U5, + MEM_U8, MEM_U16, MEM_U32, MEM_U64, PC64, NEXTPC64, + LO, HI + } type = NONE; + + bool isReg() const { + return type == REG_S64 || type == REG_F32 || type == REG_F64 || type == REG_S32 + || type == REG_U64 || type == REG_U32 || type == REG_U5; + } + + bool isImm() const { + return type == IMM_S64 || type == IMM_U16 || type == IMM_S16 || + type == IMM_F32 || type == IMM_F64 || type == IMM_S32 || + type == IMM_U64 || type == IMM_U32 || type == IMM_U5; + } + + bool isMem() const { + return type == MEM_U8 || type == MEM_U16 || type == MEM_U32 || type == MEM_U64; + } + + std::optional index_or_imm = std::nullopt; + + Operand() = default; + Operand(Type t, std::optional imm = std::nullopt) + : type(t), index_or_imm(imm) {} + } dst, op1, op2; + + bool canDoDCE() const { + return op == ADD || op == OR || op == SRL || op == SLL || op == SRA; + } + + [[nodiscard]] const Operand& GetDst() const { return dst; } + + enum BranchCond { + AL, EQ, NE, LT, GT, LE, GE + }; + + std::optional branchCond = std::nullopt; + std::optional shift = std::nullopt; + Operand bOffs = Operand::NONE; + + Entry(Opcode op, Operand dst, Operand op1, Operand op2); + Entry(Opcode op, Operand op1, Operand op2); + Entry(Opcode op, Operand bOffs, Operand op1, std::optional bc, Operand op2); + Entry(Opcode op, Operand bOffs); + Entry(Opcode op, Operand dst, Operand op1, Operand op2, Shift s); +}; + +struct IR { + void push(const Entry&); + auto begin(); + auto end(); + void print(); + void optimize(); +private: + std::vector constant_propagation(std::vector&); + std::vector dead_code_elimination(std::vector&); + std::vector code{}; +}; +} \ No newline at end of file diff --git a/src/backend/core/JIT/IR/Opcode.hpp b/src/backend/core/JIT/IR/Opcode.hpp new file mode 100644 index 00000000..9bfc90bc --- /dev/null +++ b/src/backend/core/JIT/IR/Opcode.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include + +namespace n64 { +enum class IROpcodeClass { + StorePC, Add, Special, Regimm, COP0, COP1 +}; + +struct IROpcode { + virtual ~IROpcode() = default; + virtual auto GetClass() const -> IROpcodeClass = 0; + virtual auto Reads (IRVariable const& var) -> bool = 0; + virtual auto Writes(IRVariable const& var) -> bool = 0; + virtual void Repoint(IRVariable const& var_old, IRVariable const& var_new) = 0; + virtual void PropagateConstant(IRVariable const& var, IRConstant const& constant) {} + virtual auto ToString() -> std::string = 0; +}; + +template +struct IROpcodeBase : IROpcode { + auto GetClass() const -> IROpcodeClass override { return _class; } +}; + +template +struct IRBinaryOpBase : IROpcodeBase<_class> { + IRBinaryOpBase(IRVariable const& result, IRVariable lhs, IRAnyRef rhs) + : result(const_cast(result)), lhs(lhs), rhs(rhs) {} + + IRVariable& result; + IRVarRef lhs; + IRAnyRef rhs; + + auto Reads(IRVariable const& var) -> bool override { + return &lhs.Get() == &var || + (rhs.IsVariable() && (&rhs.GetVar() == &var)); + } + + auto Writes(IRVariable const& var) -> bool override { + return result.HasValue() && (&result == &var); + } + + void Repoint( + IRVariable const& var_old, + IRVariable const& var_new + ) override { + // TODO: make this reusable? + if (result.HasValue() && (&result == &var_old)) { + result = var_new; + } + + lhs.Repoint(var_old, var_new); + rhs.Repoint(var_old, var_new); + } + + void PropagateConstant( + IRVariable const& var, + IRConstant const& constant + ) override { + rhs.PropagateConstant(var, constant); + } +}; + +struct IRStorePC final : IROpcodeBase { + IRStorePC(IRAnyRef val) : val(val) {} + + IRAnyRef val; + + auto Reads(IRVariable const& var) -> bool override { + if(val.IsVariable()) { + return &var == &val.GetVar(); + } + return false; + } + + auto Writes(IRVariable const& var) -> bool override { + return true; + } + + void Repoint( + IRVariable const& var_old, + IRVariable const& var_new + ) override { + } + + auto ToString() -> std::string override { + return fmt::format("str_pc {}", std::to_string(val)); + } +}; + +struct IRAdd final : IRBinaryOpBase { + using IRBinaryOpBase::IRBinaryOpBase; + + auto ToString() -> std::string override { + return fmt::format( + "add {}, {}, {}", + std::to_string(result), + std::to_string(lhs), + std::to_string(rhs) + ); + } +}; +} \ No newline at end of file diff --git a/src/backend/core/JIT/IR/Register.hpp b/src/backend/core/JIT/IR/Register.hpp new file mode 100644 index 00000000..e42adccb --- /dev/null +++ b/src/backend/core/JIT/IR/Register.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace n64 { +struct IRGuestReg { + IRGuestReg(u8 reg) : reg(reg) {} + + /// The ARM general purpose register + const u8 reg; +}; +} \ No newline at end of file diff --git a/src/backend/core/JIT/IR/Value.hpp b/src/backend/core/JIT/IR/Value.hpp new file mode 100644 index 00000000..a801fd7e --- /dev/null +++ b/src/backend/core/JIT/IR/Value.hpp @@ -0,0 +1,145 @@ +#pragma once +#include + +namespace n64 { +enum IRPrimitive { + Uint32, Sint32, Uint64, Sint64 +}; + +struct IRVariable { + IRVariable(IRPrimitive type, const u32 id, char const* const label) : type(type), id(id), label(label), assigned(false) {} + IRPrimitive type; + u32 id; + char const* label; + bool assigned; + + bool HasValue() const { return assigned; } + bool IsNull() const { return !assigned; } +}; + +struct IRConstant { + IRConstant() {} + IRConstant(IRPrimitive type, u64 value) : type(type), value(value) {} + + u64 value = 0; +private: + IRPrimitive type = Uint64; +}; + +struct IRAnyRef { + IRAnyRef() {} + IRAnyRef(IRVariable const& variable) : type(Type::Variable), var(&variable) {} + IRAnyRef(IRConstant const& constant) : type(Type::Constant), constant(constant) {} + + auto operator=(IRAnyRef const& other) -> IRAnyRef& { + type = other.type; + if (IsConstant()) { + constant = other.constant; + } else { + var = other.var; + } + return *this; + } + + bool IsNull() const { return type == Type::Null; } + bool IsVariable() const { return type == Type::Variable; } + bool IsConstant() const { return type == Type::Constant; } + + auto GetVar() const -> IRVariable const& { + if (!IsVariable()) { + Util::panic("called GetVar() but value is a constant or null"); + } + return *var; + } + + auto GetConst() const -> IRConstant const& { + if (!IsConstant()) { + Util::panic("called GetConst() but value is a variable or null"); + } + return constant; + } + + void Repoint(IRVariable const& var_old, IRVariable const& var_new) { + if (IsVariable() && (&GetVar() == &var_old)) { + var = &var_new; + } + } + + void PropagateConstant(IRVariable const& var, IRConstant const& constant) { + if (IsVariable() && (&GetVar() == &var)) { + type = Type::Constant; + this->constant = constant; + } + } +private: + enum Type { + Null, Variable, Constant + }; + + Type type; + + union { + IRVariable const* var; + IRConstant constant; + }; +}; + +struct IRVarRef { + IRVarRef(IRVariable const& var) : p_var(&var) {} + + auto Get() const -> IRVariable const& { + return *p_var; + } + + void Repoint(IRVariable const& var_old, IRVariable const& var_new) { + if (&var_old == p_var) { + p_var = &var_new; + } + } +private: + IRVariable const* p_var; +}; +} + + +namespace std { +inline auto to_string(n64::IRPrimitive data_type) -> std::string { + switch (data_type) { + case n64::IRPrimitive::Uint32: + return "u32"; + case n64::IRPrimitive::Sint32: + return "s32"; + case n64::IRPrimitive::Uint64: + return "u64"; + case n64::IRPrimitive::Sint64: + return "s64"; + default: + return "???"; + } +} + +inline auto to_string(n64::IRVariable const &variable) -> std::string { + if (variable.label) { + return fmt::format("var{}_{}", variable.id, variable.label); + } + return fmt::format("var{}", variable.id); +} + +inline auto to_string(n64::IRConstant const &constant) -> std::string { + return fmt::format("0x{:0X}", constant.value); +} + +inline auto to_string(n64::IRAnyRef const &value) -> std::string { + if (value.IsNull()) { + return "(null)"; + } + if (value.IsConstant()) { + return std::to_string(value.GetConst()); + } + return std::to_string(value.GetVar()); +} + +inline auto to_string(n64::IRVarRef const &variable) -> std::string { + return std::to_string(variable.Get()); +} +} \ No newline at end of file diff --git a/src/backend/core/JIT/instructions.cpp b/src/backend/core/JIT/instructions.cpp new file mode 100644 index 00000000..dacfadac --- /dev/null +++ b/src/backend/core/JIT/instructions.cpp @@ -0,0 +1,925 @@ +#include + +#define check_address_error(mask, vaddr) (((!regs.cop0.is_64bit_addressing) && (s32)(vaddr) != (vaddr)) || (((vaddr) & (mask)) != 0)) +#define check_signed_overflow(op1, op2, res) (((~((op1) ^ (op2)) & ((op1) ^ (res))) >> ((sizeof(res) * 8) - 1)) & 1) +#define check_signed_underflow(op1, op2, res) (((((op1) ^ (op2)) & ((op1) ^ (res))) >> ((sizeof(res) * 8) - 1)) & 1) + +namespace n64 { +void JIT::add(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_U32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_U32, u8(RT(instr)) }; + Entry e(Entry::ADD, dst, op1, op2); + ir.push(e); +} + +void JIT::addu(u32 instr) { + add(instr); +} + +void JIT::addi(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_U32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_S16, s16(instr) }; + Entry e(Entry::ADD, dst, op1, op2); + ir.push(e); +} + +void JIT::addiu(u32 instr) { + addi(instr); +} + +void JIT::dadd(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_U64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_U64, u8(RT(instr)) }; + Entry e(Entry::ADD, dst, op1, op2); + ir.push(e); +} + +Entry::Operand JIT::branch(u32 instr) { + auto addr = Entry::Operand{ Entry::Operand::IMM_S64, u64(s64(s16(instr))) << 2 }; + return addr; +} + +void JIT::bltz(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + Entry e(Entry::BRANCH, dst, op1, Entry::BranchCond::LT, op2); + ir.push(e); +} + +void JIT::bgez(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + Entry e(Entry::BRANCH, dst, op1, Entry::BranchCond::GE, op2); + ir.push(e); +} + +void JIT::bltzl(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::LT, op2); + ir.push(e); +} + +void JIT::bgezl(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::GE, op2); + ir.push(e); +} + +void JIT::bltzal(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LINK); + Entry e(opc, dst, op1, Entry::BranchCond::LT, op2); + ir.push(e); +} + +void JIT::bgezal(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LINK); + Entry e(opc, dst, op1, Entry::BranchCond::GE, op2); + ir.push(e); +} + +void JIT::bltzall(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LINK | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::LT, op2); + ir.push(e); +} + +void JIT::bgezall(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LINK | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::GE, op2); + ir.push(e); +} + +void JIT::beq(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + Entry e(Entry::BRANCH, dst, op1, Entry::BranchCond::EQ, op2); + ir.push(e); +} + +void JIT::bne(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + Entry e(Entry::BRANCH, dst, op1, Entry::BranchCond::NE, op2); + ir.push(e); +} + +void JIT::blez(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_S64, 0 }; + Entry e(Entry::BRANCH, dst, op1, Entry::BranchCond::LE, op2); + ir.push(e); +} + +void JIT::bgtz(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_S64, 0 }; + Entry e(Entry::BRANCH, dst, op1, Entry::BranchCond::GT, op2); + ir.push(e); +} + +void JIT::beql(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::EQ, op2); + ir.push(e); +} + +void JIT::bnel(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::NE, op2); + ir.push(e); +} + +void JIT::blezl(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_S64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::LE, op2); + ir.push(e); +} + +void JIT::bgtzl(u32 instr) { + auto dst = branch(instr); + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_S64, 0 }; + auto opc = Entry::Opcode(u16(Entry::BRANCH) | Entry::LIKELY); + Entry e(opc, dst, op1, Entry::BranchCond::GT, op2); + ir.push(e); +} + +void JIT::daddu(u32 instr) { + dadd(instr); +} + +void JIT::daddi(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_S16, s16(instr) }; + Entry e(Entry::ADD, dst, op1, op2); + ir.push(e); +} + +void JIT::daddiu(u32 instr) { + daddi(instr); +} + +void JIT::div(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_S32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S32, u8(RT(instr)) }; + Entry e(Entry::DIV, op1, op2); + ir.push(e); +} + +void JIT::divu(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_U32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_U32, u8(RT(instr)) }; + Entry e(Entry::DIV, op1, op2); + ir.push(e); +} + +void JIT::ddiv(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + Entry e(Entry::DIV, op1, op2); + ir.push(e); +} + +void JIT::ddivu(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_U64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_U64, u8(RT(instr)) }; + Entry e(Entry::DIV, op1, op2); + ir.push(e); +} + +void JIT::lui(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::IMM_S16, u64(s16(instr)) << 16 }; + Entry e(Entry::LOADS64, dst, op1); + ir.push(e); +} + +void JIT::lb(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U8, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr))}; + Entry e(Entry::LOADS8, dst, op1, op2); + ir.push(e); +} + +void JIT::lh(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U16, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS16, dst, op1, op2); + ir.push(e); +} + +void JIT::lw(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS32, dst, op1, op2); + ir.push(e); +} + +void JIT::ll(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto opc = Entry::Opcode(u16(Entry::LOADS64) | Entry::SET_LLBIT); + Entry e(opc, dst, op1, op2); + ir.push(e); +} + +void JIT::lwl(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS32_SHIFT, dst, op1, op2, Entry::LEFT); + ir.push(e); +} + +void JIT::lwr(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS32_SHIFT, dst, op1, op2, Entry::RIGHT); + ir.push(e); +} + +void JIT::ld(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U64, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS64, dst, op1, op2); + ir.push(e); +} + +void JIT::lld(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto opc = Entry::Opcode(u16(Entry::Opcode::LOADS64) | Entry::SET_LLBIT); + Entry e(opc, dst, op1, op2); + ir.push(e); +} + +void JIT::ldl(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS64_SHIFT, dst, op1, op2, Entry::LEFT); + ir.push(e); +} + +void JIT::ldr(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADS64_SHIFT, dst, op1, op2, Entry::RIGHT); + ir.push(e); +} + +void JIT::lbu(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U8, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADU8, dst, op1, op2); + ir.push(e); +} + +void JIT::lhu(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U16, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADU16, dst, op1, op2); + ir.push(e); +} + +void JIT::lwu(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::LOADU16, dst, op1, op2); + ir.push(e); +} + +void JIT::sb(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U8, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE8, dst, op1, op2); + ir.push(e); +} + +void JIT::sc(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto opc = Entry::Opcode(u16(Entry::STORE32) | Entry::SET_LLBIT); + Entry e(opc, dst, op1, op2); + ir.push(e); +} + +void JIT::scd(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto opc = Entry::Opcode(u16(Entry::STORE64) | Entry::SET_LLBIT); + Entry e(opc, dst, op1, op2); + ir.push(e); +} + +void JIT::sh(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U16, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE16, dst, op1, op2); + ir.push(e); +} + +void JIT::sw(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE32, dst, op1, op2); + ir.push(e); +} + +void JIT::sd(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U64, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE64, dst, op1, op2); + ir.push(e); +} + +void JIT::sdl(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U64, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE64, dst, op1, op2, Entry::LEFT); + ir.push(e); +} + +void JIT::sdr(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U64, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE64, dst, op1, op2, Entry::RIGHT); + ir.push(e); +} + +void JIT::swl(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE32, dst, op1, op2, Entry::LEFT); + ir.push(e); +} + +void JIT::swr(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::MEM_U32, s16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::STORE32, dst, op1, op2, Entry::RIGHT); + ir.push(e); +} + +void JIT::ori(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::IMM_U16, u16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::OR, dst, op1, op2); + ir.push(e); +} + +void JIT::or_(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::OR, dst, op1, op2); + ir.push(e); +} + +void JIT::nor(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::NOR, dst, op1, op2); + ir.push(e); +} + +void JIT::j(u32 instr) { + auto dst = Entry::Operand{Entry::Operand::IMM_S64}; + auto op1 = Entry::Operand{Entry::Operand::PC64}; + auto op2 = Entry::Operand{Entry::Operand::IMM_S64, ~0xfffffff}; + Entry and_(Entry::AND, dst, op1, op2); + ir.push(and_); + op2 = Entry::Operand{Entry::Operand::IMM_S64, (instr & 0x3ffffff) << 2}; + Entry or_(Entry::OR, dst, dst, op2); + ir.push(or_); + Entry e(Entry::BRANCH, or_.GetDst()); + ir.push(e); +} + +void JIT::jal(u32 instr) { + Entry link(Entry::MOV, + Entry::Operand{Entry::Operand::REG_S64, 31}, + Entry::Operand{Entry::Operand::NEXTPC64}); + ir.push(link); + j(instr); +} + +void JIT::jalr(u32 instr) { + auto addr = Entry::Operand{Entry::Operand::REG_U64, RS(instr)}; + Entry e(Entry::BRANCH, addr); + Entry link(Entry::MOV, + Entry::Operand{Entry::Operand::REG_S64, RD(instr)}, + Entry::Operand{Entry::Operand::PC64}); + ir.push(link); + j(instr); +} + +void JIT::slti(u32 instr) { + Entry e(Entry::SLT, + { Entry::Operand::REG_U5, RT(instr) }, + { Entry::Operand::REG_S64, RS(instr) }, + { Entry::Operand::IMM_S64, s64(s16(instr)) }); + ir.push(e); +} + +void JIT::sltiu(u32 instr) { + Entry e(Entry::SLT, + { Entry::Operand::REG_U5, RT(instr) }, + { Entry::Operand::REG_U64, RS(instr) }, + { Entry::Operand::IMM_U64, u64(s64(s16(instr))) }); + ir.push(e); +} + +void JIT::slt(u32 instr) { + Entry e(Entry::SLT, + { Entry::Operand::REG_U5, RD(instr) }, + { Entry::Operand::REG_S64, RS(instr) }, + { Entry::Operand::REG_S64, RT(instr) }); + ir.push(e); +} + +void JIT::sltu(u32 instr) { + Entry e(Entry::SLT, + { Entry::Operand::REG_U5, RD(instr) }, + { Entry::Operand::REG_U64, RS(instr) }, + { Entry::Operand::REG_U64, RT(instr) }); + ir.push(e); +} + +void JIT::xori(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::IMM_U16, u16(instr)}; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::XOR, dst, op1, op2); + ir.push(e); +} + +void JIT::xor_(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::XOR, dst, op1, op2); + ir.push(e); +} + +void JIT::andi(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::IMM_U16, u16(instr) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::AND, dst, op1, op2); + ir.push(e); +} + +void JIT::and_(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + Entry e(Entry::AND, dst, op1, op2); + ir.push(e); +} + +void JIT::sll(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::IMM_U5, std::nullopt }; + Entry e(Entry::SLL, dst, op1, op2); + ir.push(e); +} + +void JIT::sllv(u32 instr) { + if (RD(instr) != 0) [[likely]] { + mov(rcx, GPR(qword, RS(instr))); + CodeGenerator::and_(cl, 0x1F); + mov(rax, GPR(qword, RT(instr))); + sal(rax, cl); + movsxd(rcx, eax); + mov(GPR(qword, RD(instr)), rcx); + } +} + +void JIT::dsll32(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f) + 32; + mov(rax, GPR(qword, RT(instr))); + sal(rax, sa); + mov(GPR(qword, RT(instr)), rax); + } +} + +void JIT::dsll(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f); + mov(rax, GPR(qword, RT(instr))); + sal(rax, sa); + mov(GPR(qword, RT(instr)), rax); + } +} + +void JIT::dsllv(u32 instr) { + if (RD(instr) != 0) [[likely]] { + mov(rcx, GPR(qword, RS(instr))); + CodeGenerator::and_(cl, 63); + mov(rax, GPR(qword, RT(instr))); + sal(rax, cl); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::srl(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f); + mov(rax, GPR(qword, RT(instr))); + CodeGenerator::shr(rax, sa); + movsxd(rcx, eax); + mov(GPR(qword, RD(instr)), rcx); + } +} + +void JIT::srlv(u32 instr) { + if (RD(instr) != 0) [[likely]] { + mov(rcx, GPR(qword, RS(instr))); + CodeGenerator::and_(cl, 0x1F); + mov(rax, GPR(qword, RT(instr))); + shr(rax, cl); + movsxd(rcx, eax); + mov(GPR(qword, RD(instr)), rcx); + } +} + +void JIT::tgei(u32) { + +} + +void JIT::tgeiu(u32) { + +} + +void JIT::tlti(u32) { + +} + +void JIT::tltiu(u32) { + +} + +void JIT::teqi(u32) { + +} + +void JIT::tnei(u32) { + +} + +void JIT::tge(u32) { + +} + +void JIT::tgeu(u32) { + +} + +void JIT::tlt(u32) { + +} + +void JIT::tltu(u32) { + +} + +void JIT::teq(u32) { + +} + +void JIT::tne(u32) { + +} + +void JIT::dsrl(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f); + mov(rax, GPR(qword, RT(instr))); + CodeGenerator::shr(rax, sa); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::dsrlv(u32 instr) { + if (RD(instr) != 0) [[likely]] { + mov(rcx, GPR(qword, RS(instr))); + CodeGenerator::and_(cl, 63); + mov(rax, GPR(qword, RT(instr))); + shr(rax, cl); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::dsrl32(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f) + 32; + mov(rax, GPR(qword, RT(instr))); + CodeGenerator::shr(rax, sa); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::sra(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f); + mov(rax, GPR(qword, RT(instr))); + sar(rax, sa); + movsxd(rcx, eax); + mov(GPR(qword, RD(instr)), rcx); + } +} + +void JIT::srav(u32 instr) { + if (RD(instr) != 0) [[likely]] { + mov(rcx, GPR(qword, RS(instr))); + CodeGenerator::and_(cl, 0x1F); + mov(rax, GPR(qword, RT(instr))); + sar(rax, cl); + movsxd(rcx, eax); + mov(GPR(qword, RD(instr)), rcx); + } +} + +void JIT::dsra(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f); + mov(rax, GPR(qword, RT(instr))); + sar(rax, sa); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::dsrav(u32 instr) { + if (RD(instr) != 0) [[likely]] { + mov(rcx, GPR(qword, RS(instr))); + CodeGenerator::and_(cl, 63); + mov(rax, GPR(qword, RT(instr))); + sar(rax, cl); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::dsra32(u32 instr) { + if (RD(instr) != 0) [[likely]] { + u8 sa = ((instr >> 6) & 0x1f) + 32; + mov(rax, GPR(qword, RT(instr))); + sar(rax, sa); + mov(GPR(qword, RD(instr)), rax); + } +} + +void JIT::jr(u32 instr) { + auto addr = Entry::Operand{Entry::Operand::REG_U64, RS(instr)}; + Entry e(Entry::BRANCH, addr); + ir.push(e); +} + +void JIT::dsub(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + Entry e(Entry::SUB, dst, op1, op2); + ir.push(e); +} + +void JIT::dsubu(u32 instr) { + dsub(instr); +} + +void JIT::sub(u32 instr) { + auto dst = Entry::Operand{ Entry::Operand::REG_S64, u8(RD(instr)) }; + auto op1 = Entry::Operand{ Entry::Operand::REG_S32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S32, u8(RT(instr)) }; + Entry e(Entry::SUB, dst, op1, op2); + ir.push(e); +} + +void JIT::subu(u32 instr) { + sub(instr); +} + +void JIT::dmultu(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_U64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_U64, u8(RT(instr)) }; + Entry e(Entry::UMUL, op1, op2); + ir.push(e); +} + +void JIT::dmult(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_S64, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S64, u8(RT(instr)) }; + Entry e(Entry::SMUL, op1, op2); + ir.push(e); +} + +void JIT::multu(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_S32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S32, u8(RT(instr)) }; + Entry e(Entry::UMUL, op1, op2); + ir.push(e); +} + +void JIT::mult(u32 instr) { + auto op1 = Entry::Operand{ Entry::Operand::REG_S32, u8(RS(instr)) }; + auto op2 = Entry::Operand{ Entry::Operand::REG_S32, u8(RT(instr)) }; + Entry e(Entry::SMUL, op1, op2); + ir.push(e); +} + +void JIT::mflo(u32 instr) { + auto dst = Entry::Operand{Entry::Operand::REG_S64, RD(instr)}; + auto src = Entry::Operand{Entry::Operand::LO}; + ir.push({Entry::MOV, dst, src}); +} + +void JIT::mfhi(u32 instr) { + auto dst = Entry::Operand{Entry::Operand::REG_S64, RD(instr)}; + auto src = Entry::Operand{Entry::Operand::HI}; + ir.push({Entry::MOV, dst, src}); +} + +void JIT::mtlo(u32 instr) { + auto dst = Entry::Operand{Entry::Operand::LO}; + auto src = Entry::Operand{Entry::Operand::REG_S64, RS(instr)}; + ir.push({Entry::MOV, dst, src}); +} + +void JIT::mthi(u32 instr) { + auto dst = Entry::Operand{Entry::Operand::HI}; + auto src = Entry::Operand{Entry::Operand::REG_S64, RS(instr)}; + ir.push({Entry::MOV, dst, src}); +} + +void JIT::mtc0(u32 instr) { + ir.push({Entry::MTC0, + {Entry::Operand::IMM_U5, RD(instr)}, + {Entry::Operand::REG_S32, RT(instr)}}); +} + +void JIT::dmtc0(u32 instr) { + ir.push({Entry::MTC0, + {Entry::Operand::IMM_U5, RD(instr)}, + {Entry::Operand::REG_S64, RT(instr)}}); +} + +void JIT::mfc0(u32 instr) { + ir.push({Entry::MFC0, + {Entry::Operand::REG_S32, RT(instr)}, + {Entry::Operand::IMM_U5, RD(instr)}}); +} + +void JIT::dmfc0(u32 instr) { + ir.push({Entry::MFC0, + {Entry::Operand::REG_S64, RT(instr)}, + {Entry::Operand::IMM_U5, RD(instr)}}); +} + +void JIT::eret() { + /*if(status.erl) { + regs.SetPC64(ErrorEPC); + status.erl = false; + } else { + regs.SetPC64(EPC); + status.exl = false; + } + regs.cop0.Update(); + llbit = false;*/ +} + + +void JIT::tlbr() { + /*if (index.i >= 32) { + Util::panic("TLBR with TLB index {}", index.i); + } + + TLBEntry entry = tlb[index.i]; + + entryHi.raw = entry.entryHi.raw; + entryLo0.raw = entry.entryLo0.raw & 0x3FFFFFFF; + entryLo1.raw = entry.entryLo1.raw & 0x3FFFFFFF; + + entryLo0.g = entry.global; + entryLo1.g = entry.global; + pageMask.raw = entry.pageMask.raw;*/ +} + +void JIT::tlbw(int index_) { + /*PageMask page_mask{}; + page_mask = pageMask; + u32 top = page_mask.mask & 0xAAA; + page_mask.mask = top | (top >> 1); + + if(index_ >= 32) { + Util::panic("TLBWI with TLB index {}", index_); + } + + tlb[index_].entryHi.raw = entryHi.raw; + tlb[index_].entryHi.vpn2 &= ~page_mask.mask; + + tlb[index_].entryLo0.raw = entryLo0.raw & 0x03FFFFFE; + tlb[index_].entryLo1.raw = entryLo1.raw & 0x03FFFFFE; + tlb[index_].pageMask.raw = page_mask.raw; + + tlb[index_].global = entryLo0.g && entryLo1.g; + tlb[index_].initialized = true;*/ +} + +void JIT::tlbp() { + /*int match = -1; + TLBEntry* entry = TLBTryMatch(regs, entryHi.raw, &match); + if(entry && match >= 0) { + index.raw = match; + } else { + index.raw = 0; + index.p = 1; + }*/ +} + +void JIT::mtc2(u32 instr) { + +} + +void JIT::mfc2(u32 instr) { + +} + +void JIT::dmtc2(u32 instr) { + +} + +void JIT::dmfc2(u32 instr) { + +} + +void JIT::ctc2(u32) { + +} + +void JIT::cfc2(u32) { + +} + +} \ No newline at end of file diff --git a/src/backend/core/mem/Flash.cpp b/src/backend/core/mem/Flash.cpp index e0df02f2..d6ff50f7 100644 --- a/src/backend/core/mem/Flash.cpp +++ b/src/backend/core/mem/Flash.cpp @@ -49,7 +49,7 @@ void Flash::CommandExecute() { case FlashState::Erase: if(saveData.is_mapped()) { for (int i = 0; i < 128; i++) { - saveData[eraseOffs + i] = 0xFF; + saveData[eraseOffs + i] = 0xFFi8; } } else { Util::panic("Accessing flash when not mapped!"); diff --git a/src/backend/core/registers/Cop0.cpp b/src/backend/core/registers/Cop0.cpp index bf041c05..b504522f 100644 --- a/src/backend/core/registers/Cop0.cpp +++ b/src/backend/core/registers/Cop0.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace n64 { Cop0::Cop0() { @@ -336,7 +337,25 @@ template void Cop0::decode(Interpreter&, u32); template void Cop0::decode(JIT&, u32); void Cop0::decodeJIT(JIT& cpu, u32 instr) { - + u8 mask_cop = (instr >> 21) & 0x1F; + u8 mask_cop2 = instr & 0x3F; + switch(mask_cop) { + case 0x00: cpu.mfc0(instr); break; + case 0x01: cpu.dmfc0(instr); break; + case 0x04: cpu.mtc0(instr); break; + case 0x05: cpu.dmtc0(instr); break; + case 0x10 ... 0x1F: + switch(mask_cop2) { + case 0x01: cpu.tlbr(); break; + case 0x02: cpu.tlbw(index.i); break; + case 0x06: cpu.tlbw(GetRandom()); break; + case 0x08: cpu.tlbp(); break; + case 0x18: cpu.eret(); break; + default: Util::panic("Unimplemented COP0 function {} {} ({:08X}) ({:016lX})", mask_cop2 >> 3, mask_cop2 & 7, instr, cpu.regs.oldPC); + } + break; + default: Util::panic("Unimplemented COP0 instruction {} {}", mask_cop >> 4, mask_cop & 7); + } } void Cop0::decodeInterp(Registers& regs, u32 instr) {