#include #include #include "jit/helpers.hpp" namespace n64 { Interpreter::Interpreter(Mem &mem, Registers ®s) : regs(regs), mem(mem) {} bool Interpreter::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 Interpreter::CheckCompareInterrupt() const { regs.cop0.count++; regs.cop0.count &= 0x1FFFFFFFF; if (regs.cop0.count == static_cast(regs.cop0.compare) << 1) { regs.cop0.cause.ip7 = 1; mem.mmio.mi.UpdateInterrupt(); } } bool Interpreter::Fetch(Instruction &instr, u64 vaddr) { u32 paddr = 0; if (!regs.cop0.MapVAddr(Cop0::LOAD, vaddr, paddr)) { regs.cop0.HandleTLBException(vaddr); regs.cop0.FireException(Cop0::GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, vaddr); return false; } instr = mem.Read(paddr); return true; } bool Interpreter::MaybeAdvance() { CheckCompareInterrupt(); regs.prevDelaySlot = regs.delaySlot; regs.delaySlot = false; if (check_address_error(0b11, u64(regs.pc))) [[unlikely]] { regs.cop0.HandleTLBException(regs.pc); regs.cop0.FireException(Cop0::ExceptionCode::AddressErrorLoad, 0, regs.pc); return false; } if (ShouldServiceInterrupt()) { regs.cop0.FireException(Cop0::ExceptionCode::Interrupt, 0, regs.pc); return false; } regs.oldPC = regs.pc; regs.pc = regs.nextPC; regs.nextPC += 4; return true; } bool Interpreter::FetchThenMaybeAdvance(Instruction &instr) { CheckCompareInterrupt(); regs.prevDelaySlot = regs.delaySlot; regs.delaySlot = false; if (check_address_error(0b11, u64(regs.pc))) [[unlikely]] { regs.cop0.HandleTLBException(regs.pc); regs.cop0.FireException(Cop0::ExceptionCode::AddressErrorLoad, 0, regs.pc); return false; } if (!Fetch(instr, regs.pc)) return false; if (ShouldServiceInterrupt()) { regs.cop0.FireException(Cop0::ExceptionCode::Interrupt, 0, regs.pc); return false; } regs.oldPC = regs.pc; regs.pc = regs.nextPC; regs.nextPC += 4; return true; } u32 Interpreter::Step() { Instruction instr; if (!FetchThenMaybeAdvance(instr)) return 1; DecodeExecute(instr); return 1; } u32 DivideAddr(u32 addr, u32 &offset) { offset = (addr & (MAX_LINES_PER_BLOCK - 1)) / 4; return addr / MAX_LINES_PER_BLOCK; } std::shared_ptr CachedState::GetLine(u64 addr) { u32 offset; u32 page = DivideAddr(addr, offset); if (blocks[page]) return blocks[page]->lines[offset]; return nullptr; } void CachedState::InsertLine(u64 addr, std::shared_ptr line) { u32 offset; u32 page = DivideAddr(addr, offset); if (!blocks[page]) blocks[page] = std::make_unique(); blocks[page]->lines[offset] = line; } void CachedState::EvictLine(u64 addr) { u32 offset; u32 page = DivideAddr(addr, offset); if (blocks[page]) { blocks[page]->lines[offset].reset(); blocks[page].reset(); } } u32 Interpreter::ExecuteCached() { auto addr = regs.pc; auto blockAddr = addr; auto line = cachedState.GetLine(addr); if (line) { for (u32 i = 0; i < line->len; i++) { if (!MaybeAdvance()) return i + 1; Instruction instr = line->code[i]; DecodeExecute(instr); // Branch likely with false condition, it wasn't taken so don't execute the delay slot if (IsBranchLikely(instr) && !regs.delaySlot) break; } if (line->cycles == 0) Scheduler::GetInstance().SkipToNext(); return line->cycles; } std::array code; u32 i; bool fetchDelaySlot = false; for (i = 0; i < MAX_INSTRUCTION_PER_LINE; i++) { Instruction instr; if (!Fetch(instr, addr)) return i + 1; addr += 4; code[i] = instr; if (fetchDelaySlot) { i++; break; } if (InstrEndsBlock(instr)) { if (InstrHasDelaySlot(instr) && !fetchDelaySlot) { fetchDelaySlot = true; continue; } break; } } cachedState.InsertLine(blockAddr, std::make_shared(code, i, i)); return ExecuteCached(); } } // namespace n64