mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-02-24 08:01:29 +00:00
Merge pull request #17751 from unknownbrackets/riscv-jit
Initial RISC-V jit based on IR
This commit is contained in:
commit
3383d5b93a
@ -1609,6 +1609,19 @@ list(APPEND CoreExtra
|
||||
)
|
||||
|
||||
list(APPEND CoreExtra
|
||||
Core/MIPS/RiscV/RiscVAsm.cpp
|
||||
Core/MIPS/RiscV/RiscVCompALU.cpp
|
||||
Core/MIPS/RiscV/RiscVCompBranch.cpp
|
||||
Core/MIPS/RiscV/RiscVCompFPU.cpp
|
||||
Core/MIPS/RiscV/RiscVCompLoadStore.cpp
|
||||
Core/MIPS/RiscV/RiscVCompSystem.cpp
|
||||
Core/MIPS/RiscV/RiscVCompVec.cpp
|
||||
Core/MIPS/RiscV/RiscVJit.cpp
|
||||
Core/MIPS/RiscV/RiscVJit.h
|
||||
Core/MIPS/RiscV/RiscVRegCache.cpp
|
||||
Core/MIPS/RiscV/RiscVRegCache.h
|
||||
Core/MIPS/RiscV/RiscVRegCacheFPU.cpp
|
||||
Core/MIPS/RiscV/RiscVRegCacheFPU.h
|
||||
GPU/Common/VertexDecoderRiscV.cpp
|
||||
)
|
||||
|
||||
|
@ -1180,6 +1180,19 @@ bool RiscVEmitter::CJInRange(const void *src, const void *dst) const {
|
||||
return BJInRange(src, dst, 12);
|
||||
}
|
||||
|
||||
void RiscVEmitter::QuickJAL(RiscVReg scratchreg, RiscVReg rd, const u8 *dst) {
|
||||
if (!JInRange(GetCodePointer(), dst)) {
|
||||
static_assert(sizeof(intptr_t) <= sizeof(int64_t));
|
||||
int64_t pcdelta = (int64_t)dst - (int64_t)GetCodePointer();
|
||||
int32_t lower = (int32_t)SignReduce64(pcdelta, 12);
|
||||
uintptr_t upper = ((pcdelta - lower) >> 12) << 12;
|
||||
LI(scratchreg, (uintptr_t)GetCodePointer() + upper);
|
||||
JALR(rd, scratchreg, lower);
|
||||
} else {
|
||||
JAL(rd, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVEmitter::SetRegToImmediate(RiscVReg rd, uint64_t value, RiscVReg temp) {
|
||||
int64_t svalue = (int64_t)value;
|
||||
_assert_msg_(IsGPR(rd) && IsGPR(temp), "SetRegToImmediate only supports GPRs");
|
||||
|
@ -213,6 +213,19 @@ public:
|
||||
bool BInRange(const void *func) const;
|
||||
bool JInRange(const void *func) const;
|
||||
|
||||
void QuickJAL(RiscVReg scratchreg, RiscVReg rd, const u8 *dst);
|
||||
void QuickJ(RiscVReg scratchreg, const u8 *dst) {
|
||||
QuickJAL(scratchreg, R_ZERO, dst);
|
||||
}
|
||||
void QuickCallFunction(const u8 *func) {
|
||||
QuickJAL(R_RA, R_RA, func);
|
||||
}
|
||||
template <typename T>
|
||||
void QuickCallFunction(T *func) {
|
||||
static_assert(std::is_function<T>::value, "QuickCallFunction without function");
|
||||
QuickCallFunction((const u8 *)func);
|
||||
}
|
||||
|
||||
void LUI(RiscVReg rd, s32 simm32);
|
||||
void AUIPC(RiscVReg rd, s32 simm32);
|
||||
|
||||
|
@ -129,7 +129,7 @@ std::string CreateRandMAC() {
|
||||
}
|
||||
|
||||
static int DefaultCpuCore() {
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64)
|
||||
if (System_GetPropertyBool(SYSPROP_CAN_JIT))
|
||||
return (int)CPUCore::JIT;
|
||||
return (int)CPUCore::IR_JIT;
|
||||
@ -139,7 +139,7 @@ static int DefaultCpuCore() {
|
||||
}
|
||||
|
||||
static bool DefaultCodeGen() {
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
|
@ -593,6 +593,16 @@
|
||||
<ClCompile Include="MIPS\IR\IRPassSimplify.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRRegCache.cpp" />
|
||||
<ClCompile Include="MIPS\MIPSVFPUFallbacks.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVAsm.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompALU.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompBranch.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompSystem.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompFPU.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompLoadStore.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompVec.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVJit.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCache.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCacheFPU.cpp" />
|
||||
<ClCompile Include="Replay.cpp" />
|
||||
<ClCompile Include="Compatibility.cpp" />
|
||||
<ClCompile Include="Config.cpp" />
|
||||
@ -1161,6 +1171,9 @@
|
||||
<ClInclude Include="MIPS\IR\IRPassSimplify.h" />
|
||||
<ClInclude Include="MIPS\IR\IRRegCache.h" />
|
||||
<ClInclude Include="MIPS\MIPSVFPUFallbacks.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVJit.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCache.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCacheFPU.h" />
|
||||
<ClInclude Include="Replay.h" />
|
||||
<ClInclude Include="Compatibility.h" />
|
||||
<ClInclude Include="Config.h" />
|
||||
|
@ -91,6 +91,9 @@
|
||||
<Filter Include="MIPS\fake">
|
||||
<UniqueIdentifier>{678fa299-0ff7-4983-982d-2da47b52e238}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="MIPS\RiscV">
|
||||
<UniqueIdentifier>{067e3128-3aaf-4ed1-b19e-bab11606abe7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ELF\ElfReader.cpp">
|
||||
@ -1204,6 +1207,36 @@
|
||||
<ClCompile Include="RetroAchievements.cpp">
|
||||
<Filter>Core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVJit.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVAsm.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCache.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCacheFPU.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompFPU.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompLoadStore.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompVec.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompALU.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompBranch.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompSystem.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ELF\ElfReader.h">
|
||||
@ -1950,6 +1983,15 @@
|
||||
<ClInclude Include="RetroAchievements.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\RiscV\RiscVJit.h">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCache.h">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCacheFPU.h">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE.TXT" />
|
||||
|
@ -213,7 +213,9 @@ private:
|
||||
|
||||
bool ReplaceJalTo(u32 dest);
|
||||
|
||||
// Clobbers SCRATCH2.
|
||||
void SaveStaticRegisters();
|
||||
// Clobbers SCRATCH2.
|
||||
void LoadStaticRegisters();
|
||||
|
||||
void WriteExit(u32 destination, int exit_num);
|
||||
|
@ -777,7 +777,7 @@ void Arm64RegCache::FlushAll() {
|
||||
// Re-pointerify
|
||||
emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32);
|
||||
ar[allocs[i].ar].pointerified = true;
|
||||
} else {
|
||||
} else if (!allocs[i].pointerified) {
|
||||
// If this register got pointerified on the way, mark it as not, so that after save/reload (like in an interpreter fallback), it won't be regarded as such, as it simply won't be.
|
||||
ar[allocs[i].ar].pointerified = false;
|
||||
}
|
||||
|
@ -283,7 +283,9 @@ enum IRFpCompareMode {
|
||||
LessEqualUnordered, // ule, ngt (less equal, unordered)
|
||||
};
|
||||
|
||||
enum {
|
||||
typedef u8 IRReg;
|
||||
|
||||
enum : IRReg {
|
||||
IRTEMP_0 = 192,
|
||||
IRTEMP_1,
|
||||
IRTEMP_2,
|
||||
@ -332,11 +334,11 @@ struct IRMeta {
|
||||
struct IRInst {
|
||||
IROp op;
|
||||
union {
|
||||
u8 dest;
|
||||
u8 src3;
|
||||
IRReg dest;
|
||||
IRReg src3;
|
||||
};
|
||||
u8 src1;
|
||||
u8 src2;
|
||||
IRReg src1;
|
||||
IRReg src2;
|
||||
u32 constant;
|
||||
};
|
||||
|
||||
|
@ -262,11 +262,6 @@ void IRJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
|
||||
Crash();
|
||||
}
|
||||
|
||||
bool IRJit::ReplaceJalTo(u32 dest) {
|
||||
Crash();
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRBlockCache::Clear() {
|
||||
for (int i = 0; i < (int)blocks_.size(); ++i) {
|
||||
blocks_[i].Destroy(i);
|
||||
|
@ -170,9 +170,8 @@ public:
|
||||
void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override;
|
||||
void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override;
|
||||
|
||||
private:
|
||||
bool CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload);
|
||||
bool ReplaceJalTo(u32 dest);
|
||||
protected:
|
||||
virtual bool CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload);
|
||||
|
||||
JitOptions jo;
|
||||
|
||||
@ -187,4 +186,3 @@ private:
|
||||
};
|
||||
|
||||
} // namespace MIPSComp
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
|
||||
void IRRegCache::Flush(int rd) {
|
||||
void IRRegCache::Flush(IRReg rd) {
|
||||
if (rd == 0) {
|
||||
return;
|
||||
}
|
||||
@ -12,7 +12,7 @@ void IRRegCache::Flush(int rd) {
|
||||
}
|
||||
}
|
||||
|
||||
void IRRegCache::Discard(int rd) {
|
||||
void IRRegCache::Discard(IRReg rd) {
|
||||
if (rd == 0) {
|
||||
return;
|
||||
}
|
||||
@ -31,33 +31,33 @@ void IRRegCache::FlushAll() {
|
||||
}
|
||||
}
|
||||
|
||||
void IRRegCache::MapIn(int rd) {
|
||||
void IRRegCache::MapIn(IRReg rd) {
|
||||
Flush(rd);
|
||||
}
|
||||
|
||||
void IRRegCache::MapDirty(int rd) {
|
||||
void IRRegCache::MapDirty(IRReg rd) {
|
||||
Discard(rd);
|
||||
}
|
||||
|
||||
void IRRegCache::MapInIn(int rs, int rt) {
|
||||
void IRRegCache::MapInIn(IRReg rs, IRReg rt) {
|
||||
Flush(rs);
|
||||
Flush(rt);
|
||||
}
|
||||
|
||||
void IRRegCache::MapInInIn(int rd, int rs, int rt) {
|
||||
void IRRegCache::MapInInIn(IRReg rd, IRReg rs, IRReg rt) {
|
||||
Flush(rd);
|
||||
Flush(rs);
|
||||
Flush(rt);
|
||||
}
|
||||
|
||||
void IRRegCache::MapDirtyIn(int rd, int rs) {
|
||||
void IRRegCache::MapDirtyIn(IRReg rd, IRReg rs) {
|
||||
if (rs != rd) {
|
||||
Discard(rd);
|
||||
}
|
||||
Flush(rs);
|
||||
}
|
||||
|
||||
void IRRegCache::MapDirtyInIn(int rd, int rs, int rt) {
|
||||
void IRRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) {
|
||||
if (rs != rd && rt != rd) {
|
||||
Discard(rd);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
|
||||
enum {
|
||||
TOTAL_MAPPABLE_MIPSREGS = 256,
|
||||
@ -22,26 +23,26 @@ class IRRegCache {
|
||||
public:
|
||||
IRRegCache(IRWriter *ir);
|
||||
|
||||
void SetImm(int r, u32 immVal) {
|
||||
void SetImm(IRReg r, u32 immVal) {
|
||||
reg_[r].isImm = true;
|
||||
reg_[r].immVal = immVal;
|
||||
}
|
||||
|
||||
bool IsImm(int r) const { return reg_[r].isImm; }
|
||||
u32 GetImm(int r) const { return reg_[r].immVal; }
|
||||
bool IsImm(IRReg r) const { return reg_[r].isImm; }
|
||||
u32 GetImm(IRReg r) const { return reg_[r].immVal; }
|
||||
|
||||
void FlushAll();
|
||||
|
||||
void MapDirty(int rd);
|
||||
void MapIn(int rd);
|
||||
void MapInIn(int rs, int rt);
|
||||
void MapInInIn(int rd, int rs, int rt);
|
||||
void MapDirtyIn(int rd, int rs);
|
||||
void MapDirtyInIn(int rd, int rs, int rt);
|
||||
void MapDirty(IRReg rd);
|
||||
void MapIn(IRReg rd);
|
||||
void MapInIn(IRReg rs, IRReg rt);
|
||||
void MapInInIn(IRReg rd, IRReg rs, IRReg rt);
|
||||
void MapDirtyIn(IRReg rd, IRReg rs);
|
||||
void MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt);
|
||||
|
||||
private:
|
||||
void Flush(int rd);
|
||||
void Discard(int rd);
|
||||
void Flush(IRReg rd);
|
||||
void Discard(IRReg rd);
|
||||
RegIR reg_[TOTAL_MAPPABLE_MIPSREGS];
|
||||
IRWriter *ir_;
|
||||
};
|
||||
|
@ -45,6 +45,8 @@
|
||||
#include "../x86/Jit.h"
|
||||
#elif PPSSPP_ARCH(MIPS)
|
||||
#include "../MIPS/MipsJit.h"
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
#include "../RiscV/RiscVJit.h"
|
||||
#else
|
||||
#include "../fake/FakeJit.h"
|
||||
#endif
|
||||
@ -108,6 +110,8 @@ namespace MIPSComp {
|
||||
return new MIPSComp::Jit(mipsState);
|
||||
#elif PPSSPP_ARCH(MIPS)
|
||||
return new MIPSComp::MipsJit(mipsState);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
return new MIPSComp::RiscVJit(mipsState);
|
||||
#else
|
||||
return new MIPSComp::FakeJit(mipsState);
|
||||
#endif
|
||||
|
308
Core/MIPS/RiscV/RiscVAsm.cpp
Normal file
308
Core/MIPS/RiscV/RiscVAsm.cpp
Normal file
@ -0,0 +1,308 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
static const bool enableDebug = false;
|
||||
static const bool enableDisasm = false;
|
||||
|
||||
static void ShowPC(u32 downcount, void *membase, void *jitbase) {
|
||||
static int count = 0;
|
||||
if (currentMIPS) {
|
||||
ERROR_LOG(JIT, "[%08x] ShowPC Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase);
|
||||
} else {
|
||||
ERROR_LOG(JIT, "Universe corrupt?");
|
||||
}
|
||||
//if (count > 2000)
|
||||
// exit(0);
|
||||
count++;
|
||||
}
|
||||
|
||||
static void ShowBlockError(int type) {
|
||||
if (type == 1) {
|
||||
ERROR_LOG(JIT, "[%08x] ShowBlockError: block num was out of range in emuhack", currentMIPS->pc);
|
||||
} else if (type == 2) {
|
||||
ERROR_LOG(JIT, "[%08x] ShowBlockError: block num pointed to null jitblock", currentMIPS->pc);
|
||||
} else {
|
||||
ERROR_LOG(JIT, "[%08x] ShowBlockError: invalid error type", currentMIPS->pc);
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::GenerateFixedCode(const JitOptions &jo) {
|
||||
BeginWrite(GetMemoryProtectPageSize());
|
||||
const u8 *start = AlignCodePage();
|
||||
|
||||
if (jo.useStaticAlloc) {
|
||||
saveStaticRegisters_ = AlignCode16();
|
||||
SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
gpr.EmitSaveStaticRegisters();
|
||||
RET();
|
||||
|
||||
loadStaticRegisters_ = AlignCode16();
|
||||
gpr.EmitLoadStaticRegisters();
|
||||
LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
RET();
|
||||
|
||||
start = saveStaticRegisters_;
|
||||
} else {
|
||||
saveStaticRegisters_ = nullptr;
|
||||
loadStaticRegisters_ = nullptr;
|
||||
}
|
||||
|
||||
// TODO: Do we actually need updateRoundingMode_? Hm.
|
||||
//applyRoundingMode_ = AlignCode16();
|
||||
if (false) {
|
||||
// Not sure if RISC-V has any flush to zero capability? Leaving it off for now...
|
||||
LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31));
|
||||
|
||||
// We can skip if the rounding mode is nearest (0) and flush is not set.
|
||||
// (as restoreRoundingMode cleared it out anyway)
|
||||
FixupBranch skip = BEQ(SCRATCH2, R_ZERO);
|
||||
|
||||
// MIPS Rounding Mode: RISC-V
|
||||
// 0: Round nearest 0
|
||||
// 1: Round to zero 1
|
||||
// 2: Round up (ceil) 3
|
||||
// 3: Round down (floor) 2
|
||||
if (cpu_info.RiscV_Zbs) {
|
||||
BEXTI(SCRATCH1, SCRATCH2, 1);
|
||||
} else {
|
||||
ANDI(SCRATCH1, SCRATCH2, 2);
|
||||
SRLI(SCRATCH1, SCRATCH1, 1);
|
||||
}
|
||||
// Swap the lowest bit by the second bit.
|
||||
XOR(SCRATCH2, SCRATCH2, SCRATCH1);
|
||||
|
||||
FSRM(SCRATCH2);
|
||||
|
||||
SetJumpTarget(skip);
|
||||
RET();
|
||||
}
|
||||
|
||||
//updateRoundingMode_ = AlignCode16();
|
||||
if (false) {
|
||||
LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31));
|
||||
|
||||
// Set SCRATCH2 to FZ:RM (FZ is bit 24, and RM are lowest 2 bits.)
|
||||
ANDI(SCRATCH1, SCRATCH2, 1 << 24);
|
||||
ANDI(SCRATCH2, SCRATCH2, 3);
|
||||
SRLI(SCRATCH1, SCRATCH1, 22);
|
||||
OR(SCRATCH2, SCRATCH2, SCRATCH1);
|
||||
|
||||
// Let's update js.currentRoundingFunc with the right convertS0ToSCRATCH1 func.
|
||||
//LI(SCRATCH1, convertS0ToSCRATCH1);
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
SH_ADD(3, SCRATCH1, SCRATCH2, SCRATCH1);
|
||||
} else {
|
||||
SLLI(SCRATCH2, SCRATCH2, 3);
|
||||
ADD(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
}
|
||||
LD(SCRATCH2, SCRATCH1, 0);
|
||||
//LI(SCRATCH1, &js.currentRoundingFunc);
|
||||
SW(SCRATCH2, SCRATCH1, 0);
|
||||
RET();
|
||||
}
|
||||
|
||||
enterDispatcher_ = AlignCode16();
|
||||
|
||||
// Start by saving some regs on the stack. There are 12 GPs and 12 FPs we want.
|
||||
// Note: we leave R_SP as, well, SP, so it doesn't need to be saved.
|
||||
_assert_msg_(cpu_info.Mode64bit, "RiscVAsm currently assumes RV64, not RV32 or RV128");
|
||||
static constexpr RiscVReg regs_to_save[]{ R_RA, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27 };
|
||||
// TODO: Maybe we shouldn't regalloc all of these? Is it worth it?
|
||||
static constexpr RiscVReg regs_to_save_fp[]{ F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27 };
|
||||
int saveSize = 8 * (int)(ARRAY_SIZE(regs_to_save) + ARRAY_SIZE(regs_to_save_fp));
|
||||
if (saveSize & 0xF)
|
||||
saveSize += 8;
|
||||
_assert_msg_((saveSize & 0xF) == 0, "Stack must be kept aligned");
|
||||
int saveOffset = 0;
|
||||
ADDI(R_SP, R_SP, -saveSize);
|
||||
for (RiscVReg r : regs_to_save) {
|
||||
SD(r, R_SP, saveOffset);
|
||||
saveOffset += 8;
|
||||
}
|
||||
for (RiscVReg r : regs_to_save_fp) {
|
||||
FS(64, r, R_SP, saveOffset);
|
||||
saveOffset += 8;
|
||||
}
|
||||
_assert_(saveOffset <= saveSize);
|
||||
|
||||
// Fixed registers, these are always kept when in Jit context.
|
||||
LI(MEMBASEREG, Memory::base, SCRATCH1);
|
||||
LI(CTXREG, mips_, SCRATCH1);
|
||||
LI(JITBASEREG, blockStartAddrs_, SCRATCH1);
|
||||
|
||||
LoadStaticRegisters();
|
||||
MovFromPC(SCRATCH1);
|
||||
outerLoopPCInSCRATCH1_ = GetCodePtr();
|
||||
MovToPC(SCRATCH1);
|
||||
outerLoop_ = GetCodePtr();
|
||||
// Advance can change the downcount (or thread), so must save/restore around it.
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
QuickCallFunction(&CoreTiming::Advance);
|
||||
ApplyRoundingMode(true);
|
||||
LoadStaticRegisters();
|
||||
|
||||
dispatcherCheckCoreState_ = GetCodePtr();
|
||||
LI(SCRATCH1, &coreState, SCRATCH2);
|
||||
LW(SCRATCH1, SCRATCH1, 0);
|
||||
FixupBranch badCoreState = BNE(SCRATCH1, R_ZERO);
|
||||
|
||||
// We just checked coreState, so go to advance if downcount is negative.
|
||||
BLT(DOWNCOUNTREG, R_ZERO, outerLoop_);
|
||||
FixupBranch skipToRealDispatch = J();
|
||||
|
||||
dispatcherPCInSCRATCH1_ = GetCodePtr();
|
||||
MovToPC(SCRATCH1);
|
||||
|
||||
dispatcher_ = GetCodePtr();
|
||||
FixupBranch bail = BLT(DOWNCOUNTREG, R_ZERO);
|
||||
SetJumpTarget(skipToRealDispatch);
|
||||
|
||||
dispatcherNoCheck_ = GetCodePtr();
|
||||
|
||||
// Debug
|
||||
if (enableDebug) {
|
||||
MV(X10, DOWNCOUNTREG);
|
||||
MV(X11, MEMBASEREG);
|
||||
MV(X12, JITBASEREG);
|
||||
QuickCallFunction(&ShowPC);
|
||||
}
|
||||
|
||||
LWU(SCRATCH1, CTXREG, offsetof(MIPSState, pc));
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
LI(SCRATCH2, 0x3FFFFFFF);
|
||||
AND(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
#endif
|
||||
ADD(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
dispatcherFetch_ = GetCodePtr();
|
||||
LWU(SCRATCH1, SCRATCH1, 0);
|
||||
SRLI(SCRATCH2, SCRATCH1, 24);
|
||||
// We're in other words comparing to the top 8 bits of MIPS_EMUHACK_OPCODE by subtracting.
|
||||
ADDI(SCRATCH2, SCRATCH2, -(MIPS_EMUHACK_OPCODE >> 24));
|
||||
FixupBranch needsCompile = BNE(SCRATCH2, R_ZERO);
|
||||
// Use a wall to mask by 0x00FFFFFF and extract the block number.
|
||||
SLLI(SCRATCH1, SCRATCH1, XLEN - 24);
|
||||
// But actually, we want * 8, so skip shifting back just a bit.
|
||||
_assert_msg_(sizeof(blockStartAddrs_[0]) == 8, "RiscVAsm currently assumes pointers are 64-bit");
|
||||
SRLI(SCRATCH1, SCRATCH1, XLEN - 24 - 3);
|
||||
if (enableDebug) {
|
||||
// Let's do some extra validation of the block number in debug mode for testing.
|
||||
|
||||
LI(SCRATCH2, MAX_ALLOWED_JIT_BLOCKS * 8);
|
||||
FixupBranch highBlockNum = BGEU(SCRATCH1, SCRATCH2);
|
||||
ADD(SCRATCH1, JITBASEREG, SCRATCH1);
|
||||
// TODO: Consider replacing the block nums after all, just trying to use IR block cache.
|
||||
LD(SCRATCH1, SCRATCH1, 0);
|
||||
LI(SCRATCH2, 2);
|
||||
FixupBranch invalidBlockNum = BEQ(SCRATCH1, R_ZERO);
|
||||
JR(SCRATCH1);
|
||||
|
||||
SetJumpTarget(highBlockNum);
|
||||
LI(SCRATCH2, 1);
|
||||
SetJumpTarget(invalidBlockNum);
|
||||
|
||||
MV(X10, SCRATCH2);
|
||||
QuickCallFunction(&ShowBlockError);
|
||||
} else {
|
||||
ADD(SCRATCH1, JITBASEREG, SCRATCH1);
|
||||
// TODO: Consider replacing the block nums after all, just trying to use IR block cache.
|
||||
LD(SCRATCH1, SCRATCH1, 0);
|
||||
JR(SCRATCH1);
|
||||
}
|
||||
SetJumpTarget(needsCompile);
|
||||
|
||||
// No block found, let's jit. We don't need to save static regs, they're all callee saved.
|
||||
RestoreRoundingMode(true);
|
||||
QuickCallFunction(&MIPSComp::JitAt);
|
||||
ApplyRoundingMode(true);
|
||||
|
||||
// Try again, the block index should be set now.
|
||||
J(dispatcherNoCheck_);
|
||||
|
||||
SetJumpTarget(bail);
|
||||
|
||||
LI(SCRATCH1, &coreState, SCRATCH2);
|
||||
LW(SCRATCH1, SCRATCH1, 0);
|
||||
BEQ(SCRATCH1, R_ZERO, outerLoop_);
|
||||
|
||||
const uint8_t *quitLoop = GetCodePtr();
|
||||
SetJumpTarget(badCoreState);
|
||||
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
|
||||
_assert_msg_(cpu_info.Mode64bit, "RiscVAsm currently assumes RV64, not RV32 or RV128");
|
||||
saveOffset = 0;
|
||||
for (RiscVReg r : regs_to_save) {
|
||||
LD(r, R_SP, saveOffset);
|
||||
saveOffset += 8;
|
||||
}
|
||||
for (RiscVReg r : regs_to_save_fp) {
|
||||
FL(64, r, R_SP, saveOffset);
|
||||
saveOffset += 8;
|
||||
}
|
||||
ADDI(R_SP, R_SP, saveSize);
|
||||
|
||||
RET();
|
||||
|
||||
// TODO
|
||||
crashHandler_ = GetCodePtr();
|
||||
LI(SCRATCH1, &coreState, SCRATCH2);
|
||||
LI(SCRATCH2, CORE_RUNTIME_ERROR);
|
||||
SW(SCRATCH2, SCRATCH1, 0);
|
||||
J(quitLoop);
|
||||
|
||||
// TODO: Do we need this?
|
||||
static const Round roundModes[8] = { Round::NEAREST_EVEN, Round::TOZERO, Round::UP, Round::DOWN, Round::NEAREST_EVEN, Round::TOZERO, Round::UP, Round::DOWN };
|
||||
for (size_t i = 0; i < ARRAY_SIZE(roundModes); ++i) {
|
||||
//convertS0ToSCRATCH1[i] = AlignCode16();
|
||||
|
||||
//FCVT(FConv::W, FConv::S, SCRATCH1, F0, roundModes[i]);
|
||||
//RET();
|
||||
}
|
||||
|
||||
// Leave this at the end, add more stuff above.
|
||||
if (enableDisasm) {
|
||||
std::vector<std::string> lines = DisassembleRV64(start, GetCodePtr() - start);
|
||||
for (auto s : lines) {
|
||||
INFO_LOG(JIT, "%s", s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Let's spare the pre-generated code from unprotect-reprotect.
|
||||
AlignCodePage();
|
||||
jitStartOffset_ = (int)(GetCodePtr() - start);
|
||||
// Don't forget to zap the instruction cache! This must stay at the end of this function.
|
||||
FlushIcache();
|
||||
EndWrite();
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
655
Core/MIPS/RiscV/RiscVCompALU.cpp
Normal file
655
Core/MIPS/RiscV/RiscVCompALU.cpp
Normal file
@ -0,0 +1,655 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for integer / arithmetic / logic related instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_Arith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
bool allowPtrMath = true;
|
||||
#ifndef MASKED_PSP_MEMORY
|
||||
// Since we modify it, we can't safely.
|
||||
allowPtrMath = false;
|
||||
#endif
|
||||
|
||||
// RISC-V only adds signed immediates, so rewrite a small enough subtract to an add.
|
||||
// We use -2047 and 2048 here because the range swaps.
|
||||
if (inst.op == IROp::SubConst && (int32_t)inst.constant >= -2047 && (int32_t)inst.constant <= 2048) {
|
||||
inst.op = IROp::AddConst;
|
||||
inst.constant = (uint32_t)-(int32_t)inst.constant;
|
||||
}
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Add:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
ADDW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Sub:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SUBW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::AddConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
// Typical of stack pointer updates.
|
||||
if (gpr.IsMappedAsPointer(inst.src1) && inst.dest == inst.src1 && allowPtrMath) {
|
||||
gpr.MarkPtrDirty(gpr.RPtr(inst.dest));
|
||||
ADDI(gpr.RPtr(inst.dest), gpr.RPtr(inst.dest), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
ADDIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
ADDW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SubConst:
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
SUBW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::Neg:
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SUBW(gpr.R(inst.dest), R_ZERO, gpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Logic(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::And:
|
||||
if (inst.src1 != inst.src2) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
AND(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Or:
|
||||
if (inst.src1 != inst.src2) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
OR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
// If both were normalized before, the result is normalized.
|
||||
if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Xor:
|
||||
if (inst.src1 == inst.src2) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
XOR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::AndConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
ANDI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
AND(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
// If the sign bits aren't cleared, and it was normalized before - it still is.
|
||||
if ((inst.constant & 0x80000000) != 0 && gpr.IsNormalized32(inst.src1))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
// Otherwise, if we cleared the sign bits, it's naturally normalized.
|
||||
else if ((inst.constant & 0x80000000) == 0)
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::OrConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
ORI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
OR(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
// Since our constant is normalized, oring its bits in won't hurt normalization.
|
||||
if (gpr.IsNormalized32(inst.src1))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::XorConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
XORI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
XOR(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Not:
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
NOT(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Assign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Mov:
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Ext8to32:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SEXT_B(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 24);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 24);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Ext16to32:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SEXT_H(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 16);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 16);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Bits(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ReverseBits:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::BSwap16:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::BSwap32:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
REV8(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
if (XLEN >= 64) {
|
||||
// REV8 swaps the entire register, so get the 32 highest bits.
|
||||
SRAI(gpr.R(inst.dest), gpr.R(inst.dest), XLEN - 32);
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
}
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Clz:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Shift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Shl:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Shr:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRLW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Sar:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRAW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Ror:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
RORW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::ShlImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::ShrImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SarImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.src1), 31);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::RorImm:
|
||||
if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
RORIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2 & 31);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Compare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Slt:
|
||||
// Not using the NORM32 flag so we don't confuse ourselves on overlap.
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
SLT(gpr.R(inst.dest), lhs, rhs);
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::SltConst:
|
||||
// Not using the NORM32 flag so we don't confuse ourselves on overlap.
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
if (inst.constant == 0) {
|
||||
// Basically, getting the sign bit. Let's shift instead.
|
||||
SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31);
|
||||
} else {
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
SLTI(gpr.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLT(gpr.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
}
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::SltU:
|
||||
// Not using the NORM32 flag so we don't confuse ourselves on overlap.
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
// It's still fine to sign extend, the biggest just get even bigger.
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
SLTU(gpr.R(inst.dest), lhs, rhs);
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::SltUConst:
|
||||
// Not using the NORM32 flag so we don't confuse ourselves on overlap.
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
if (inst.constant == 0) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else {
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
|
||||
// We sign extend because we're comparing against something normalized.
|
||||
// It's also the most efficient to set.
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
SLTIU(gpr.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLTU(gpr.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_CondAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
FixupBranch fixup;
|
||||
switch (inst.op) {
|
||||
case IROp::MovZ:
|
||||
case IROp::MovNZ:
|
||||
if (inst.dest == inst.src2)
|
||||
return;
|
||||
|
||||
// We could have a "zero" that with wrong upper due to XOR, so we have to normalize.
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, true);
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::MovZ:
|
||||
fixup = BNE(lhs, R_ZERO);
|
||||
break;
|
||||
case IROp::MovNZ:
|
||||
fixup = BEQ(lhs, R_ZERO);
|
||||
break;
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src2));
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::Max:
|
||||
if (inst.src1 != inst.src2) {
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MAX(gpr.R(inst.dest), lhs, rhs);
|
||||
// Because we had to normalize the inputs, the output is normalized.
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
} else if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Min:
|
||||
if (inst.src1 != inst.src2) {
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MIN(gpr.R(inst.dest), lhs, rhs);
|
||||
// Because we had to normalize the inputs, the output is normalized.
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
} else if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_HiLo(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::MtLo:
|
||||
gpr.MapDirtyIn(IRREG_LO, inst.src1);
|
||||
MV(gpr.R(IRREG_LO), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_LO), gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::MtHi:
|
||||
gpr.MapDirtyIn(IRREG_HI, inst.src1);
|
||||
MV(gpr.R(IRREG_HI), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_HI), gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::MfLo:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_LO);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_LO));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_LO));
|
||||
break;
|
||||
|
||||
case IROp::MfHi:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_HI);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_HI));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_HI));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Mult(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
auto makeArgsUnsigned = [&](RiscVReg *lhs, RiscVReg *rhs) {
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
ZEXT_W(SCRATCH1, gpr.R(inst.src1));
|
||||
ZEXT_W(SCRATCH2, gpr.R(inst.src2));
|
||||
} else {
|
||||
SLLI(SCRATCH1, gpr.R(inst.src1), XLEN - 32);
|
||||
SRLI(SCRATCH1, SCRATCH1, XLEN - 32);
|
||||
SLLI(SCRATCH2, gpr.R(inst.src2), XLEN - 32);
|
||||
SRLI(SCRATCH2, SCRATCH2, XLEN - 32);
|
||||
}
|
||||
*lhs = SCRATCH1;
|
||||
*rhs = SCRATCH2;
|
||||
};
|
||||
auto combinePrevMulResult = [&] {
|
||||
// TODO: Using a single reg for HI/LO would make this less ugly.
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
ZEXT_W(gpr.R(IRREG_LO), gpr.R(IRREG_LO));
|
||||
} else {
|
||||
SLLI(gpr.R(IRREG_LO), gpr.R(IRREG_LO), XLEN - 32);
|
||||
SRLI(gpr.R(IRREG_LO), gpr.R(IRREG_LO), XLEN - 32);
|
||||
}
|
||||
SLLI(gpr.R(IRREG_HI), gpr.R(IRREG_HI), 32);
|
||||
OR(gpr.R(IRREG_LO), gpr.R(IRREG_LO), gpr.R(IRREG_HI));
|
||||
};
|
||||
auto splitMulResult = [&] {
|
||||
SRAI(gpr.R(IRREG_HI), gpr.R(IRREG_LO), 32);
|
||||
gpr.MarkDirty(gpr.R(IRREG_HI), true);
|
||||
};
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Mult:
|
||||
// TODO: Maybe IR could simplify when HI is not needed or clobbered?
|
||||
// TODO: HI/LO merge optimization? Have to be careful of passes that split them...
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL(gpr.R(IRREG_LO), lhs, rhs);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::MultU:
|
||||
// This is an "anti-norm32" case. Let's just zero always.
|
||||
// TODO: If we could know that LO was only needed, we could use MULW and be done.
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2);
|
||||
makeArgsUnsigned(&lhs, &rhs);
|
||||
MUL(gpr.R(IRREG_LO), lhs, rhs);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::Madd:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
ADD(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::MaddU:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
makeArgsUnsigned(&lhs, &rhs);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
ADD(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::Msub:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
SUB(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::MsubU:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
makeArgsUnsigned(&lhs, &rhs);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
SUB(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Div(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Div:
|
||||
case IROp::DivU:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
146
Core/MIPS/RiscV/RiscVCompBranch.cpp
Normal file
146
Core/MIPS/RiscV/RiscVCompBranch.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for exits.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_Exit(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg exitReg = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConst:
|
||||
FlushAll();
|
||||
LI(SCRATCH1, inst.constant);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
break;
|
||||
|
||||
case IROp::ExitToReg:
|
||||
exitReg = gpr.MapReg(inst.src1);
|
||||
FlushAll();
|
||||
// TODO: If ever we don't read this back in dispatcherPCInSCRATCH1_, we should zero upper.
|
||||
MV(SCRATCH1, exitReg);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
break;
|
||||
|
||||
case IROp::ExitToPC:
|
||||
FlushAll();
|
||||
QuickJ(R_RA, dispatcher_);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_ExitIf(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
FixupBranch fixup;
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfEq:
|
||||
case IROp::ExitToConstIfNeq:
|
||||
gpr.MapInIn(inst.src1, inst.src2);
|
||||
// We can't use SCRATCH1, which is destroyed by FlushAll()... but cheat and use R_RA.
|
||||
NormalizeSrc12(inst, &lhs, &rhs, R_RA, SCRATCH2, true);
|
||||
FlushAll();
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfEq:
|
||||
fixup = BNE(lhs, rhs);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfNeq:
|
||||
fixup = BEQ(lhs, rhs);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
LI(SCRATCH1, inst.constant);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
case IROp::ExitToConstIfGeZ:
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
gpr.MapReg(inst.src1);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH2, true);
|
||||
FlushAll();
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
fixup = BGE(R_ZERO, lhs);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfGeZ:
|
||||
fixup = BLT(lhs, R_ZERO);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
fixup = BGE(lhs, R_ZERO);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
fixup = BLT(R_ZERO, lhs);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
LI(SCRATCH1, inst.constant);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfFpTrue:
|
||||
case IROp::ExitToConstIfFpFalse:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
186
Core/MIPS/RiscV/RiscVCompFPU.cpp
Normal file
186
Core/MIPS/RiscV/RiscVCompFPU.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for floating point related instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_FArith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FAdd:
|
||||
case IROp::FSub:
|
||||
case IROp::FMul:
|
||||
case IROp::FDiv:
|
||||
case IROp::FSqrt:
|
||||
case IROp::FNeg:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FCondAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FMin:
|
||||
case IROp::FMax:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FMov:
|
||||
case IROp::FAbs:
|
||||
case IROp::FSign:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FRound(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FRound:
|
||||
case IROp::FTrunc:
|
||||
case IROp::FCeil:
|
||||
case IROp::FFloor:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FCvt(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FCvtWS:
|
||||
case IROp::FCvtSW:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FSat(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FSat0_1:
|
||||
case IROp::FSatMinus1_1:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FCompare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ZeroFpCond:
|
||||
case IROp::FCmp:
|
||||
case IROp::FCmovVfpuCC:
|
||||
case IROp::FCmpVfpuBit:
|
||||
case IROp::FCmpVfpuAggregate:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_RoundingMode(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::RestoreRoundingMode:
|
||||
case IROp::ApplyRoundingMode:
|
||||
case IROp::UpdateRoundingMode:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FSpecial(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FSin:
|
||||
case IROp::FCos:
|
||||
case IROp::FRSqrt:
|
||||
case IROp::FRecip:
|
||||
case IROp::FAsin:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
252
Core/MIPS/RiscV/RiscVCompLoadStore.cpp
Normal file
252
Core/MIPS/RiscV/RiscVCompLoadStore.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for load/store instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::SetScratch1ToSrc1Address(IRReg src1) {
|
||||
gpr.MapReg(src1);
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
SLLIW(SCRATCH1, gpr.R(src1), 2);
|
||||
SRLIW(SCRATCH1, SCRATCH1, 2);
|
||||
ADD(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
#else
|
||||
// Clear the top bits to be safe.
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
ADD_UW(SCRATCH1, gpr.R(src1), MEMBASEREG);
|
||||
} else {
|
||||
_assert_(XLEN == 64);
|
||||
SLLI(SCRATCH1, gpr.R(src1), 32);
|
||||
SRLI(SCRATCH1, SCRATCH1, 32);
|
||||
ADD(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t RiscVJit::AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant) {
|
||||
if (constant < -2048 || constant > 2047) {
|
||||
LI(SCRATCH2, constant);
|
||||
ADD(SCRATCH1, *reg, SCRATCH2);
|
||||
*reg = SCRATCH1;
|
||||
return 0;
|
||||
}
|
||||
return constant;
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Load(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
// If they're the same, MapReg may subtract MEMBASEREG, so just mark dirty.
|
||||
if (inst.dest == inst.src1)
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
else
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Load8:
|
||||
LBU(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load8Ext:
|
||||
LB(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load16:
|
||||
LHU(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load16Ext:
|
||||
LH(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load32:
|
||||
LW(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_LoadShift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Load32Left:
|
||||
case IROp::Load32Right:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FLoad(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::LoadFloat:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecLoad(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::LoadVec4:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Store(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
gpr.SpillLock(inst.src3, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
RiscVReg valueReg = gpr.TryMapTempImm(inst.src3);
|
||||
if (valueReg == INVALID_REG)
|
||||
valueReg = gpr.MapReg(inst.src3);
|
||||
gpr.ReleaseSpillLock(inst.src3, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Store8:
|
||||
SB(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Store16:
|
||||
SH(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Store32:
|
||||
SW(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_StoreShift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Store32Left:
|
||||
case IROp::Store32Right:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::StoreFloat:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::StoreVec4:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
145
Core/MIPS/RiscV/RiscVCompSystem.cpp
Normal file
145
Core/MIPS/RiscV/RiscVCompSystem.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for basic PC/downcount accounting, syscalls, debug funcs, etc.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_Basic(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::SetConst:
|
||||
// Sign extend all constants. We get 0xFFFFFFFF sometimes, and it's more work to truncate.
|
||||
// The register only holds 32 bits in the end anyway.
|
||||
gpr.SetImm(inst.dest, (int32_t)inst.constant);
|
||||
break;
|
||||
|
||||
case IROp::SetConstF:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::Downcount:
|
||||
if (inst.constant <= 2048) {
|
||||
ADDI(DOWNCOUNTREG, DOWNCOUNTREG, -(s32)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH1, inst.constant, SCRATCH2);
|
||||
SUB(DOWNCOUNTREG, DOWNCOUNTREG, SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SetPC:
|
||||
gpr.MapIn(inst.src1);
|
||||
MovToPC(gpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::SetPCConst:
|
||||
LI(SCRATCH1, inst.constant, SCRATCH2);
|
||||
MovToPC(SCRATCH1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Transfer(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::SetCtrlVFPU:
|
||||
case IROp::SetCtrlVFPUReg:
|
||||
case IROp::SetCtrlVFPUFReg:
|
||||
case IROp::FpCondToReg:
|
||||
case IROp::VfpuCtrlToReg:
|
||||
case IROp::FMovFromGPR:
|
||||
case IROp::FMovToGPR:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_System(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Interpret:
|
||||
case IROp::Syscall:
|
||||
case IROp::CallReplacement:
|
||||
case IROp::Break:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Breakpoint(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Breakpoint:
|
||||
case IROp::MemoryCheck:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_ValidateAddress(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ValidateAddress8:
|
||||
case IROp::ValidateAddress16:
|
||||
case IROp::ValidateAddress32:
|
||||
case IROp::ValidateAddress128:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
123
Core/MIPS/RiscV/RiscVCompVec.cpp
Normal file
123
Core/MIPS/RiscV/RiscVCompVec.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for vector instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_VecAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Init:
|
||||
case IROp::Vec4Shuffle:
|
||||
case IROp::Vec4Mov:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecArith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Add:
|
||||
case IROp::Vec4Sub:
|
||||
case IROp::Vec4Mul:
|
||||
case IROp::Vec4Div:
|
||||
case IROp::Vec4Scale:
|
||||
case IROp::Vec4Neg:
|
||||
case IROp::Vec4Abs:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecHoriz(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Dot:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecPack(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec2Unpack16To31:
|
||||
case IROp::Vec2Unpack16To32:
|
||||
case IROp::Vec4Unpack8To32:
|
||||
case IROp::Vec4DuplicateUpperBitsAndShift1:
|
||||
case IROp::Vec4Pack31To8:
|
||||
case IROp::Vec4Pack32To8:
|
||||
case IROp::Vec2Pack31To16:
|
||||
case IROp::Vec2Pack32To16:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecClamp(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4ClampToZero:
|
||||
case IROp::Vec2ClampToZero:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
557
Core/MIPS/RiscV/RiscVJit.cpp
Normal file
557
Core/MIPS/RiscV/RiscVJit.cpp
Normal file
@ -0,0 +1,557 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
#include "Common/Profiler/Profiler.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) {
|
||||
// Automatically disable incompatible options.
|
||||
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
|
||||
jo.enablePointerify = false;
|
||||
}
|
||||
|
||||
AllocCodeSpace(1024 * 1024 * 16);
|
||||
SetAutoCompress(true);
|
||||
|
||||
// TODO: Consider replacing block num method form IRJit - this is 2MB.
|
||||
blockStartAddrs_ = new const u8 *[MAX_ALLOWED_JIT_BLOCKS];
|
||||
memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS);
|
||||
|
||||
gpr.Init(this);
|
||||
// TODO: fpr
|
||||
|
||||
GenerateFixedCode(jo);
|
||||
}
|
||||
|
||||
RiscVJit::~RiscVJit() {
|
||||
delete [] blockStartAddrs_;
|
||||
}
|
||||
|
||||
void RiscVJit::RunLoopUntil(u64 globalticks) {
|
||||
PROFILE_THIS_SCOPE("jit");
|
||||
((void (*)())enterDispatcher_)();
|
||||
}
|
||||
|
||||
bool RiscVJit::CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload) {
|
||||
// Check that we're not full (we allow less blocks than IR itself.)
|
||||
if (blocks_.GetNumBlocks() >= MAX_ALLOWED_JIT_BLOCKS - 1)
|
||||
return false;
|
||||
|
||||
if (!IRJit::CompileBlock(em_address, instructions, mipsBytes, preload))
|
||||
return false;
|
||||
|
||||
// TODO: Block linking, checked entries and such.
|
||||
|
||||
int block_num;
|
||||
if (preload) {
|
||||
block_num = blocks_.GetBlockNumberFromStartAddress(em_address);
|
||||
} else {
|
||||
u32 first_inst = Memory::ReadUnchecked_U32(em_address);
|
||||
_assert_msg_(MIPS_IS_RUNBLOCK(first_inst), "Should've written an emuhack");
|
||||
|
||||
block_num = first_inst & MIPS_EMUHACK_VALUE_MASK;
|
||||
}
|
||||
|
||||
_assert_msg_(block_num >= 0 && block_num < MAX_ALLOWED_JIT_BLOCKS, "Bad block num");
|
||||
_assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block %d reused before clear", block_num);
|
||||
blockStartAddrs_[block_num] = GetCodePointer();
|
||||
|
||||
gpr.Start();
|
||||
// TODO: fpr.
|
||||
|
||||
for (const IRInst &inst : instructions) {
|
||||
CompileIRInst(inst);
|
||||
|
||||
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
|
||||
gpr.FlushAll();
|
||||
}
|
||||
// TODO
|
||||
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
|
||||
//fpr.FlushAll();
|
||||
}
|
||||
|
||||
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
|
||||
if (GetSpaceLeft() < 0x800) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: a properly constructed block should never get here.
|
||||
// TODO: Need to do more than just this? Call a func to set an exception?
|
||||
QuickJ(R_RA, crashHandler_);
|
||||
|
||||
FlushIcache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static u32 DoIRInst(uint64_t value) {
|
||||
IRInst inst;
|
||||
memcpy(&inst, &value, sizeof(inst));
|
||||
|
||||
return IRInterpret(currentMIPS, &inst, 1);
|
||||
}
|
||||
|
||||
void RiscVJit::CompileIRInst(IRInst inst) {
|
||||
switch (inst.op) {
|
||||
case IROp::Nop:
|
||||
break;
|
||||
|
||||
case IROp::SetConst:
|
||||
case IROp::SetConstF:
|
||||
case IROp::Downcount:
|
||||
case IROp::SetPC:
|
||||
case IROp::SetPCConst:
|
||||
CompIR_Basic(inst);
|
||||
break;
|
||||
|
||||
case IROp::Add:
|
||||
case IROp::Sub:
|
||||
case IROp::AddConst:
|
||||
case IROp::SubConst:
|
||||
case IROp::Neg:
|
||||
CompIR_Arith(inst);
|
||||
break;
|
||||
|
||||
case IROp::And:
|
||||
case IROp::Or:
|
||||
case IROp::Xor:
|
||||
case IROp::AndConst:
|
||||
case IROp::OrConst:
|
||||
case IROp::XorConst:
|
||||
case IROp::Not:
|
||||
CompIR_Logic(inst);
|
||||
break;
|
||||
|
||||
case IROp::Mov:
|
||||
case IROp::Ext8to32:
|
||||
case IROp::Ext16to32:
|
||||
CompIR_Assign(inst);
|
||||
break;
|
||||
|
||||
case IROp::ReverseBits:
|
||||
case IROp::BSwap16:
|
||||
case IROp::BSwap32:
|
||||
case IROp::Clz:
|
||||
CompIR_Bits(inst);
|
||||
break;
|
||||
|
||||
case IROp::Shl:
|
||||
case IROp::Shr:
|
||||
case IROp::Sar:
|
||||
case IROp::Ror:
|
||||
case IROp::ShlImm:
|
||||
case IROp::ShrImm:
|
||||
case IROp::SarImm:
|
||||
case IROp::RorImm:
|
||||
CompIR_Shift(inst);
|
||||
break;
|
||||
|
||||
case IROp::Slt:
|
||||
case IROp::SltConst:
|
||||
case IROp::SltU:
|
||||
case IROp::SltUConst:
|
||||
CompIR_Compare(inst);
|
||||
break;
|
||||
|
||||
case IROp::MovZ:
|
||||
case IROp::MovNZ:
|
||||
case IROp::Max:
|
||||
case IROp::Min:
|
||||
CompIR_CondAssign(inst);
|
||||
break;
|
||||
|
||||
case IROp::MtLo:
|
||||
case IROp::MtHi:
|
||||
case IROp::MfLo:
|
||||
case IROp::MfHi:
|
||||
CompIR_HiLo(inst);
|
||||
break;
|
||||
|
||||
case IROp::Mult:
|
||||
case IROp::MultU:
|
||||
case IROp::Madd:
|
||||
case IROp::MaddU:
|
||||
case IROp::Msub:
|
||||
case IROp::MsubU:
|
||||
CompIR_Mult(inst);
|
||||
break;
|
||||
|
||||
case IROp::Div:
|
||||
case IROp::DivU:
|
||||
CompIR_Div(inst);
|
||||
break;
|
||||
|
||||
case IROp::Load8:
|
||||
case IROp::Load8Ext:
|
||||
case IROp::Load16:
|
||||
case IROp::Load16Ext:
|
||||
case IROp::Load32:
|
||||
CompIR_Load(inst);
|
||||
break;
|
||||
|
||||
case IROp::Load32Left:
|
||||
case IROp::Load32Right:
|
||||
CompIR_LoadShift(inst);
|
||||
break;
|
||||
|
||||
case IROp::LoadFloat:
|
||||
CompIR_FLoad(inst);
|
||||
break;
|
||||
|
||||
case IROp::LoadVec4:
|
||||
CompIR_VecLoad(inst);
|
||||
break;
|
||||
|
||||
case IROp::Store8:
|
||||
case IROp::Store16:
|
||||
case IROp::Store32:
|
||||
CompIR_Store(inst);
|
||||
break;
|
||||
|
||||
case IROp::Store32Left:
|
||||
case IROp::Store32Right:
|
||||
CompIR_StoreShift(inst);
|
||||
break;
|
||||
|
||||
case IROp::StoreFloat:
|
||||
CompIR_FStore(inst);
|
||||
break;
|
||||
|
||||
case IROp::StoreVec4:
|
||||
CompIR_VecStore(inst);
|
||||
break;
|
||||
|
||||
case IROp::FAdd:
|
||||
case IROp::FSub:
|
||||
case IROp::FMul:
|
||||
case IROp::FDiv:
|
||||
case IROp::FSqrt:
|
||||
case IROp::FNeg:
|
||||
CompIR_FArith(inst);
|
||||
break;
|
||||
|
||||
case IROp::FMin:
|
||||
case IROp::FMax:
|
||||
CompIR_FCondAssign(inst);
|
||||
break;
|
||||
|
||||
case IROp::FMov:
|
||||
case IROp::FAbs:
|
||||
case IROp::FSign:
|
||||
CompIR_FAssign(inst);
|
||||
break;
|
||||
|
||||
case IROp::FRound:
|
||||
case IROp::FTrunc:
|
||||
case IROp::FCeil:
|
||||
case IROp::FFloor:
|
||||
CompIR_FRound(inst);
|
||||
break;
|
||||
|
||||
case IROp::FCvtWS:
|
||||
case IROp::FCvtSW:
|
||||
CompIR_FCvt(inst);
|
||||
break;
|
||||
|
||||
case IROp::FSat0_1:
|
||||
case IROp::FSatMinus1_1:
|
||||
CompIR_FSat(inst);
|
||||
break;
|
||||
|
||||
case IROp::ZeroFpCond:
|
||||
case IROp::FCmp:
|
||||
case IROp::FCmovVfpuCC:
|
||||
case IROp::FCmpVfpuBit:
|
||||
case IROp::FCmpVfpuAggregate:
|
||||
CompIR_FCompare(inst);
|
||||
break;
|
||||
|
||||
case IROp::RestoreRoundingMode:
|
||||
case IROp::ApplyRoundingMode:
|
||||
case IROp::UpdateRoundingMode:
|
||||
CompIR_RoundingMode(inst);
|
||||
break;
|
||||
|
||||
case IROp::SetCtrlVFPU:
|
||||
case IROp::SetCtrlVFPUReg:
|
||||
case IROp::SetCtrlVFPUFReg:
|
||||
case IROp::FpCondToReg:
|
||||
case IROp::VfpuCtrlToReg:
|
||||
case IROp::FMovFromGPR:
|
||||
case IROp::FMovToGPR:
|
||||
CompIR_Transfer(inst);
|
||||
break;
|
||||
|
||||
case IROp::Vec4Init:
|
||||
case IROp::Vec4Shuffle:
|
||||
case IROp::Vec4Mov:
|
||||
CompIR_VecAssign(inst);
|
||||
break;
|
||||
|
||||
case IROp::Vec4Add:
|
||||
case IROp::Vec4Sub:
|
||||
case IROp::Vec4Mul:
|
||||
case IROp::Vec4Div:
|
||||
case IROp::Vec4Scale:
|
||||
case IROp::Vec4Neg:
|
||||
case IROp::Vec4Abs:
|
||||
CompIR_VecArith(inst);
|
||||
break;
|
||||
|
||||
case IROp::Vec4Dot:
|
||||
CompIR_VecHoriz(inst);
|
||||
break;
|
||||
|
||||
case IROp::Vec2Unpack16To31:
|
||||
case IROp::Vec2Unpack16To32:
|
||||
case IROp::Vec4Unpack8To32:
|
||||
case IROp::Vec4DuplicateUpperBitsAndShift1:
|
||||
case IROp::Vec4Pack31To8:
|
||||
case IROp::Vec4Pack32To8:
|
||||
case IROp::Vec2Pack31To16:
|
||||
case IROp::Vec2Pack32To16:
|
||||
CompIR_VecPack(inst);
|
||||
break;
|
||||
|
||||
case IROp::Vec4ClampToZero:
|
||||
case IROp::Vec2ClampToZero:
|
||||
CompIR_VecClamp(inst);
|
||||
break;
|
||||
|
||||
case IROp::FSin:
|
||||
case IROp::FCos:
|
||||
case IROp::FRSqrt:
|
||||
case IROp::FRecip:
|
||||
case IROp::FAsin:
|
||||
CompIR_FSpecial(inst);
|
||||
break;
|
||||
|
||||
case IROp::Interpret:
|
||||
case IROp::Syscall:
|
||||
case IROp::CallReplacement:
|
||||
case IROp::Break:
|
||||
CompIR_System(inst);
|
||||
break;
|
||||
|
||||
case IROp::Breakpoint:
|
||||
case IROp::MemoryCheck:
|
||||
CompIR_Breakpoint(inst);
|
||||
break;
|
||||
|
||||
case IROp::ValidateAddress8:
|
||||
case IROp::ValidateAddress16:
|
||||
case IROp::ValidateAddress32:
|
||||
case IROp::ValidateAddress128:
|
||||
CompIR_ValidateAddress(inst);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConst:
|
||||
case IROp::ExitToReg:
|
||||
case IROp::ExitToPC:
|
||||
CompIR_Exit(inst);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfEq:
|
||||
case IROp::ExitToConstIfNeq:
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
case IROp::ExitToConstIfGeZ:
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
case IROp::ExitToConstIfFpTrue:
|
||||
case IROp::ExitToConstIfFpFalse:
|
||||
CompIR_ExitIf(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
_assert_msg_(false, "Unexpected IR op %d", (int)inst.op);
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Generic(IRInst inst) {
|
||||
// For now, we're gonna do it the slow and ugly way.
|
||||
// Maybe there's a smarter way to fallback?
|
||||
uint64_t value;
|
||||
memcpy(&value, &inst, sizeof(inst));
|
||||
|
||||
FlushAll();
|
||||
LI(X10, value, SCRATCH2);
|
||||
SaveStaticRegisters();
|
||||
QuickCallFunction(&DoIRInst);
|
||||
LoadStaticRegisters();
|
||||
// Result in X10 aka SCRATCH1.
|
||||
_assert_(X10 == SCRATCH1);
|
||||
if (BInRange(dispatcherPCInSCRATCH1_)) {
|
||||
BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_);
|
||||
} else {
|
||||
FixupBranch skip = BEQ(X10, R_ZERO);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
SetJumpTarget(skip);
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::FlushAll() {
|
||||
gpr.FlushAll();
|
||||
// TODO: fpr.
|
||||
}
|
||||
|
||||
bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) {
|
||||
// Used in disassembly viewer.
|
||||
if (ptr == dispatcher_) {
|
||||
name = "dispatcher";
|
||||
} else if (ptr == dispatcherPCInSCRATCH1_) {
|
||||
name = "dispatcher (PC in SCRATCH1)";
|
||||
} else if (ptr == dispatcherNoCheck_) {
|
||||
name = "dispatcherNoCheck";
|
||||
} else if (ptr == saveStaticRegisters_) {
|
||||
name = "saveStaticRegisters";
|
||||
} else if (ptr == loadStaticRegisters_) {
|
||||
name = "loadStaticRegisters";
|
||||
} else if (ptr == enterDispatcher_) {
|
||||
name = "enterDispatcher";
|
||||
} else if (!IsInSpace(ptr)) {
|
||||
return false;
|
||||
} else {
|
||||
uintptr_t uptr = (uintptr_t)ptr;
|
||||
int block_num = -1;
|
||||
for (int i = 0; i < MAX_ALLOWED_JIT_BLOCKS; ++i) {
|
||||
uintptr_t blockptr = (uintptr_t)blockStartAddrs_[i];
|
||||
// Out of allocated blocks.
|
||||
if (uptr == 0)
|
||||
break;
|
||||
|
||||
if (uptr >= blockptr)
|
||||
block_num = i;
|
||||
if (uptr < blockptr)
|
||||
break;
|
||||
}
|
||||
|
||||
if (block_num == -1) {
|
||||
name = "(unknown or deleted block)";
|
||||
return true;
|
||||
}
|
||||
|
||||
const IRBlock *block = blocks_.GetBlock(block_num);
|
||||
if (block) {
|
||||
u32 start = 0, size = 0;
|
||||
block->GetRange(start, size);
|
||||
name = StringFromFormat("(block %d at %08x)", block_num, start);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RiscVJit::CodeInRange(const u8 *ptr) const {
|
||||
return IsInSpace(ptr);
|
||||
}
|
||||
|
||||
bool RiscVJit::IsAtDispatchFetch(const u8 *ptr) const {
|
||||
return ptr == dispatcherFetch_;
|
||||
}
|
||||
|
||||
const u8 *RiscVJit::GetDispatcher() const {
|
||||
return dispatcher_;
|
||||
}
|
||||
|
||||
const u8 *RiscVJit::GetCrashHandler() const {
|
||||
return crashHandler_;
|
||||
}
|
||||
|
||||
void RiscVJit::ClearCache() {
|
||||
IRJit::ClearCache();
|
||||
|
||||
ClearCodeSpace(jitStartOffset_);
|
||||
FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_);
|
||||
|
||||
memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS);
|
||||
}
|
||||
|
||||
void RiscVJit::UpdateFCR31() {
|
||||
IRJit::UpdateFCR31();
|
||||
|
||||
// TODO: Handle rounding modes?
|
||||
}
|
||||
|
||||
void RiscVJit::RestoreRoundingMode(bool force) {
|
||||
// TODO: Could maybe skip if not hasSetRounding? But that's on IRFrontend...
|
||||
FSRMI(Round::NEAREST_EVEN);
|
||||
}
|
||||
|
||||
void RiscVJit::ApplyRoundingMode(bool force) {
|
||||
// TODO: Also could maybe sometimes skip?
|
||||
//QuickCallFunction(applyRoundingMode_);
|
||||
}
|
||||
|
||||
void RiscVJit::MovFromPC(RiscVReg r) {
|
||||
LWU(r, CTXREG, offsetof(MIPSState, pc));
|
||||
}
|
||||
|
||||
void RiscVJit::MovToPC(RiscVReg r) {
|
||||
SW(r, CTXREG, offsetof(MIPSState, pc));
|
||||
}
|
||||
|
||||
void RiscVJit::SaveStaticRegisters() {
|
||||
if (jo.useStaticAlloc) {
|
||||
QuickCallFunction(saveStaticRegisters_);
|
||||
} else {
|
||||
// Inline the single operation
|
||||
SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::LoadStaticRegisters() {
|
||||
if (jo.useStaticAlloc) {
|
||||
QuickCallFunction(loadStaticRegisters_);
|
||||
} else {
|
||||
LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::NormalizeSrc1(IRInst inst, RiscVReg *reg, RiscVReg tempReg, bool allowOverlap) {
|
||||
*reg = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, tempReg);
|
||||
}
|
||||
|
||||
void RiscVJit::NormalizeSrc12(IRInst inst, RiscVReg *lhs, RiscVReg *rhs, RiscVReg lhsTempReg, RiscVReg rhsTempReg, bool allowOverlap) {
|
||||
*lhs = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, lhsTempReg);
|
||||
*rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg);
|
||||
}
|
||||
|
||||
RiscVReg RiscVJit::NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVReg tempReg) {
|
||||
// For proper compare, we must sign extend so they both match or don't match.
|
||||
// But don't change pointers, in case one is SP (happens in LittleBigPlanet.)
|
||||
if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0) {
|
||||
return R_ZERO;
|
||||
} else if (gpr.IsMappedAsPointer(rs) || rs == rd) {
|
||||
return gpr.Normalize32(rs, tempReg);
|
||||
} else {
|
||||
return gpr.Normalize32(rs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
138
Core/MIPS/RiscV/RiscVJit.h
Normal file
138
Core/MIPS/RiscV/RiscVJit.h
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Common/RiscVEmitter.h"
|
||||
#include "Core/MIPS/IR/IRJit.h"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
class RiscVJit : public RiscVGen::RiscVCodeBlock, public IRJit {
|
||||
public:
|
||||
RiscVJit(MIPSState *mipsState);
|
||||
~RiscVJit();
|
||||
|
||||
void RunLoopUntil(u64 globalticks) override;
|
||||
|
||||
bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
|
||||
bool CodeInRange(const u8 *ptr) const override;
|
||||
bool IsAtDispatchFetch(const u8 *ptr) const override;
|
||||
const u8 *GetDispatcher() const override;
|
||||
const u8 *GetCrashHandler() const override;
|
||||
|
||||
void ClearCache() override;
|
||||
void UpdateFCR31() override;
|
||||
|
||||
// TODO: GetBlockCacheDebugInterface, block linking?
|
||||
|
||||
protected:
|
||||
bool CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload) override;
|
||||
|
||||
void CompileIRInst(IRInst inst);
|
||||
|
||||
private:
|
||||
void GenerateFixedCode(const JitOptions &jo);
|
||||
|
||||
void RestoreRoundingMode(bool force = false);
|
||||
void ApplyRoundingMode(bool force = false);
|
||||
void MovFromPC(RiscVGen::RiscVReg r);
|
||||
void MovToPC(RiscVGen::RiscVReg r);
|
||||
|
||||
void SaveStaticRegisters();
|
||||
void LoadStaticRegisters();
|
||||
|
||||
// Note: destroys SCRATCH1.
|
||||
void FlushAll();
|
||||
|
||||
void CompIR_Arith(IRInst inst);
|
||||
void CompIR_Assign(IRInst inst);
|
||||
void CompIR_Basic(IRInst inst);
|
||||
void CompIR_Bits(IRInst inst);
|
||||
void CompIR_Breakpoint(IRInst inst);
|
||||
void CompIR_Compare(IRInst inst);
|
||||
void CompIR_CondAssign(IRInst inst);
|
||||
void CompIR_Div(IRInst inst);
|
||||
void CompIR_Exit(IRInst inst);
|
||||
void CompIR_ExitIf(IRInst inst);
|
||||
void CompIR_FArith(IRInst inst);
|
||||
void CompIR_FAssign(IRInst inst);
|
||||
void CompIR_FCompare(IRInst inst);
|
||||
void CompIR_FCondAssign(IRInst inst);
|
||||
void CompIR_FCvt(IRInst inst);
|
||||
void CompIR_FLoad(IRInst inst);
|
||||
void CompIR_FRound(IRInst inst);
|
||||
void CompIR_FSat(IRInst inst);
|
||||
void CompIR_FSpecial(IRInst inst);
|
||||
void CompIR_FStore(IRInst inst);
|
||||
void CompIR_Generic(IRInst inst);
|
||||
void CompIR_HiLo(IRInst inst);
|
||||
void CompIR_Load(IRInst inst);
|
||||
void CompIR_LoadShift(IRInst inst);
|
||||
void CompIR_Logic(IRInst inst);
|
||||
void CompIR_Mult(IRInst inst);
|
||||
void CompIR_RoundingMode(IRInst inst);
|
||||
void CompIR_Shift(IRInst inst);
|
||||
void CompIR_Store(IRInst inst);
|
||||
void CompIR_StoreShift(IRInst inst);
|
||||
void CompIR_System(IRInst inst);
|
||||
void CompIR_Transfer(IRInst inst);
|
||||
void CompIR_VecArith(IRInst inst);
|
||||
void CompIR_VecAssign(IRInst inst);
|
||||
void CompIR_VecClamp(IRInst inst);
|
||||
void CompIR_VecHoriz(IRInst inst);
|
||||
void CompIR_VecLoad(IRInst inst);
|
||||
void CompIR_VecPack(IRInst inst);
|
||||
void CompIR_VecStore(IRInst inst);
|
||||
void CompIR_ValidateAddress(IRInst inst);
|
||||
|
||||
void SetScratch1ToSrc1Address(IRReg src1);
|
||||
// Modifies SCRATCH regs.
|
||||
int32_t AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant);
|
||||
void NormalizeSrc1(IRInst inst, RiscVGen::RiscVReg *reg, RiscVGen::RiscVReg tempReg, bool allowOverlap);
|
||||
void NormalizeSrc12(IRInst inst, RiscVGen::RiscVReg *lhs, RiscVGen::RiscVReg *rhs, RiscVGen::RiscVReg lhsTempReg, RiscVGen::RiscVReg rhsTempReg, bool allowOverlap);
|
||||
RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg);
|
||||
|
||||
RiscVRegCache gpr;
|
||||
|
||||
static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144;
|
||||
|
||||
const u8 *enterDispatcher_ = nullptr;
|
||||
|
||||
const u8 *outerLoop_ = nullptr;
|
||||
const u8 *outerLoopPCInSCRATCH1_ = nullptr;
|
||||
const u8 *dispatcherCheckCoreState_ = nullptr;
|
||||
const u8 *dispatcherPCInSCRATCH1_ = nullptr;
|
||||
const u8 *dispatcher_ = nullptr;
|
||||
const u8 *dispatcherNoCheck_ = nullptr;
|
||||
const u8 *dispatcherFetch_ = nullptr;
|
||||
|
||||
const u8 *saveStaticRegisters_ = nullptr;
|
||||
const u8 *loadStaticRegisters_ = nullptr;
|
||||
|
||||
const u8 *crashHandler_ = nullptr;
|
||||
|
||||
int jitStartOffset_ = 0;
|
||||
const u8 **blockStartAddrs_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace MIPSComp
|
1075
Core/MIPS/RiscV/RiscVRegCache.cpp
Normal file
1075
Core/MIPS/RiscV/RiscVRegCache.cpp
Normal file
File diff suppressed because it is too large
Load Diff
191
Core/MIPS/RiscV/RiscVRegCache.h
Normal file
191
Core/MIPS/RiscV/RiscVRegCache.h
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/RiscVEmitter.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
|
||||
namespace RiscVJitConstants {
|
||||
|
||||
// Note: we don't support 32-bit or 128-bit CPUs currently.
|
||||
constexpr int XLEN = 64;
|
||||
|
||||
const RiscVGen::RiscVReg DOWNCOUNTREG = RiscVGen::X24;
|
||||
const RiscVGen::RiscVReg JITBASEREG = RiscVGen::X25;
|
||||
const RiscVGen::RiscVReg CTXREG = RiscVGen::X26;
|
||||
const RiscVGen::RiscVReg MEMBASEREG = RiscVGen::X27;
|
||||
// TODO: Experiment. X7-X13 are compressed regs. X8/X9 are saved so nice for static alloc, though.
|
||||
const RiscVGen::RiscVReg SCRATCH1 = RiscVGen::X10;
|
||||
const RiscVGen::RiscVReg SCRATCH2 = RiscVGen::X11;
|
||||
|
||||
// Have to account for all of them due to temps, etc.
|
||||
constexpr int TOTAL_MAPPABLE_MIPSREGS = 256;
|
||||
|
||||
enum class MIPSLoc {
|
||||
IMM,
|
||||
RVREG,
|
||||
// In a native reg, but an adjusted pointer (not pointerified - unaligned.)
|
||||
RVREG_AS_PTR,
|
||||
// In a native reg, but also has a known immediate value.
|
||||
RVREG_IMM,
|
||||
MEM,
|
||||
};
|
||||
|
||||
// Initing is the default so the flag is reversed.
|
||||
enum class MIPSMap {
|
||||
INIT = 0,
|
||||
DIRTY = 1,
|
||||
NOINIT = 2 | DIRTY,
|
||||
MARK_NORM32 = 4,
|
||||
};
|
||||
static inline MIPSMap operator |(const MIPSMap &lhs, const MIPSMap &rhs) {
|
||||
return MIPSMap((int)lhs | (int)rhs);
|
||||
}
|
||||
static inline MIPSMap operator &(const MIPSMap &lhs, const MIPSMap &rhs) {
|
||||
return MIPSMap((int)lhs & (int)rhs);
|
||||
}
|
||||
|
||||
enum class MapType {
|
||||
AVOID_LOAD,
|
||||
AVOID_LOAD_MARK_NORM32,
|
||||
ALWAYS_LOAD,
|
||||
};
|
||||
|
||||
} // namespace RiscVJitConstants
|
||||
|
||||
namespace MIPSAnalyst {
|
||||
struct AnalysisResults;
|
||||
};
|
||||
|
||||
namespace MIPSComp {
|
||||
struct JitOptions;
|
||||
}
|
||||
|
||||
// Not using IRReg since this can be -1.
|
||||
typedef int IRRegIndex;
|
||||
constexpr IRRegIndex IRREG_INVALID = -1;
|
||||
|
||||
struct RegStatusRiscV {
|
||||
IRRegIndex mipsReg; // if -1, no mipsreg attached.
|
||||
bool isDirty; // Should the register be written back?
|
||||
bool pointerified; // Has added the memory base into the top part of the reg. Note - still usable as 32-bit reg (in only some cases.)
|
||||
bool tempLocked; // Reserved for a temp register.
|
||||
bool normalized32; // 32 bits sign extended to XLEN. RISC-V can't always address just the low 32-bits, so this matters.
|
||||
};
|
||||
|
||||
struct RegStatusMIPS {
|
||||
// Where is this MIPS register?
|
||||
RiscVJitConstants::MIPSLoc loc;
|
||||
// Data (both or only one may be used, depending on loc.)
|
||||
u64 imm;
|
||||
RiscVGen::RiscVReg reg; // reg index
|
||||
bool spillLock; // if true, this register cannot be spilled.
|
||||
bool isStatic; // if true, this register will not be written back to ram by the regcache
|
||||
// If loc == ML_MEM, it's back in its location in the CPU context struct.
|
||||
};
|
||||
|
||||
class RiscVRegCache {
|
||||
public:
|
||||
RiscVRegCache(MIPSState *mipsState, MIPSComp::JitOptions *jo);
|
||||
~RiscVRegCache() {}
|
||||
|
||||
void Init(RiscVGen::RiscVEmitter *emitter);
|
||||
// TODO: Maybe pass in IR block and start PC for logging/debugging?
|
||||
void Start();
|
||||
|
||||
// Protect the arm register containing a MIPS register from spilling, to ensure that
|
||||
// it's being kept allocated.
|
||||
void SpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID);
|
||||
void ReleaseSpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID);
|
||||
void ReleaseSpillLocksAndDiscardTemps();
|
||||
|
||||
void SetImm(IRRegIndex reg, u64 immVal);
|
||||
bool IsImm(IRRegIndex reg) const;
|
||||
bool IsPureImm(IRRegIndex reg) const;
|
||||
u64 GetImm(IRRegIndex reg) const;
|
||||
// Optimally set a register to an imm value (possibly using another register.)
|
||||
void SetRegImm(RiscVGen::RiscVReg reg, u64 imm);
|
||||
|
||||
// May fail and return INVALID_REG if it needs flushing.
|
||||
RiscVGen::RiscVReg TryMapTempImm(IRRegIndex);
|
||||
|
||||
// Returns an ARM register containing the requested MIPS register.
|
||||
RiscVGen::RiscVReg MapReg(IRRegIndex reg, RiscVJitConstants::MIPSMap mapFlags = RiscVJitConstants::MIPSMap::INIT);
|
||||
RiscVGen::RiscVReg MapRegAsPointer(IRRegIndex reg);
|
||||
|
||||
bool IsMapped(IRRegIndex reg);
|
||||
bool IsMappedAsPointer(IRRegIndex reg);
|
||||
bool IsMappedAsStaticPointer(IRRegIndex reg);
|
||||
bool IsInRAM(IRRegIndex reg);
|
||||
bool IsNormalized32(IRRegIndex reg);
|
||||
|
||||
void MarkDirty(RiscVGen::RiscVReg reg, bool andNormalized32 = false);
|
||||
void MarkPtrDirty(RiscVGen::RiscVReg reg);
|
||||
// Copies to another reg if specified, otherwise same reg.
|
||||
RiscVGen::RiscVReg Normalize32(IRRegIndex reg, RiscVGen::RiscVReg destReg = RiscVGen::INVALID_REG);
|
||||
void MapIn(IRRegIndex rs);
|
||||
void MapInIn(IRRegIndex rd, IRRegIndex rs);
|
||||
void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD);
|
||||
void FlushRiscVReg(RiscVGen::RiscVReg r);
|
||||
void FlushBeforeCall();
|
||||
void FlushAll();
|
||||
void FlushR(IRRegIndex r);
|
||||
void DiscardR(IRRegIndex r);
|
||||
|
||||
RiscVGen::RiscVReg GetAndLockTempR();
|
||||
|
||||
RiscVGen::RiscVReg R(IRRegIndex preg); // Returns a cached register, while checking that it's NOT mapped as a pointer
|
||||
RiscVGen::RiscVReg RPtr(IRRegIndex preg); // Returns a cached register, if it has been mapped as a pointer
|
||||
|
||||
// These are called once on startup to generate functions, that you should then call.
|
||||
void EmitLoadStaticRegisters();
|
||||
void EmitSaveStaticRegisters();
|
||||
|
||||
private:
|
||||
struct StaticAllocation {
|
||||
IRRegIndex mr;
|
||||
RiscVGen::RiscVReg ar;
|
||||
bool pointerified;
|
||||
};
|
||||
const StaticAllocation *GetStaticAllocations(int &count);
|
||||
const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count);
|
||||
void MapRegTo(RiscVGen::RiscVReg reg, IRRegIndex mipsReg, RiscVJitConstants::MIPSMap mapFlags);
|
||||
RiscVGen::RiscVReg AllocateReg();
|
||||
RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered);
|
||||
RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r);
|
||||
void AddMemBase(RiscVGen::RiscVReg reg);
|
||||
int GetMipsRegOffset(IRRegIndex r);
|
||||
|
||||
bool IsValidReg(IRRegIndex r) const;
|
||||
bool IsValidRegNoZero(IRRegIndex r) const;
|
||||
|
||||
MIPSState *mips_;
|
||||
RiscVGen::RiscVEmitter *emit_ = nullptr;
|
||||
MIPSComp::JitOptions *jo_;
|
||||
|
||||
enum {
|
||||
NUM_RVREG = 32, // 31 actual registers, plus the zero/sp register which is not mappable.
|
||||
NUM_MIPSREG = RiscVJitConstants::TOTAL_MAPPABLE_MIPSREGS,
|
||||
};
|
||||
|
||||
RegStatusRiscV ar[NUM_RVREG]{};
|
||||
RegStatusMIPS mr[NUM_MIPSREG]{};
|
||||
};
|
16
Core/MIPS/RiscV/RiscVRegCacheFPU.cpp
Normal file
16
Core/MIPS/RiscV/RiscVRegCacheFPU.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
18
Core/MIPS/RiscV/RiscVRegCacheFPU.h
Normal file
18
Core/MIPS/RiscV/RiscVRegCacheFPU.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
@ -153,10 +153,6 @@ void FakeJit::Comp_RunBlock(MIPSOpcode op)
|
||||
ERROR_LOG(JIT, "Comp_RunBlock should never be reached!");
|
||||
}
|
||||
|
||||
bool FakeJit::ReplaceJalTo(u32 dest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void FakeJit::Comp_ReplacementFunc(MIPSOpcode op)
|
||||
{
|
||||
}
|
||||
|
@ -162,8 +162,6 @@ private:
|
||||
void MovFromPC(FakeReg r);
|
||||
void MovToPC(FakeReg r);
|
||||
|
||||
bool ReplaceJalTo(u32 dest);
|
||||
|
||||
void SaveDowncount();
|
||||
void RestoreDowncount();
|
||||
|
||||
|
@ -263,10 +263,15 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
#endif
|
||||
Core_ExecException(targetAddr, currentMIPS->pc, ExecExceptionType::JUMP);
|
||||
// Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately.
|
||||
context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler();
|
||||
ERROR_LOG(MEMMAP, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress);
|
||||
inCrashHandler = false;
|
||||
return true;
|
||||
uintptr_t crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler();
|
||||
if (crashHandler != 0) {
|
||||
context->CTX_PC = crashHandler;
|
||||
ERROR_LOG(MEMMAP, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress);
|
||||
inCrashHandler = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
type = MemoryExceptionType::UNKNOWN;
|
||||
} else if (success) {
|
||||
if (info.isMemoryWrite) {
|
||||
type = MemoryExceptionType::WRITE_WORD;
|
||||
@ -303,8 +308,11 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
|
||||
g_lastCrashAddress = codePtr;
|
||||
|
||||
// Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately.
|
||||
uintptr_t crashHandler = 0;
|
||||
if (MIPSComp::jit)
|
||||
context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler();
|
||||
crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler();
|
||||
if (crashHandler != 0)
|
||||
context->CTX_PC = crashHandler;
|
||||
else
|
||||
handled = false;
|
||||
ERROR_LOG(MEMMAP, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str());
|
||||
|
@ -1003,7 +1003,7 @@ void JitCompareScreen::UpdateDisasm() {
|
||||
snprintf(temp, sizeof(temp), "%i/%i", currentBlock_, blockCacheDebug->GetNumBlocks());
|
||||
blockName_->SetText(temp);
|
||||
|
||||
if (currentBlock_ < 0 || currentBlock_ >= blockCacheDebug->GetNumBlocks()) {
|
||||
if (currentBlock_ < 0 || !blockCacheDebug || currentBlock_ >= blockCacheDebug->GetNumBlocks()) {
|
||||
leftDisasm_->Add(new TextView(dev->T("No block")));
|
||||
rightDisasm_->Add(new TextView(dev->T("No block")));
|
||||
blockStats_->SetText("");
|
||||
@ -1067,6 +1067,9 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) {
|
||||
}
|
||||
|
||||
JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface();
|
||||
if (!blockCache)
|
||||
return UI::EVENT_DONE;
|
||||
|
||||
BlockCacheStats bcStats;
|
||||
blockCache->ComputeStats(bcStats);
|
||||
NOTICE_LOG(JIT, "Num blocks: %i", bcStats.numBlocks);
|
||||
|
@ -178,26 +178,18 @@ bool TestJit() {
|
||||
jit_speed = ExecCPUTest();
|
||||
|
||||
// Disassemble
|
||||
JitBlockCache *cache = MIPSComp::jit->GetBlockCache();
|
||||
JitBlock *block = cache->GetBlock(0); // Should only be one block.
|
||||
#if PPSSPP_ARCH(ARM)
|
||||
std::vector<std::string> lines = DisassembleArm2(block->normalEntry, block->codeSize);
|
||||
#elif PPSSPP_ARCH(ARM64)
|
||||
std::vector<std::string> lines = DisassembleArm64(block->normalEntry, block->codeSize);
|
||||
#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
std::vector<std::string> lines = DisassembleX86(block->normalEntry, block->codeSize);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
std::vector<std::string> lines = DisassembleRV64(block->normalEntry, block->codeSize);
|
||||
#else
|
||||
std::vector<std::string> lines;
|
||||
#endif
|
||||
// Cut off at 25 due to the repetition above. Might need tweaking for large instructions.
|
||||
const int cutoff = 25;
|
||||
for (int i = 0; i < std::min((int)lines.size(), cutoff); i++) {
|
||||
printf("%s\n", lines[i].c_str());
|
||||
JitBlockCacheDebugInterface *cache = MIPSComp::jit->GetBlockCacheDebugInterface();
|
||||
if (cache) {
|
||||
JitBlockDebugInfo block = cache->GetBlockDebugInfo(0); // Should only be one block.
|
||||
std::vector<std::string> &lines = block.targetDisasm;
|
||||
// Cut off at 25 due to the repetition above. Might need tweaking for large instructions.
|
||||
const int cutoff = 25;
|
||||
for (int i = 0; i < std::min((int)lines.size(), cutoff); i++) {
|
||||
printf("%s\n", lines[i].c_str());
|
||||
}
|
||||
if (lines.size() > cutoff)
|
||||
printf("...\n");
|
||||
}
|
||||
if (lines.size() > cutoff)
|
||||
printf("...\n");
|
||||
printf("Jit was %fx faster than interp.\n\n", jit_speed / interp_speed);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user