CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/MIPS/RiscV/RiscVJit.cpp
Views: 1401
// Copyright (c) 2023- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <cstddef>18#include "Core/MemMap.h"19#include "Core/MIPS/MIPSTables.h"20#include "Core/MIPS/RiscV/RiscVJit.h"21#include "Core/MIPS/RiscV/RiscVRegCache.h"2223#include <algorithm>24// for std::min2526namespace MIPSComp {2728using namespace RiscVGen;29using namespace RiscVJitConstants;3031// Needs space for a LI and J which might both be 32-bit offsets.32static constexpr int MIN_BLOCK_NORMAL_LEN = 16;33static constexpr int MIN_BLOCK_EXIT_LEN = 8;3435RiscVJitBackend::RiscVJitBackend(JitOptions &jitopt, IRBlockCache &blocks)36: IRNativeBackend(blocks), jo(jitopt), regs_(&jo) {37// Automatically disable incompatible options.38if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {39jo.enablePointerify = false;40}41jo.optimizeForInterpreter = false;4243// Since we store the offset, this is as big as it can be.44// We could shift off one bit to double it, would need to change RiscVAsm.45AllocCodeSpace(1024 * 1024 * 16);46SetAutoCompress(true);4748regs_.Init(this);49}5051RiscVJitBackend::~RiscVJitBackend() {52}5354static void NoBlockExits() {55_assert_msg_(false, "Never exited block, invalid IR?");56}5758bool RiscVJitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num, bool preload) {59if (GetSpaceLeft() < 0x800)60return false;6162IRBlock *block = irBlockCache->GetBlock(block_num);63BeginWrite(std::min(GetSpaceLeft(), (size_t)block->GetNumIRInstructions() * 32));6465u32 startPC = block->GetOriginalStart();66bool wroteCheckedOffset = false;67if (jo.enableBlocklink && !jo.useBackJump) {68SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));69wroteCheckedOffset = true;7071WriteDebugPC(startPC);7273FixupBranch normalEntry = BGE(DOWNCOUNTREG, R_ZERO);74LI(SCRATCH1, startPC);75QuickJ(R_RA, outerLoopPCInSCRATCH1_);76SetJumpTarget(normalEntry);77}7879// Don't worry, the codespace isn't large enough to overflow offsets.80const u8 *blockStart = GetCodePointer();81block->SetNativeOffset((int)GetOffset(blockStart));82compilingBlockNum_ = block_num;8384regs_.Start(irBlockCache, block_num);8586std::vector<const u8 *> addresses;87const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);88for (int i = 0; i < block->GetNumIRInstructions(); ++i) {89const IRInst &inst = instructions[i];90regs_.SetIRIndex(i);91addresses.push_back(GetCodePtr());9293CompileIRInst(inst);9495if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR))96regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR));9798// Safety check, in case we get a bunch of really large jit ops without a lot of branching.99if (GetSpaceLeft() < 0x800) {100compilingBlockNum_ = -1;101return false;102}103}104105// We should've written an exit above. If we didn't, bad things will happen.106// Only check if debug stats are enabled - needlessly wastes jit space.107if (DebugStatsEnabled()) {108QuickCallFunction(&NoBlockExits, SCRATCH2);109QuickJ(R_RA, hooks_.crashHandler);110}111112int len = (int)GetOffset(GetCodePointer()) - block->GetNativeOffset();113if (len < MIN_BLOCK_NORMAL_LEN) {114// We need at least 16 bytes to invalidate blocks with, but larger doesn't need to align.115ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - len);116}117118if (!wroteCheckedOffset) {119// Always record this, even if block link disabled - it's used for size calc.120SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));121}122123if (jo.enableBlocklink && jo.useBackJump) {124WriteDebugPC(startPC);125126// Most blocks shouldn't be >= 4KB, so usually we can just BGE.127if (BInRange(blockStart)) {128BGE(DOWNCOUNTREG, R_ZERO, blockStart);129} else {130FixupBranch skip = BLT(DOWNCOUNTREG, R_ZERO);131J(blockStart);132SetJumpTarget(skip);133}134LI(SCRATCH1, startPC);135QuickJ(R_RA, outerLoopPCInSCRATCH1_);136}137138if (logBlocks_ > 0) {139--logBlocks_;140141std::map<const u8 *, int> addressesLookup;142for (int i = 0; i < (int)addresses.size(); ++i)143addressesLookup[addresses[i]] = i;144145INFO_LOG(Log::JIT, "=============== RISCV (%08x, %d bytes) ===============", startPC, len);146const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);147for (const u8 *p = blockStart; p < GetCodePointer(); ) {148auto it = addressesLookup.find(p);149if (it != addressesLookup.end()) {150const IRInst &inst = instructions[it->second];151152char temp[512];153DisassembleIR(temp, sizeof(temp), inst);154INFO_LOG(Log::JIT, "IR: #%d %s", it->second, temp);155}156157auto next = std::next(it);158const u8 *nextp = next == addressesLookup.end() ? GetCodePointer() : next->first;159160#if PPSSPP_ARCH(RISCV64) || (PPSSPP_PLATFORM(WINDOWS) && !defined(__LIBRETRO__))161auto lines = DisassembleRV64(p, (int)(nextp - p));162for (const auto &line : lines)163INFO_LOG(Log::JIT, "RV: %s", line.c_str());164#endif165p = nextp;166}167}168169EndWrite();170FlushIcache();171compilingBlockNum_ = -1;172173return true;174}175176void RiscVJitBackend::WriteConstExit(uint32_t pc) {177int block_num = blocks_.GetBlockNumberFromStartAddress(pc);178const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);179180int exitStart = (int)GetOffset(GetCodePointer());181if (block_num >= 0 && jo.enableBlocklink && nativeBlock && nativeBlock->checkedOffset != 0) {182QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);183} else {184LI(SCRATCH1, pc);185QuickJ(R_RA, dispatcherPCInSCRATCH1_);186}187188if (jo.enableBlocklink) {189// In case of compression or early link, make sure it's large enough.190int len = (int)GetOffset(GetCodePointer()) - exitStart;191if (len < MIN_BLOCK_EXIT_LEN) {192ReserveCodeSpace(MIN_BLOCK_EXIT_LEN - len);193len = MIN_BLOCK_EXIT_LEN;194}195196AddLinkableExit(compilingBlockNum_, pc, exitStart, len);197}198}199200void RiscVJitBackend::OverwriteExit(int srcOffset, int len, int block_num) {201_dbg_assert_(len >= MIN_BLOCK_EXIT_LEN);202203const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);204if (nativeBlock) {205u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + srcOffset;206if (PlatformIsWXExclusive()) {207ProtectMemoryPages(writable, len, MEM_PROT_READ | MEM_PROT_WRITE);208}209210RiscVEmitter emitter(GetBasePtr() + srcOffset, writable);211emitter.QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);212int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);213if (bytesWritten < len)214emitter.ReserveCodeSpace(len - bytesWritten);215emitter.FlushIcache();216217if (PlatformIsWXExclusive()) {218ProtectMemoryPages(writable, 16, MEM_PROT_READ | MEM_PROT_EXEC);219}220}221}222223void RiscVJitBackend::CompIR_Generic(IRInst inst) {224// If we got here, we're going the slow way.225uint64_t value;226memcpy(&value, &inst, sizeof(inst));227228FlushAll();229LI(X10, value, SCRATCH2);230SaveStaticRegisters();231WriteDebugProfilerStatus(IRProfilerStatus::IR_INTERPRET);232QuickCallFunction(&DoIRInst, SCRATCH2);233WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);234LoadStaticRegisters();235236// We only need to check the return value if it's a potential exit.237if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) {238// Result in X10 aka SCRATCH1.239_assert_(X10 == SCRATCH1);240if (BInRange(dispatcherPCInSCRATCH1_)) {241BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_);242} else {243FixupBranch skip = BEQ(X10, R_ZERO);244QuickJ(R_RA, dispatcherPCInSCRATCH1_);245SetJumpTarget(skip);246}247}248}249250void RiscVJitBackend::CompIR_Interpret(IRInst inst) {251MIPSOpcode op(inst.constant);252253// IR protects us against this being a branching instruction (well, hopefully.)254FlushAll();255SaveStaticRegisters();256WriteDebugProfilerStatus(IRProfilerStatus::INTERPRET);257if (DebugStatsEnabled()) {258LI(X10, MIPSGetName(op));259QuickCallFunction(&NotifyMIPSInterpret, SCRATCH2);260}261LI(X10, (int32_t)inst.constant);262QuickCallFunction((const u8 *)MIPSGetInterpretFunc(op), SCRATCH2);263WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);264LoadStaticRegisters();265}266267void RiscVJitBackend::FlushAll() {268regs_.FlushAll();269}270271bool RiscVJitBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const {272// Used in disassembly viewer.273// Don't use spaces; profilers get confused or truncate them.274if (ptr == dispatcherPCInSCRATCH1_) {275name = "dispatcherPCInSCRATCH1";276} else if (ptr == outerLoopPCInSCRATCH1_) {277name = "outerLoopPCInSCRATCH1";278} else if (ptr == dispatcherNoCheck_) {279name = "dispatcherNoCheck";280} else if (ptr == saveStaticRegisters_) {281name = "saveStaticRegisters";282} else if (ptr == loadStaticRegisters_) {283name = "loadStaticRegisters";284} else if (ptr == applyRoundingMode_) {285name = "applyRoundingMode";286} else if (ptr >= GetBasePtr() && ptr < GetBasePtr() + jitStartOffset_) {287name = "fixedCode";288} else {289return IRNativeBackend::DescribeCodePtr(ptr, name);290}291return true;292}293294void RiscVJitBackend::ClearAllBlocks() {295ClearCodeSpace(jitStartOffset_);296FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_);297EraseAllLinks(-1);298}299300void RiscVJitBackend::InvalidateBlock(IRBlockCache *irBlockCache, int block_num) {301IRBlock *block = irBlockCache->GetBlock(block_num);302int offset = block->GetNativeOffset();303u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + offset;304305// Overwrite the block with a jump to compile it again.306u32 pc = block->GetOriginalStart();307if (pc != 0) {308// Hopefully we always have at least 16 bytes, which should be all we need.309if (PlatformIsWXExclusive()) {310ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_WRITE);311}312313RiscVEmitter emitter(GetBasePtr() + offset, writable);314// We sign extend to ensure it will fit in 32-bit and 8 bytes LI.315// TODO: May need to change if dispatcher doesn't reload PC.316emitter.LI(SCRATCH1, (int32_t)pc);317emitter.QuickJ(R_RA, dispatcherPCInSCRATCH1_);318int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);319if (bytesWritten < MIN_BLOCK_NORMAL_LEN)320emitter.ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - bytesWritten);321emitter.FlushIcache();322323if (PlatformIsWXExclusive()) {324ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_EXEC);325}326}327328EraseAllLinks(block_num);329}330331void RiscVJitBackend::RestoreRoundingMode(bool force) {332FSRMI(Round::NEAREST_EVEN);333}334335void RiscVJitBackend::ApplyRoundingMode(bool force) {336QuickCallFunction(applyRoundingMode_);337}338339void RiscVJitBackend::MovFromPC(RiscVReg r) {340LWU(r, CTXREG, offsetof(MIPSState, pc));341}342343void RiscVJitBackend::MovToPC(RiscVReg r) {344SW(r, CTXREG, offsetof(MIPSState, pc));345}346347void RiscVJitBackend::WriteDebugPC(uint32_t pc) {348if (hooks_.profilerPC) {349int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();350LI(SCRATCH2, hooks_.profilerPC);351LI(R_RA, (int32_t)pc);352SW(R_RA, SCRATCH2, 0);353}354}355356void RiscVJitBackend::WriteDebugPC(RiscVReg r) {357if (hooks_.profilerPC) {358int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();359LI(SCRATCH2, hooks_.profilerPC);360SW(r, SCRATCH2, 0);361}362}363364void RiscVJitBackend::WriteDebugProfilerStatus(IRProfilerStatus status) {365if (hooks_.profilerPC) {366int offset = (const u8 *)hooks_.profilerStatus - GetBasePtr();367LI(SCRATCH2, hooks_.profilerStatus);368LI(R_RA, (int)status);369SW(R_RA, SCRATCH2, 0);370}371}372373void RiscVJitBackend::SaveStaticRegisters() {374if (jo.useStaticAlloc) {375QuickCallFunction(saveStaticRegisters_);376} else {377// Inline the single operation378SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));379}380}381382void RiscVJitBackend::LoadStaticRegisters() {383if (jo.useStaticAlloc) {384QuickCallFunction(loadStaticRegisters_);385} else {386LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));387}388}389390void RiscVJitBackend::NormalizeSrc1(IRInst inst, RiscVReg *reg, RiscVReg tempReg, bool allowOverlap) {391*reg = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, tempReg);392}393394void RiscVJitBackend::NormalizeSrc12(IRInst inst, RiscVReg *lhs, RiscVReg *rhs, RiscVReg lhsTempReg, RiscVReg rhsTempReg, bool allowOverlap) {395*lhs = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, lhsTempReg);396*rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg);397}398399RiscVReg RiscVJitBackend::NormalizeR(IRReg rs, IRReg rd, RiscVReg tempReg) {400// For proper compare, we must sign extend so they both match or don't match.401// But don't change pointers, in case one is SP (happens in LittleBigPlanet.)402if (regs_.IsGPRImm(rs) && regs_.GetGPRImm(rs) == 0) {403return R_ZERO;404} else if (regs_.IsGPRMappedAsPointer(rs) || rs == rd) {405return regs_.Normalize32(rs, tempReg);406} else {407return regs_.Normalize32(rs);408}409}410411} // namespace MIPSComp412413414