#include #include namespace n64 { #ifndef __aarch64__ JIT::JIT(Mem& mem, Registers& regs) : regs(regs), mem(mem) { regs.SetJIT(this); mem.SetJIT(this); blockCache.resize(kUpperSize); if (cs_open(CS_ARCH_MIPS, static_cast(CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN), &disassemblerMips) != CS_ERR_OK) { panic("Failed to initialize MIPS disassembler"); } if (cs_open(CS_ARCH_X86, static_cast(CS_MODE_64 | CS_MODE_LITTLE_ENDIAN), &disassemblerX86) != CS_ERR_OK) { panic("Failed to initialize x86 disassembler"); } } bool JIT::ShouldServiceInterrupt() const { const bool interrupts_pending = (regs.cop0.status.im & regs.cop0.cause.interruptPending) != 0; const bool interrupts_enabled = regs.cop0.status.ie == 1; const bool currently_handling_exception = regs.cop0.status.exl == 1; const bool currently_handling_error = regs.cop0.status.erl == 1; return interrupts_pending && interrupts_enabled && !currently_handling_exception && !currently_handling_error; } void JIT::CheckCompareInterrupt() const { regs.cop0.count++; regs.cop0.count &= 0x1FFFFFFFF; if (regs.cop0.count == static_cast(regs.cop0.compare) << 1) { regs.cop0.cause.ip7 = 1; Core::GetMem().mmio.mi.UpdateInterrupt(); } } void JIT::InvalidateBlock(const u32 paddr) { if (const u32 index = paddr >> kUpperShift; !blockCache[index].empty()) blockCache[index] = {}; } std::optional JIT::FetchInstruction(s64 vaddr) { u32 paddr = 0; if (check_address_error(0b11, vaddr)) [[unlikely]] { /*regs.cop0.HandleTLBException(blockPC); regs.cop0.FireException(ExceptionCode::AddressErrorLoad, 0, blockPC); return 1;*/ Util::Error::GetInstance().Throw( {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {}, "[JIT]: Unhandled exception ADL due to unaligned PC virtual value!"); return std::nullopt; } if (!regs.cop0.MapVAddr(Cop0::LOAD, vaddr, paddr)) { /*regs.cop0.HandleTLBException(blockPC); regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC); return 1;*/ Util::Error::GetInstance().Throw( {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {}, "[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address!", static_cast(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD))); return std::nullopt; } const u32 instr = Core::GetMem().Read(paddr); info("{}", Disassembler::GetInstance().DisassembleSimple(paddr, instr).full); return instr; } void JIT::SetPC32(const s32 val) { code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]); code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1); code.mov(code.SCR1.cvt32(), val); code.movsxd(code.SCR1.cvt64(), code.SCR1.cvt32()); code.mov(code.qword[code.rbp + PC_OFFSET], code.SCR1); code.mov(code.SCR1.cvt32(), val + 4); code.movsxd(code.SCR1.cvt64(), code.SCR1.cvt32()); code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], code.SCR1); } void JIT::SetPC64(const s64 val) { code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]); code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1); code.mov(code.SCR1, val); code.mov(code.qword[code.rbp + PC_OFFSET], code.SCR1); code.mov(code.SCR1, val + 4); code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], code.SCR1); } void JIT::SetPC32(const Xbyak::Reg32& val) { code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]); code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1); code.movsxd(val.cvt64(), val); code.mov(code.qword[code.rbp + PC_OFFSET], val); code.add(val, 4); code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], val); } void JIT::SetPC64(const Xbyak::Reg64& val) { code.mov(code.SCR1, code.qword[code.rbp + PC_OFFSET]); code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1); code.mov(code.qword[code.rbp + PC_OFFSET], val); code.add(val, 4); code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], val); } u32 JIT::Step() { blockOldPC = regs.oldPC; blockPC = regs.pc; blockNextPC = regs.nextPC; u32 paddr = 0; if (!regs.cop0.MapVAddr(Cop0::LOAD, blockPC, paddr)) { /*regs.cop0.HandleTLBException(blockPC); regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, blockPC); return 1;*/ Util::Error::GetInstance().Throw({Util::Error::Severity::NON_FATAL}, {Util::Error::Type::UNHANDLED_EXCEPTION}, blockPC, {}, "[JIT]: Unhandled exception TLB exception {} when retrieving PC physical address!", static_cast(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD))); return 0; } const u32 upperIndex = paddr >> kUpperShift; const u32 lowerIndex = paddr & kLowerMask; if (!blockCache[upperIndex].empty()) { if (blockCache[upperIndex][lowerIndex]) { // trace("[JIT]: Executing already compiled block @ 0x{:016X}", blockPC); return blockCache[upperIndex][lowerIndex](); } } else { blockCache[upperIndex].resize(kLowerSize); } info("[JIT]: Compiling block @ 0x{:016X}:", static_cast(blockPC)); const auto blockInfo = code.getCurr(); const auto block = code.getCurr(); blockCache[upperIndex][lowerIndex] = block; code.setProtectModeRW(); u32 instructionsInBlock = 0; bool instrEndsBlock = false; code.sub(code.rsp, 8); code.push(code.rbp); code.mov(code.rbp, reinterpret_cast(this)); // Load context pointer cs_insn *insn; info("\tMIPS code (guest PC = 0x{:016X}):", static_cast(blockPC)); emitMemberFunctionCall(&JIT::AdvanceDelaySlot, this); while (true) { paddr = 0; auto instruction = FetchInstruction(blockPC); if(!instruction) return 0; instructionsInBlock++; blockOldPC = blockPC; blockPC = blockNextPC; blockNextPC += 4; if(InstrEndsBlock(instruction.value())) { const auto delay_instruction = FetchInstruction(blockPC); // get instruction in delay slot if(!delay_instruction) return 0; if(InstrEndsBlock(delay_instruction.value())) { Util::Error::GetInstance().Throw( {Util::Error::Severity::NON_FATAL}, {Util::Error::Type::JIT_BRANCH_INSIDE_DELAY_SLOT}, blockPC, {}, "[JIT]: Unhandled case of branch from delay slot!"); return 0; } instructionsInBlock++; blockOldPC = blockPC; blockPC = blockNextPC; blockNextPC += 4; Emit(delay_instruction.value()); Emit(instruction.value()); if(!branch_taken) { Xbyak::Label runtime_branch_taken; code.mov(code.SCR1, code.byte[code.rbp + BRANCH_TAKEN_OFFSET]); code.cmp(code.SCR1, 0); code.jne(runtime_branch_taken); code.mov(code.SCR1, blockOldPC); code.mov(code.qword[code.rbp + OLD_PC_OFFSET], code.SCR1); code.mov(code.SCR1, blockPC); code.mov(code.qword[code.rbp + PC_OFFSET], code.SCR1); code.mov(code.SCR1, blockNextPC); code.mov(code.qword[code.rbp + NEXT_PC_OFFSET], code.SCR1); code.L(runtime_branch_taken); } if(branch_taken) branch_taken = false; emitMemberFunctionCall(&JIT::AdvanceDelaySlot, this); break; } Emit(instruction.value()); emitMemberFunctionCall(&JIT::AdvanceDelaySlot, this); } code.mov(code.rax, instructionsInBlock); code.pop(code.rbp); code.add(code.rsp, 8); code.ret(); code.setProtectModeRE(); static size_t blockInfoSize = 0; blockInfoSize = code.getSize() - blockInfoSize; info("\tX86 code (block address = 0x{:016X}):", reinterpret_cast(block)); const auto count = cs_disasm(disassemblerX86, blockInfo, blockInfoSize, reinterpret_cast(block), 0, &insn); if (count > 0) { for (size_t j = 0; j < count; j++) { info("\t\t0x{:016X}:\t{}\t\t{}", insn[j].address, insn[j].mnemonic, insn[j].op_str); } cs_free(insn, count); } // panic(""); return block(); } #endif void JIT::DumpBlockCacheToDisk() const { Util::WriteFileBinary(code.getCode(), code.getSize(), "jit.dump"); } } // namespace n64