mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
loongarch: Add emitter, JIT and disassembler support for LoongArch64
This commit is contained in:
@@ -600,6 +600,8 @@ source_group(RISCV64 FILES ${CommonRISCV64})
|
||||
set(CommonLOONGARCH64
|
||||
${CommonJIT}
|
||||
Common/LoongArchCPUDetect.cpp
|
||||
Common/LoongArch64Emitter.cpp
|
||||
Common/LoongArch64Emitter.h
|
||||
Core/MIPS/fake/FakeJit.cpp
|
||||
Core/MIPS/fake/FakeJit.h
|
||||
)
|
||||
@@ -1825,6 +1827,20 @@ list(APPEND CoreExtra
|
||||
GPU/Common/VertexDecoderRiscV.cpp
|
||||
)
|
||||
|
||||
list(APPEND CoreExtra
|
||||
Core/MIPS/LoongArch64/LoongArch64Asm.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64CompALU.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64CompBranch.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64CompFPU.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64CompLoadStore.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64CompSystem.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64CompVec.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64Jit.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64Jit.h
|
||||
Core/MIPS/LoongArch64/LoongArch64RegCache.cpp
|
||||
Core/MIPS/LoongArch64/LoongArch64RegCache.h
|
||||
)
|
||||
|
||||
if(NOT MOBILE_DEVICE)
|
||||
set(CoreExtra ${CoreExtra}
|
||||
Core/AVIDump.cpp
|
||||
@@ -2436,6 +2452,8 @@ add_library(${CoreLibName} ${CoreLinkType}
|
||||
ext/disarm.h
|
||||
ext/riscv-disas.cpp
|
||||
ext/riscv-disas.h
|
||||
ext/loongarch-disasm.cpp
|
||||
ext/loongarch-disasm.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/git-version.cpp
|
||||
)
|
||||
|
||||
@@ -2810,6 +2828,7 @@ if(UNITTEST)
|
||||
unittest/TestVertexJit.cpp
|
||||
unittest/TestVFS.cpp
|
||||
unittest/TestRiscVEmitter.cpp
|
||||
unittest/TestLoongArch64Emitter.cpp
|
||||
unittest/TestSoftwareGPUJit.cpp
|
||||
unittest/TestThreadManager.cpp
|
||||
unittest/JitHarness.cpp
|
||||
|
||||
@@ -440,6 +440,7 @@
|
||||
<ClInclude Include="Input\GestureDetector.h" />
|
||||
<ClInclude Include="Input\InputState.h" />
|
||||
<ClInclude Include="Input\KeyCodes.h" />
|
||||
<ClInclude Include="LoongArch64Emitter.h" />
|
||||
<ClInclude Include="Math\CrossSIMD.h" />
|
||||
<ClInclude Include="Math\curves.h" />
|
||||
<ClInclude Include="Math\expression_parser.h" />
|
||||
@@ -897,6 +898,7 @@
|
||||
<ClCompile Include="Input\InputState.cpp" />
|
||||
<ClCompile Include="GhidraClient.cpp" />
|
||||
<ClCompile Include="Log.cpp" />
|
||||
<ClCompile Include="LoongArch64Emitter.cpp" />
|
||||
<ClCompile Include="Math\curves.cpp" />
|
||||
<ClCompile Include="Math\expression_parser.cpp" />
|
||||
<ClCompile Include="Math\fast\fast_matrix.c" />
|
||||
|
||||
@@ -688,6 +688,7 @@
|
||||
<ClInclude Include="Audio\AudioBackend.h">
|
||||
<Filter>Audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LoongArch64Emitter.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
@@ -1288,6 +1289,7 @@
|
||||
<ClCompile Include="..\ext\imgui\imgui_extras.cpp">
|
||||
<Filter>ext\imgui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LoongArch64Emitter.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Crypto">
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
#define Crash() {asm ("brk #0");}
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
#define Crash() {asm ("ebreak");}
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
#define Crash() {asm ("break 0");}
|
||||
#else
|
||||
#include <signal.h>
|
||||
#define Crash() {kill(getpid(), SIGINT);}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -172,7 +172,7 @@ std::string CreateRandMAC() {
|
||||
}
|
||||
|
||||
static int DefaultCpuCore() {
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64)
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64) || PPSSPP_ARCH(LOONGARCH64)
|
||||
if (System_GetPropertyBool(SYSPROP_CAN_JIT))
|
||||
return (int)CPUCore::JIT;
|
||||
return (int)CPUCore::IR_INTERPRETER;
|
||||
@@ -182,7 +182,7 @@ static int DefaultCpuCore() {
|
||||
}
|
||||
|
||||
static bool DefaultCodeGen() {
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64)
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64) || PPSSPP_ARCH(LOONGARCH64)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
|
||||
@@ -428,6 +428,7 @@
|
||||
<ClCompile Include="..\ext\libzip\zip_unchange_archive.c" />
|
||||
<ClCompile Include="..\ext\libzip\zip_unchange_data.c" />
|
||||
<ClCompile Include="..\ext\libzip\zip_utf-8.c" />
|
||||
<ClCompile Include="..\ext\loongarch-disasm.cpp" />
|
||||
<ClCompile Include="..\ext\riscv-disas.cpp" />
|
||||
<ClCompile Include="..\ext\sfmt19937\SFMT.c" />
|
||||
<ClCompile Include="..\ext\sha1\sha1.cpp" />
|
||||
@@ -518,6 +519,15 @@
|
||||
<ClCompile Include="MIPS\IR\IRNativeCommon.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRPassSimplify.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRRegCache.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64Asm.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompALU.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompBranch.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompFPU.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompLoadStore.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompSystem.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompVec.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64Jit.cpp" />
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64RegCache.cpp" />
|
||||
<ClCompile Include="MIPS\MIPSVFPUFallbacks.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVAsm.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompALU.cpp" />
|
||||
@@ -967,6 +977,7 @@
|
||||
<ClInclude Include="..\ext\libzip\zip_source_file.h" />
|
||||
<ClInclude Include="..\ext\libzip\zip_source_file_stdio.h" />
|
||||
<ClInclude Include="..\ext\libzip\zip_source_file_win32.h" />
|
||||
<ClInclude Include="..\ext\loongarch-disasm.h" />
|
||||
<ClInclude Include="..\ext\riscv-disas.h" />
|
||||
<ClInclude Include="..\ext\sfmt19937\SFMT-common.h" />
|
||||
<ClInclude Include="..\ext\sfmt19937\SFMT-params.h" />
|
||||
@@ -1053,6 +1064,8 @@
|
||||
<ClInclude Include="MIPS\IR\IRNativeCommon.h" />
|
||||
<ClInclude Include="MIPS\IR\IRPassSimplify.h" />
|
||||
<ClInclude Include="MIPS\IR\IRRegCache.h" />
|
||||
<ClInclude Include="MIPS\LoongArch64\LoongArch64Jit.h" />
|
||||
<ClInclude Include="MIPS\LoongArch64\LoongArch64RegCache.h" />
|
||||
<ClInclude Include="MIPS\MIPSVFPUFallbacks.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVJit.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCache.h" />
|
||||
|
||||
@@ -94,6 +94,9 @@
|
||||
<Filter Include="MIPS\RiscV">
|
||||
<UniqueIdentifier>{067e3128-3aaf-4ed1-b19e-bab11606abe7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="MIPS\LoongArch64">
|
||||
<UniqueIdentifier>{1ef9e44d-2e76-4984-a6e5-5f6f143acfc8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ELF\ElfReader.cpp">
|
||||
@@ -1354,6 +1357,36 @@
|
||||
<ClCompile Include="FileLoaders\ZipFileLoader.cpp">
|
||||
<Filter>FileLoaders</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64Asm.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompALU.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompBranch.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompFPU.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompLoadStore.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompSystem.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64CompVec.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64Jit.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\LoongArch64\LoongArch64RegCache.cpp">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ext\loongarch-disasm.cpp">
|
||||
<Filter>Ext</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ELF\ElfReader.h">
|
||||
@@ -2193,6 +2226,15 @@
|
||||
<ClInclude Include="HLE\AtracBase.h">
|
||||
<Filter>HLE\Libraries</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\LoongArch64\LoongArch64Jit.h">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\LoongArch64\LoongArch64RegCache.h">
|
||||
<Filter>MIPS\LoongArch64</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ext\loongarch-disasm.h">
|
||||
<Filter>Ext</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE.TXT" />
|
||||
|
||||
@@ -762,6 +762,8 @@ JitBlockDebugInfo IRNativeBlockCacheDebugInterface::GetBlockDebugInfo(int blockN
|
||||
debugInfo.targetDisasm = DisassembleX86(blockStart, codeSize);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
debugInfo.targetDisasm = DisassembleRV64(blockStart, codeSize);
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
debugInfo.targetDisasm = DisassembleLA64(blockStart, codeSize);
|
||||
#endif
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
@@ -662,6 +662,9 @@ int JitBlockCache::GetBlockExitSize() {
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
// Will depend on the sequence found to encode the destination address.
|
||||
return 0;
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
// Will depend on the sequence found to encode the destination address.
|
||||
return 0;
|
||||
#else
|
||||
#warning GetBlockExitSize unimplemented
|
||||
return 0;
|
||||
@@ -715,6 +718,8 @@ JitBlockDebugInfo JitBlockCache::GetBlockDebugInfo(int blockNum) const {
|
||||
debugInfo.targetDisasm = DisassembleX86(block->normalEntry, block->codeSize);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
debugInfo.targetDisasm = DisassembleRV64(block->normalEntry, block->codeSize);
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
debugInfo.targetDisasm = DisassembleLA64(block->normalEntry, block->codeSize);
|
||||
#endif
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ext/disarm.h"
|
||||
#include "ext/riscv-disas.h"
|
||||
#include "ext/udis86/udis86.h"
|
||||
#include "ext/loongarch-disasm.h"
|
||||
|
||||
#include "Common/LogReporting.h"
|
||||
#include "Common/StringUtils.h"
|
||||
@@ -49,6 +50,8 @@
|
||||
#include "../MIPS/MipsJit.h"
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
#include "../RiscV/RiscVJit.h"
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
#include "../LoongArch64/LoongArch64Jit.h"
|
||||
#else
|
||||
#include "../fake/FakeJit.h"
|
||||
#endif
|
||||
@@ -118,6 +121,8 @@ namespace MIPSComp {
|
||||
return new MIPSComp::MipsJit(mipsState);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
return new MIPSComp::RiscVJit(mipsState);
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
return new MIPSComp::LoongArch64Jit(mipsState);
|
||||
#else
|
||||
return new MIPSComp::FakeJit(mipsState);
|
||||
#endif
|
||||
@@ -358,3 +363,30 @@ std::vector<std::string> DisassembleRV64(const u8 *data, int size) {
|
||||
return lines;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PPSSPP_ARCH(LOONGARCH64) || defined(DISASM_ALL)
|
||||
std::vector<std::string> DisassembleLA64(const u8 *data, int size) {
|
||||
std::vector<std::string> lines;
|
||||
|
||||
int invalid_count = 0;
|
||||
auto invalid_flush = [&]() {
|
||||
if (invalid_count != 0) {
|
||||
lines.push_back(StringFromFormat("(%d invalid bytes)", invalid_count));
|
||||
invalid_count = 0;
|
||||
}
|
||||
};
|
||||
|
||||
char temp[512];
|
||||
Ins ins;
|
||||
for (int i = 0; i < size; i += 4) {
|
||||
const u32 *codePtr = (const u32 *)(data + i);
|
||||
invalid_flush();
|
||||
la_disasm(*codePtr, &ins);
|
||||
sprint_ins(&ins, temp);
|
||||
lines.push_back(ReplaceAll(temp, "\t", " "));
|
||||
}
|
||||
|
||||
invalid_flush();
|
||||
return lines;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,7 @@ std::vector<std::string> DisassembleArm2(const u8 *data, int size);
|
||||
std::vector<std::string> DisassembleArm64(const u8 *data, int size);
|
||||
std::vector<std::string> DisassembleX86(const u8 *data, int size);
|
||||
std::vector<std::string> DisassembleRV64(const u8 *data, int size);
|
||||
std::vector<std::string> DisassembleLA64(const u8 *data, int size);
|
||||
|
||||
struct JitBlock;
|
||||
class JitBlockCache;
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace MIPSComp {
|
||||
|
||||
useStaticAlloc = false;
|
||||
enablePointerify = false;
|
||||
#if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(RISCV64)
|
||||
#if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(RISCV64) || PPSSPP_ARCH(LOONGARCH64)
|
||||
useStaticAlloc = !Disabled(JitDisable::STATIC_ALLOC);
|
||||
// iOS/etc. may disable at runtime if Memory::base is not nicely aligned.
|
||||
enablePointerify = !Disabled(JitDisable::POINTERIFY);
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
#include "Core/Core.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
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(Log::JIT, "[%08x] ShowPC Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase);
|
||||
} else {
|
||||
ERROR_LOG(Log::JIT, "Universe corrupt?");
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::GenerateFixedCode(MIPSState *mipsState) {
|
||||
// This will be used as a writable scratch area, always 32-bit accessible.
|
||||
const u8 *start = AlignCodePage();
|
||||
if (DebugProfilerEnabled()) {
|
||||
ProtectMemoryPages(start, GetMemoryProtectPageSize(), MEM_PROT_READ | MEM_PROT_WRITE);
|
||||
hooks_.profilerPC = (uint32_t *)GetWritableCodePtr();
|
||||
*hooks_.profilerPC = 0;
|
||||
hooks_.profilerStatus = (IRProfilerStatus *)GetWritableCodePtr() + 1;
|
||||
*hooks_.profilerStatus = IRProfilerStatus::NOT_RUNNING;
|
||||
SetCodePointer(GetCodePtr() + sizeof(uint32_t) * 2, GetWritableCodePtr() + sizeof(uint32_t) * 2);
|
||||
}
|
||||
|
||||
const u8 *disasmStart = AlignCodePage();
|
||||
BeginWrite(GetMemoryProtectPageSize());
|
||||
if (jo.useStaticAlloc) {
|
||||
saveStaticRegisters_ = AlignCode16();
|
||||
ST_W(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
regs_.EmitSaveStaticRegisters();
|
||||
RET();
|
||||
|
||||
loadStaticRegisters_ = AlignCode16();
|
||||
regs_.EmitLoadStaticRegisters();
|
||||
LD_W(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
RET();
|
||||
} else {
|
||||
saveStaticRegisters_ = nullptr;
|
||||
loadStaticRegisters_ = nullptr;
|
||||
}
|
||||
|
||||
applyRoundingMode_ = AlignCode16();
|
||||
{
|
||||
// LoongArch64 does not have any flush to zero capability, so leaving it off
|
||||
LD_WU(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 = BEQZ(SCRATCH2);
|
||||
|
||||
// MIPS Rounding Mode: LoongArch64
|
||||
// 0: Round nearest 0 RNE
|
||||
// 1: Round to zero 1 RZ
|
||||
// 2: Round up (ceil) 2 RP
|
||||
// 3: Round down (floor) 3 RM
|
||||
MOVGR2FCSR(FCSR3, SCRATCH2);
|
||||
|
||||
SetJumpTarget(skip);
|
||||
RET();
|
||||
}
|
||||
|
||||
hooks_.enterDispatcher = (IRNativeFuncNoArg)AlignCode16();
|
||||
|
||||
// Start by saving some regs on the stack. There are 11 GPs and 8 FPs we want.
|
||||
// Note: we leave R_SP as, well, SP, so it doesn't need to be saved.
|
||||
static constexpr LoongArch64Reg regs_to_save[]{ R_RA, R22, R23, R24, R25, R26, R27, R28, R29, R30, R31 };
|
||||
// TODO: Maybe we shouldn't regalloc all of these? Is it worth it?
|
||||
static constexpr LoongArch64Reg regs_to_save_fp[]{ F24, F25, F26, F27, F28, F29, F30, F31 };
|
||||
int saveSize = (64 / 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_D(R_SP, R_SP, -saveSize);
|
||||
for (LoongArch64Reg r : regs_to_save) {
|
||||
ST_D(r, R_SP, saveOffset);
|
||||
saveOffset += 64 / 8;
|
||||
}
|
||||
for (LoongArch64Reg r : regs_to_save_fp) {
|
||||
FST_D(r, R_SP, saveOffset);
|
||||
saveOffset += 64 / 8;
|
||||
}
|
||||
_assert_(saveOffset <= saveSize);
|
||||
|
||||
// Fixed registers, these are always kept when in Jit context.
|
||||
LI(MEMBASEREG, Memory::base);
|
||||
LI(CTXREG, mipsState);
|
||||
LI(JITBASEREG, GetBasePtr() - MIPS_EMUHACK_OPCODE);
|
||||
|
||||
LoadStaticRegisters();
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
MovFromPC(SCRATCH1);
|
||||
WriteDebugPC(SCRATCH1);
|
||||
outerLoopPCInSCRATCH1_ = GetCodePtr();
|
||||
MovToPC(SCRATCH1);
|
||||
outerLoop_ = GetCodePtr();
|
||||
// Advance can change the downcount (or thread), so must save/restore around it.
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::TIMER_ADVANCE);
|
||||
QuickCallFunction(&CoreTiming::Advance, R20);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
ApplyRoundingMode(true);
|
||||
LoadStaticRegisters();
|
||||
|
||||
dispatcherCheckCoreState_ = GetCodePtr();
|
||||
LI(SCRATCH1, &coreState);
|
||||
LD_W(SCRATCH1, SCRATCH1, 0);
|
||||
FixupBranch badCoreState = BNEZ(SCRATCH1);
|
||||
|
||||
// We just checked coreState, so go to advance if downcount is negative.
|
||||
BLT(DOWNCOUNTREG, R_ZERO, outerLoop_);
|
||||
FixupBranch skipToRealDispatch = B();
|
||||
|
||||
dispatcherPCInSCRATCH1_ = GetCodePtr();
|
||||
MovToPC(SCRATCH1);
|
||||
|
||||
hooks_.dispatcher = GetCodePtr();
|
||||
FixupBranch bail = BLT(DOWNCOUNTREG, R_ZERO);
|
||||
SetJumpTarget(skipToRealDispatch);
|
||||
|
||||
dispatcherNoCheck_ = GetCodePtr();
|
||||
|
||||
// Debug
|
||||
if (enableDebug) {
|
||||
MOVE(R4, DOWNCOUNTREG);
|
||||
MOVE(R5, MEMBASEREG);
|
||||
MOVE(R6, JITBASEREG);
|
||||
QuickCallFunction(&ShowPC, R20);
|
||||
}
|
||||
|
||||
LD_WU(SCRATCH1, CTXREG, offsetof(MIPSState, pc));
|
||||
WriteDebugPC(SCRATCH1);
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
LI(SCRATCH2, 0x3FFFFFFF);
|
||||
AND(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
#endif
|
||||
ADD_D(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
hooks_.dispatchFetch = GetCodePtr();
|
||||
LD_WU(SCRATCH1, SCRATCH1, 0);
|
||||
SRLI_D(SCRATCH2, SCRATCH1, 24);
|
||||
// We're in other words comparing to the top 8 bits of MIPS_EMUHACK_OPCODE by subtracting.
|
||||
ADDI_D(SCRATCH2, SCRATCH2, -(MIPS_EMUHACK_OPCODE >> 24));
|
||||
FixupBranch needsCompile = BNEZ(SCRATCH2);
|
||||
// No need to mask, JITBASEREG has already accounted for the upper bits.
|
||||
ADD_D(SCRATCH1, JITBASEREG, SCRATCH1);
|
||||
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);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::COMPILING);
|
||||
QuickCallFunction(&MIPSComp::JitAt, R20);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
ApplyRoundingMode(true);
|
||||
|
||||
// Try again, the block index should be set now.
|
||||
B(dispatcherNoCheck_);
|
||||
|
||||
SetJumpTarget(bail);
|
||||
|
||||
LI(SCRATCH1, &coreState);
|
||||
LD_W(SCRATCH1, SCRATCH1, 0);
|
||||
BEQZ(SCRATCH1, outerLoop_);
|
||||
|
||||
const uint8_t *quitLoop = GetCodePtr();
|
||||
SetJumpTarget(badCoreState);
|
||||
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::NOT_RUNNING);
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
|
||||
saveOffset = 0;
|
||||
for (LoongArch64Reg r : regs_to_save) {
|
||||
LD_D(r, R_SP, saveOffset);
|
||||
saveOffset += 64 / 8;
|
||||
}
|
||||
for (LoongArch64Reg r : regs_to_save_fp) {
|
||||
FLD_D(r, R_SP, saveOffset);
|
||||
saveOffset += 64 / 8;
|
||||
}
|
||||
ADDI_D(R_SP, R_SP, saveSize);
|
||||
|
||||
RET();
|
||||
|
||||
hooks_.crashHandler = GetCodePtr();
|
||||
LI(SCRATCH1, &coreState);
|
||||
LI(SCRATCH2, CORE_RUNTIME_ERROR);
|
||||
ST_W(SCRATCH2, SCRATCH1, 0);
|
||||
B(quitLoop);
|
||||
|
||||
// Leave this at the end, add more stuff above.
|
||||
if (enableDisasm) {
|
||||
#if PPSSPP_ARCH(LOONGARCH64)
|
||||
std::vector<std::string> lines = DisassembleLA64(start, GetCodePtr() - start);
|
||||
for (auto s : lines) {
|
||||
INFO_LOG(Log::JIT, "%s", s.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,658 @@
|
||||
// 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/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.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 LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Arith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
bool allowPtrMath = true;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
// Since we modify it, we can't safely.
|
||||
allowPtrMath = false;
|
||||
#endif
|
||||
|
||||
// LoongArch64 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:
|
||||
regs_.Map(inst);
|
||||
ADD_W(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Sub:
|
||||
regs_.Map(inst);
|
||||
SUB_W(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::AddConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
// Typical of stack pointer updates.
|
||||
if (regs_.IsGPRMappedAsPointer(inst.dest) && inst.dest == inst.src1 && allowPtrMath) {
|
||||
regs_.MarkGPRAsPointerDirty(inst.dest);
|
||||
ADDI_D(regs_.RPtr(inst.dest), regs_.RPtr(inst.dest), inst.constant);
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
ADDI_W(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
ADD_W(regs_.R(inst.dest), regs_.R(inst.src1), SCRATCH1);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SubConst:
|
||||
regs_.Map(inst);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
SUB_W(regs_.R(inst.dest), regs_.R(inst.src1), SCRATCH1);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Neg:
|
||||
regs_.Map(inst);
|
||||
SUB_W(regs_.R(inst.dest), R_ZERO, regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Logic(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
bool resultNormalized = false;
|
||||
switch (inst.op) {
|
||||
case IROp::And:
|
||||
if (inst.src1 != inst.src2) {
|
||||
regs_.Map(inst);
|
||||
AND(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Or:
|
||||
if (inst.src1 != inst.src2) {
|
||||
// If both were normalized before, the result is normalized.
|
||||
resultNormalized = regs_.IsNormalized32(inst.src1) && regs_.IsNormalized32(inst.src2);
|
||||
regs_.Map(inst);
|
||||
OR(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, resultNormalized);
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Xor:
|
||||
if (inst.src1 == inst.src2) {
|
||||
regs_.SetGPRImm(inst.dest, 0);
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
XOR(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::AndConst:
|
||||
resultNormalized = regs_.IsNormalized32(inst.src1);
|
||||
regs_.Map(inst);
|
||||
// LoongArch64's ANDI use unsigned 12-bit immediate
|
||||
if ((int32_t)inst.constant >= 0 && (int32_t)inst.constant < 4096) {
|
||||
ANDI(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
AND(regs_.R(inst.dest), regs_.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
// If the sign bits aren't cleared, and it was normalized before - it still is.
|
||||
if ((inst.constant & 0x80000000) != 0 && resultNormalized)
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
// Otherwise, if we cleared the sign bits, it's naturally normalized.
|
||||
else if ((inst.constant & 0x80000000) == 0)
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::OrConst:
|
||||
resultNormalized = regs_.IsNormalized32(inst.src1);
|
||||
regs_.Map(inst);
|
||||
if ((int32_t)inst.constant >= 0 && (int32_t)inst.constant < 4096) {
|
||||
ORI(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
OR(regs_.R(inst.dest), regs_.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
// Since our constant is normalized, oring its bits in won't hurt normalization.
|
||||
regs_.MarkGPRDirty(inst.dest, resultNormalized);
|
||||
break;
|
||||
|
||||
case IROp::XorConst:
|
||||
regs_.Map(inst);
|
||||
if ((int32_t)inst.constant >= 0 && (int32_t)inst.constant < 4096) {
|
||||
XORI(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
XOR(regs_.R(inst.dest), regs_.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Not:
|
||||
regs_.Map(inst);
|
||||
ORN(regs_.R(inst.dest), R_ZERO, regs_.R(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Assign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Mov:
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Ext8to32:
|
||||
regs_.Map(inst);
|
||||
EXT_W_B(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Ext16to32:
|
||||
regs_.Map(inst);
|
||||
EXT_W_H(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Bits(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ReverseBits:
|
||||
regs_.Map(inst);
|
||||
BITREV_W(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::BSwap16:
|
||||
regs_.Map(inst);
|
||||
REVB_2H(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::BSwap32:
|
||||
regs_.Map(inst);
|
||||
REVB_2W(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Clz:
|
||||
regs_.Map(inst);
|
||||
CLZ_W(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Shift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Shl:
|
||||
regs_.Map(inst);
|
||||
SLL_W(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Shr:
|
||||
regs_.Map(inst);
|
||||
SRL_W(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Sar:
|
||||
regs_.Map(inst);
|
||||
SRA_W(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::Ror:
|
||||
regs_.Map(inst);
|
||||
ROTR_W(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::ShlImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
regs_.SetGPRImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
SLLI_W(regs_.R(inst.dest), regs_.R(inst.src1), inst.src2);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::ShrImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
regs_.SetGPRImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
SRLI_W(regs_.R(inst.dest), regs_.R(inst.src1), inst.src2);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SarImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
regs_.Map(inst);
|
||||
SRAI_W(regs_.R(inst.dest), regs_.R(inst.src1), 31);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
SRAI_W(regs_.R(inst.dest), regs_.R(inst.src1), inst.src2);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::RorImm:
|
||||
if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
ROTRI_W(regs_.R(inst.dest), regs_.R(inst.src1), inst.src2 & 31);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Compare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg lhs = INVALID_REG;
|
||||
LoongArch64Reg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Slt:
|
||||
regs_.Map(inst);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
|
||||
SLT(regs_.R(inst.dest), lhs, rhs);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::SltConst:
|
||||
if (inst.constant == 0) {
|
||||
// Basically, getting the sign bit. Let's shift instead.
|
||||
regs_.Map(inst);
|
||||
SRLI_W(regs_.R(inst.dest), regs_.R(inst.src1), 31);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
SLTI(regs_.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLT(regs_.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SltU:
|
||||
regs_.Map(inst);
|
||||
// It's still fine to sign extend, the biggest just get even bigger.
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
|
||||
SLTU(regs_.R(inst.dest), lhs, rhs);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::SltUConst:
|
||||
if (inst.constant == 0) {
|
||||
regs_.SetGPRImm(inst.dest, 0);
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
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) {
|
||||
SLTUI(regs_.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLTU(regs_.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_CondAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg lhs = INVALID_REG;
|
||||
LoongArch64Reg 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" with wrong upper due to XOR, so we have to normalize.
|
||||
regs_.Map(inst);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, true);
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::MovZ:
|
||||
fixup = BNEZ(lhs);
|
||||
break;
|
||||
case IROp::MovNZ:
|
||||
fixup = BEQZ(lhs);
|
||||
break;
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src2));
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::Max:
|
||||
if (inst.src1 != inst.src2) {
|
||||
CompIR_Generic(inst);
|
||||
} else if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Min:
|
||||
if (inst.src1 != inst.src2) {
|
||||
CompIR_Generic(inst);
|
||||
} else if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_HiLo(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::MtLo:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
|
||||
// 32-63 bits of IRREG_LO + 0-31 bits of inst.src1
|
||||
BSTRINS_D(regs_.R(IRREG_LO), regs_.R(inst.src1), 31, 0);
|
||||
break;
|
||||
|
||||
case IROp::MtHi:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
|
||||
BSTRINS_D(regs_.R(IRREG_LO), regs_.R(inst.src1), 63, 32);
|
||||
break;
|
||||
|
||||
case IROp::MfLo:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::INIT } });
|
||||
// It won't be normalized, but that's fine...
|
||||
MOVE(regs_.R(inst.dest), regs_.R(IRREG_LO));
|
||||
break;
|
||||
|
||||
case IROp::MfHi:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::INIT } });
|
||||
SRAI_D(regs_.R(inst.dest), regs_.R(IRREG_LO), 32);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Mult(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
auto putArgsIntoScratches = [&](LoongArch64Reg *lhs, LoongArch64Reg *rhs) {
|
||||
MOVE(SCRATCH1, regs_.R(inst.src1));
|
||||
MOVE(SCRATCH2, regs_.R(inst.src2));
|
||||
*lhs = SCRATCH1;
|
||||
*rhs = SCRATCH2;
|
||||
};
|
||||
|
||||
LoongArch64Reg lhs = INVALID_REG;
|
||||
LoongArch64Reg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Mult:
|
||||
// TODO: Maybe IR could simplify when HI is not needed or clobbered?
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL_D(regs_.R(IRREG_LO), lhs, rhs);
|
||||
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.
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
|
||||
putArgsIntoScratches(&lhs, &rhs);
|
||||
MULW_D_WU(regs_.R(IRREG_LO), lhs, rhs);
|
||||
break;
|
||||
|
||||
case IROp::Madd:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL_D(SCRATCH1, lhs, rhs);
|
||||
ADD_D(regs_.R(IRREG_LO), regs_.R(IRREG_LO), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::MaddU:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
|
||||
putArgsIntoScratches(&lhs, &rhs);
|
||||
MULW_D_WU(SCRATCH1, lhs, rhs);
|
||||
ADD_D(regs_.R(IRREG_LO), regs_.R(IRREG_LO), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::Msub:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL_D(SCRATCH1, lhs, rhs);
|
||||
SUB_D(regs_.R(IRREG_LO), regs_.R(IRREG_LO), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::MsubU:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::DIRTY } });
|
||||
putArgsIntoScratches(&lhs, &rhs);
|
||||
MULW_D_WU(SCRATCH1, lhs, rhs);
|
||||
SUB_D(regs_.R(IRREG_LO), regs_.R(IRREG_LO), SCRATCH1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Div(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg numReg, denomReg;
|
||||
switch (inst.op) {
|
||||
case IROp::Div:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
|
||||
// We have to do this because of the divide by zero and overflow checks below.
|
||||
NormalizeSrc12(inst, &numReg, &denomReg, SCRATCH1, SCRATCH2, true);
|
||||
DIV_W(regs_.R(IRREG_LO), numReg, denomReg);
|
||||
MOD_W(R_RA, numReg, denomReg);
|
||||
// Now to combine them. We'll do more with them below...
|
||||
BSTRINS_D(regs_.R(IRREG_LO), R_RA, 63, 32);
|
||||
|
||||
// Now some tweaks for divide by zero and overflow.
|
||||
{
|
||||
// Start with divide by zero, the quotient and remainder are arbitrary numbers.
|
||||
FixupBranch skipNonZero = BNEZ(denomReg);
|
||||
// Clear the arbitrary number
|
||||
XOR(regs_.R(IRREG_LO), regs_.R(IRREG_LO), regs_.R(IRREG_LO));
|
||||
// Replace remainder to numReg
|
||||
BSTRINS_D(regs_.R(IRREG_LO), numReg, 63, 32);
|
||||
FixupBranch keepNegOne = BGE(numReg, R_ZERO);
|
||||
// Replace quotient with 1.
|
||||
ADDI_D(regs_.R(IRREG_LO), regs_.R(IRREG_LO), 1);
|
||||
SetJumpTarget(keepNegOne);
|
||||
// Replace quotient with -1.
|
||||
ADDI_D(regs_.R(IRREG_LO), regs_.R(IRREG_LO), -1);
|
||||
SetJumpTarget(skipNonZero);
|
||||
|
||||
// For overflow, LoongArch sets LO right, but remainder to zero.
|
||||
// Cheating a bit by using R_RA as a temp...
|
||||
LI(R_RA, (int32_t)0x80000000);
|
||||
FixupBranch notMostNegative = BNE(numReg, R_RA);
|
||||
LI(R_RA, -1);
|
||||
FixupBranch notNegativeOne = BNE(denomReg, R_RA);
|
||||
// Take our R_RA and put it in the high bits.
|
||||
SLLI_D(R_RA, R_RA, 32);
|
||||
OR(regs_.R(IRREG_LO), regs_.R(IRREG_LO), R_RA);
|
||||
SetJumpTarget(notNegativeOne);
|
||||
SetJumpTarget(notMostNegative);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::DivU:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 2, MIPSMap::NOINIT } });
|
||||
// We have to do this because of the divide by zero check below.
|
||||
NormalizeSrc12(inst, &numReg, &denomReg, SCRATCH1, SCRATCH2, true);
|
||||
DIV_WU(regs_.R(IRREG_LO), numReg, denomReg);
|
||||
MOD_WU(R_RA, numReg, denomReg);
|
||||
|
||||
// On divide by zero, special dealing with the 0xFFFF case.
|
||||
{
|
||||
FixupBranch skipNonZero = BNEZ(denomReg);
|
||||
// Move -1 to quotient.
|
||||
ADDI_D(regs_.R(IRREG_LO), R_ZERO, -1);
|
||||
// Move numReg to remainder (stores in RA currently).
|
||||
MOVE(R_RA, numReg);
|
||||
// Luckily, we don't need SCRATCH2/denomReg anymore.
|
||||
LI(SCRATCH2, 0xFFFF);
|
||||
FixupBranch keepNegOne = BLTU(SCRATCH2, numReg);
|
||||
MOVE(regs_.R(IRREG_LO), SCRATCH2);
|
||||
SetJumpTarget(keepNegOne);
|
||||
SetJumpTarget(skipNonZero);
|
||||
}
|
||||
|
||||
// Now combine the remainder in.
|
||||
BSTRINS_D(regs_.R(IRREG_LO), R_RA, 63, 32);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,143 @@
|
||||
// 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/MIPS/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.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 LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Exit(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg exitReg = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConst:
|
||||
FlushAll();
|
||||
WriteConstExit(inst.constant);
|
||||
break;
|
||||
|
||||
case IROp::ExitToReg:
|
||||
exitReg = regs_.MapGPR(inst.src1);
|
||||
FlushAll();
|
||||
// TODO: If ever we don't read this back in dispatcherPCInSCRATCH1_, we should zero upper.
|
||||
MOVE(SCRATCH1, exitReg);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
break;
|
||||
|
||||
case IROp::ExitToPC:
|
||||
FlushAll();
|
||||
QuickJ(R_RA, dispatcherCheckCoreState_);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_ExitIf(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg lhs = INVALID_REG;
|
||||
LoongArch64Reg rhs = INVALID_REG;
|
||||
FixupBranch fixup;
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfEq:
|
||||
case IROp::ExitToConstIfNeq:
|
||||
regs_.Map(inst);
|
||||
// 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;
|
||||
}
|
||||
|
||||
WriteConstExit(inst.constant);
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
case IROp::ExitToConstIfGeZ:
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
regs_.Map(inst);
|
||||
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;
|
||||
}
|
||||
|
||||
WriteConstExit(inst.constant);
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfFpTrue:
|
||||
case IROp::ExitToConstIfFpFalse:
|
||||
// Note: not used.
|
||||
DISABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,607 @@
|
||||
// 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/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.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 LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FArith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FAdd:
|
||||
regs_.Map(inst);
|
||||
FADD_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FSub:
|
||||
regs_.Map(inst);
|
||||
FSUB_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FMul:
|
||||
regs_.Map(inst);
|
||||
// We'll assume everyone will make it such that 0 * infinity = NAN properly.
|
||||
// See blame on this comment if that proves untrue.
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FDiv:
|
||||
regs_.Map(inst);
|
||||
FDIV_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FSqrt:
|
||||
regs_.Map(inst);
|
||||
FSQRT_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FNeg:
|
||||
regs_.Map(inst);
|
||||
FNEG_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FCondAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CUN);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
FixupBranch unordered = BNEZ(SCRATCH1);
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FMin:
|
||||
FMIN_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FMax:
|
||||
FMAX_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
FixupBranch ordererDone = B();
|
||||
SetJumpTarget(unordered);
|
||||
|
||||
MOVFR2GR_S(SCRATCH1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH2, regs_.F(inst.src2));
|
||||
|
||||
// If both are negative, we flip the comparison (not two's compliment.)
|
||||
// We cheat and use RA...
|
||||
AND(R_RA, SCRATCH1, SCRATCH2);
|
||||
SRLI_W(R_RA, R_RA, 31);
|
||||
|
||||
LoongArch64Reg isSrc1LowerReg = regs_.GetAndLockTempGPR();
|
||||
SLT(isSrc1LowerReg, SCRATCH1, SCRATCH2);
|
||||
// Flip the flag (to reverse the min/max) based on if both were negative.
|
||||
XOR(isSrc1LowerReg, isSrc1LowerReg, R_RA);
|
||||
FixupBranch useSrc1;
|
||||
switch (inst.op) {
|
||||
case IROp::FMin:
|
||||
useSrc1 = BNEZ(isSrc1LowerReg);
|
||||
break;
|
||||
|
||||
case IROp::FMax:
|
||||
useSrc1 = BEQZ(isSrc1LowerReg);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
MOVE(SCRATCH1, SCRATCH2);
|
||||
SetJumpTarget(useSrc1);
|
||||
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH1);
|
||||
|
||||
SetJumpTarget(ordererDone);
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FMov:
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
FMOV_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FAbs:
|
||||
regs_.Map(inst);
|
||||
FABS_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FSign:
|
||||
{
|
||||
regs_.Map(inst);
|
||||
// Check if it's negative zero, either 0x20/0x200 is zero.
|
||||
FCLASS_S(SCRATCHF1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH1, SCRATCHF1);
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x220);
|
||||
SLTUI(SCRATCH1, SCRATCH1, 1);
|
||||
// Okay, it's zero if zero, 1 otherwise. Convert 1 to a constant 1.0.
|
||||
// Probably non-zero is the common case, so we make that the straight line.
|
||||
FixupBranch skipOne = BEQZ(SCRATCH1);
|
||||
LI(SCRATCH1, 1.0f);
|
||||
|
||||
// Now we just need the sign from it.
|
||||
MOVFR2GR_S(SCRATCH2, regs_.F(inst.src1));
|
||||
// Use a wall to isolate the sign, and combine.
|
||||
SRAI_W(SCRATCH2, SCRATCH2, 31);
|
||||
SLLI_W(SCRATCH2, SCRATCH2, 31);
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
|
||||
SetJumpTarget(skipOne);
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FRound(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
regs_.Map(inst);
|
||||
// FTINT* instruction will convert NAN to zero, tested on 3A6000.
|
||||
QuickFLI(32, SCRATCHF1, (uint32_t)0x7fffffffl, SCRATCH1);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src1), LoongArch64Fcond::CUN);
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FRound:
|
||||
FTINTRNE_W_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FTrunc:
|
||||
FTINTRZ_W_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FCeil:
|
||||
FTINTRP_W_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FFloor:
|
||||
FTINTRM_W_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
// Switch to INT_MAX if it was NAN.
|
||||
FSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF1, FCC0);
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FCvt(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FCvtWS:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::FCvtSW:
|
||||
regs_.Map(inst);
|
||||
FFINT_S_W(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FCvtScaledWS:
|
||||
regs_.Map(inst);
|
||||
// Prepare for the NAN result
|
||||
QuickFLI(32, SCRATCHF1, (uint32_t)(0x7FFFFFFF), SCRATCH1);
|
||||
// Prepare the multiplier.
|
||||
QuickFLI(32, SCRATCHF1, (float)(1UL << (inst.src2 & 0x1F)), SCRATCH1);
|
||||
|
||||
switch (inst.src2 >> 6) {
|
||||
case 0: // RNE
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src1), LoongArch64Fcond::CUN);
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1);
|
||||
FTINTRNE_W_S(regs_.F(inst.dest), regs_.F(inst.dest));
|
||||
FSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2, FCC0);
|
||||
break;
|
||||
case 1: // RZ
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src1), LoongArch64Fcond::CUN);
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1);
|
||||
FTINTRZ_W_S(regs_.F(inst.dest), regs_.F(inst.dest));
|
||||
FSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2, FCC0);
|
||||
break;
|
||||
case 2: // RP
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src1), LoongArch64Fcond::CUN);
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1);
|
||||
FTINTRP_W_S(regs_.F(inst.dest), regs_.F(inst.dest));
|
||||
FSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2, FCC0);
|
||||
break;
|
||||
case 3: // RM
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src1), LoongArch64Fcond::CUN);
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1);
|
||||
FTINTRM_W_S(regs_.F(inst.dest), regs_.F(inst.dest));
|
||||
FSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2, FCC0);
|
||||
break;
|
||||
default:
|
||||
_assert_msg_(false, "Invalid rounding mode for FCvtScaledWS");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case IROp::FCvtScaledSW:
|
||||
regs_.Map(inst);
|
||||
FFINT_S_W(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
|
||||
// Pre-divide so we can avoid any actual divide.
|
||||
QuickFLI(32, SCRATCHF1, 1.0f / (1UL << (inst.src2 & 0x1F)), SCRATCH1);
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FSat(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FSat0_1:
|
||||
regs_.Map(inst);
|
||||
QuickFLI(32, SCRATCHF1, (float)1.0f, SCRATCH1);
|
||||
// Check whether FMAX takes the larger of the two zeros, which is what we want.
|
||||
QuickFLI(32, SCRATCHF2, (float)0.0f, SCRATCH1);
|
||||
|
||||
FMIN_S(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1);
|
||||
FMAX_S(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2);
|
||||
break;
|
||||
|
||||
case IROp::FSatMinus1_1:
|
||||
regs_.Map(inst);
|
||||
QuickFLI(32, SCRATCHF1, (float)1.0f, SCRATCH1);
|
||||
FNEG_S(SCRATCHF2, SCRATCHF1);
|
||||
|
||||
FMIN_S(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1);
|
||||
FMAX_S(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FCompare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
constexpr IRReg IRREG_VFPU_CC = IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FCmp:
|
||||
switch (inst.dest) {
|
||||
case IRFpCompareMode::False:
|
||||
regs_.SetGPRImm(IRREG_FPCOND, 0);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EitherUnordered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CUN);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EqualOrdered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CEQ);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EqualUnordered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CUEQ);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessEqualOrdered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CLE);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessEqualUnordered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CULE);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessOrdered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CLT);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessUnordered:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CULT);
|
||||
MOVCF2GR(regs_.R(IRREG_FPCOND), FCC0);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
_assert_msg_(false, "Unexpected IRFpCompareMode %d", inst.dest);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FCmovVfpuCC:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_VFPU_CC, 1, MIPSMap::INIT } });
|
||||
if ((inst.src2 & 0xF) == 0) {
|
||||
ANDI(SCRATCH1, regs_.R(IRREG_VFPU_CC), 1);
|
||||
} else {
|
||||
BSTRPICK_D(SCRATCH1, regs_.R(IRREG_VFPU_CC), inst.src2 & 0xF, inst.src2 & 0xF);
|
||||
}
|
||||
if ((inst.src2 >> 7) & 1) {
|
||||
FixupBranch skip = BEQZ(SCRATCH1);
|
||||
FMOV_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
SetJumpTarget(skip);
|
||||
} else {
|
||||
FixupBranch skip = BNEZ(SCRATCH1);
|
||||
FMOV_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
SetJumpTarget(skip);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FCmpVfpuBit:
|
||||
regs_.MapGPR(IRREG_VFPU_CC, MIPSMap::DIRTY);
|
||||
|
||||
switch (VCondition(inst.dest & 0xF)) {
|
||||
case VC_EQ:
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CEQ);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
break;
|
||||
case VC_NE:
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CNE);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
break;
|
||||
case VC_LT:
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CLT);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
break;
|
||||
case VC_LE:
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src1), regs_.F(inst.src2), LoongArch64Fcond::CLE);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
break;
|
||||
case VC_GT:
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src2), regs_.F(inst.src1), LoongArch64Fcond::CLT);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
break;
|
||||
case VC_GE:
|
||||
regs_.Map(inst);
|
||||
FCMP_COND_S(FCC0, regs_.F(inst.src2), regs_.F(inst.src1), LoongArch64Fcond::CLE);
|
||||
MOVCF2GR(SCRATCH1, FCC0);
|
||||
break;
|
||||
case VC_EZ:
|
||||
case VC_NZ:
|
||||
regs_.MapFPR(inst.src1);
|
||||
// Zero is either 0x20 or 0x200.
|
||||
FCLASS_S(SCRATCHF1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH1, SCRATCHF1);
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x220);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SLTU(SCRATCH1, R_ZERO, SCRATCH1);
|
||||
else
|
||||
SLTUI(SCRATCH1, SCRATCH1, 1);
|
||||
break;
|
||||
case VC_EN:
|
||||
case VC_NN:
|
||||
regs_.MapFPR(inst.src1);
|
||||
// NAN is either 0x1 or 0x2.
|
||||
FCLASS_S(SCRATCHF1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH1, SCRATCHF1);
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x3);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SLTU(SCRATCH1, R_ZERO, SCRATCH1);
|
||||
else
|
||||
SLTUI(SCRATCH1, SCRATCH1, 1);
|
||||
break;
|
||||
case VC_EI:
|
||||
case VC_NI:
|
||||
regs_.MapFPR(inst.src1);
|
||||
// Infinity is either 0x40 or 0x04.
|
||||
FCLASS_S(SCRATCHF1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH1, SCRATCHF1);
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x44);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SLTU(SCRATCH1, R_ZERO, SCRATCH1);
|
||||
else
|
||||
SLTUI(SCRATCH1, SCRATCH1, 1);
|
||||
break;
|
||||
case VC_ES:
|
||||
case VC_NS:
|
||||
regs_.MapFPR(inst.src1);
|
||||
// Infinity is either 0x40 or 0x04, NAN is either 0x1 or 0x2.
|
||||
FCLASS_S(SCRATCHF1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH1, SCRATCHF1);
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x47);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SLTU(SCRATCH1, R_ZERO, SCRATCH1);
|
||||
else
|
||||
SLTUI(SCRATCH1, SCRATCH1, 1);
|
||||
break;
|
||||
case VC_TR:
|
||||
LI(SCRATCH1, 1);
|
||||
break;
|
||||
case VC_FL:
|
||||
LI(SCRATCH1, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
ANDI(regs_.R(IRREG_VFPU_CC), regs_.R(IRREG_VFPU_CC), ~(1 << (inst.dest >> 4)));
|
||||
if ((inst.dest >> 4) != 0)
|
||||
SLLI_D(SCRATCH1, SCRATCH1, inst.dest >> 4);
|
||||
OR(regs_.R(IRREG_VFPU_CC), regs_.R(IRREG_VFPU_CC), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::FCmpVfpuAggregate:
|
||||
regs_.MapGPR(IRREG_VFPU_CC, MIPSMap::DIRTY);
|
||||
if (inst.dest == 1) {
|
||||
ANDI(SCRATCH1, regs_.R(IRREG_VFPU_CC), inst.dest);
|
||||
// Negate so 1 becomes all bits set and zero stays zero, then mask to 0x30.
|
||||
SUB_D(SCRATCH1, R_ZERO, SCRATCH1);
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x30);
|
||||
|
||||
// Reject the old any/all bits and replace them with our own.
|
||||
ANDI(regs_.R(IRREG_VFPU_CC), regs_.R(IRREG_VFPU_CC), ~0x30);
|
||||
OR(regs_.R(IRREG_VFPU_CC), regs_.R(IRREG_VFPU_CC), SCRATCH1);
|
||||
} else {
|
||||
ANDI(SCRATCH1, regs_.R(IRREG_VFPU_CC), inst.dest);
|
||||
FixupBranch skipZero = BEQZ(SCRATCH1);
|
||||
|
||||
// To compare to inst.dest for "all", let's simply subtract it and compare to zero.
|
||||
ADDI_D(SCRATCH1, SCRATCH1, -inst.dest);
|
||||
SLTUI(SCRATCH1, SCRATCH1, 1);
|
||||
// Now we combine with the "any" bit.
|
||||
SLLI_D(SCRATCH1, SCRATCH1, 5);
|
||||
ORI(SCRATCH1, SCRATCH1, 0x10);
|
||||
|
||||
SetJumpTarget(skipZero);
|
||||
|
||||
// Reject the old any/all bits and replace them with our own.
|
||||
ANDI(regs_.R(IRREG_VFPU_CC), regs_.R(IRREG_VFPU_CC), ~0x30);
|
||||
OR(regs_.R(IRREG_VFPU_CC), regs_.R(IRREG_VFPU_CC), SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_RoundingMode(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::RestoreRoundingMode:
|
||||
RestoreRoundingMode();
|
||||
break;
|
||||
|
||||
case IROp::ApplyRoundingMode:
|
||||
ApplyRoundingMode();
|
||||
break;
|
||||
|
||||
case IROp::UpdateRoundingMode:
|
||||
// Do nothing, we don't use any instructions that need updating the rounding mode.
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FSpecial(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
auto callFuncF_F = [&](float (*func)(float)) {
|
||||
regs_.FlushBeforeCall();
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::MATH_HELPER);
|
||||
|
||||
// It might be in a non-volatile register.
|
||||
// TODO: May have to handle a transfer if SIMD here.
|
||||
if (regs_.IsFPRMapped(inst.src1)) {
|
||||
FMOV_S(F0, regs_.F(inst.src1));
|
||||
} else {
|
||||
int offset = offsetof(MIPSState, f) + inst.src1 * 4;
|
||||
FLD_S(F0, CTXREG, offset);
|
||||
}
|
||||
QuickCallFunction(func, SCRATCH1);
|
||||
|
||||
regs_.MapFPR(inst.dest, MIPSMap::NOINIT);
|
||||
// If it's already F0, we're done - MapReg doesn't actually overwrite the reg in that case.
|
||||
if (regs_.F(inst.dest) != F0) {
|
||||
FMOV_S(regs_.F(inst.dest), F0);
|
||||
}
|
||||
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
};
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FSin:
|
||||
callFuncF_F(&vfpu_sin);
|
||||
break;
|
||||
|
||||
case IROp::FCos:
|
||||
callFuncF_F(&vfpu_cos);
|
||||
break;
|
||||
|
||||
case IROp::FRSqrt:
|
||||
regs_.Map(inst);
|
||||
FRSQRT_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FRecip:
|
||||
regs_.Map(inst);
|
||||
FRECIP_S(regs_.F(inst.dest), regs_.F(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FAsin:
|
||||
callFuncF_F(&vfpu_asin);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,396 @@
|
||||
// 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/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.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 LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
void LoongArch64JitBackend::SetScratch1ToSrc1Address(IRReg src1) {
|
||||
regs_.MapGPR(src1);
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
SLLI_W(SCRATCH1, regs_.R(src1), 2);
|
||||
SRLI_W(SCRATCH1, SCRATCH1, 2);
|
||||
ADD_D(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
#else
|
||||
// Clear the top bits to be safe.
|
||||
SLLI_D(SCRATCH1, regs_.R(src1), 32);
|
||||
SRLI_D(SCRATCH1, SCRATCH1, 32);
|
||||
ADD_D(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t LoongArch64JitBackend::AdjustForAddressOffset(LoongArch64Gen::LoongArch64Reg *reg, int32_t constant, int32_t range) {
|
||||
if (constant < -2048 || constant + range > 2047) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
if (constant > 0)
|
||||
constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
// It can't be this negative, must be a constant with top bit set.
|
||||
if ((constant & 0xC0000000) == 0x80000000) {
|
||||
LI(SCRATCH2, (uint32_t)constant);
|
||||
ADD_D(SCRATCH1, *reg, SCRATCH2);
|
||||
} else {
|
||||
LI(SCRATCH2, constant);
|
||||
ADD_D(SCRATCH1, *reg, SCRATCH2);
|
||||
}
|
||||
*reg = SCRATCH1;
|
||||
return 0;
|
||||
}
|
||||
return constant;
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Load(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
regs_.SpillLockGPR(inst.dest, inst.src1);
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
// With NOINIT, MapReg won't subtract MEMBASEREG even if dest == src1.
|
||||
regs_.MapGPR(inst.dest, MIPSMap::NOINIT);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Load8:
|
||||
LD_BU(regs_.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load8Ext:
|
||||
LD_B(regs_.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load16:
|
||||
LD_HU(regs_.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load16Ext:
|
||||
LD_H(regs_.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load32:
|
||||
LD_W(regs_.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load32Linked:
|
||||
if (inst.dest != MIPS_REG_ZERO)
|
||||
LD_W(regs_.R(inst.dest), addrReg, imm);
|
||||
regs_.SetGPRImm(IRREG_LLBIT, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_LoadShift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Load32Left:
|
||||
case IROp::Load32Right:
|
||||
// Should not happen if the pass to split is active.
|
||||
DISABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FLoad(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::LoadFloat:
|
||||
regs_.MapFPR(inst.dest, MIPSMap::NOINIT);
|
||||
FLD_S(regs_.F(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecLoad(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
// We need to be able to address the whole 16 bytes, so offset of 12.
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant, 12);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::LoadVec4:
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
regs_.MapVec4(inst.dest, MIPSMap::NOINIT);
|
||||
VLD(regs_.V(inst.dest), addrReg, imm);
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// Spilling is okay.
|
||||
regs_.MapFPR(inst.dest + i, MIPSMap::NOINIT);
|
||||
FLD_S(regs_.F(inst.dest + i), addrReg, imm + 4 * i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Store(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
regs_.SpillLockGPR(inst.src3, inst.src1);
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
LoongArch64Reg valueReg = regs_.TryMapTempImm(inst.src3);
|
||||
if (valueReg == INVALID_REG)
|
||||
valueReg = regs_.MapGPR(inst.src3);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Store8:
|
||||
ST_B(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Store16:
|
||||
ST_H(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Store32:
|
||||
ST_W(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_CondStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
if (inst.op != IROp::Store32Conditional)
|
||||
INVALIDOP;
|
||||
|
||||
regs_.SpillLockGPR(IRREG_LLBIT, inst.src3, inst.src1);
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
regs_.MapGPR(inst.src3, inst.dest == MIPS_REG_ZERO ? MIPSMap::INIT : MIPSMap::DIRTY);
|
||||
regs_.MapGPR(IRREG_LLBIT);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
FixupBranch condFailed = BEQZ(regs_.R(IRREG_LLBIT));
|
||||
ST_W(regs_.R(inst.src3), addrReg, imm);
|
||||
|
||||
if (inst.dest != MIPS_REG_ZERO) {
|
||||
LI(regs_.R(inst.dest), 1);
|
||||
FixupBranch finish = B();
|
||||
|
||||
SetJumpTarget(condFailed);
|
||||
LI(regs_.R(inst.dest), 0);
|
||||
SetJumpTarget(finish);
|
||||
} else {
|
||||
SetJumpTarget(condFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_StoreShift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Store32Left:
|
||||
case IROp::Store32Right:
|
||||
// Should not happen if the pass to split is active.
|
||||
DISABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_FStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::StoreFloat:
|
||||
regs_.MapFPR(inst.src3);
|
||||
FST_S(regs_.F(inst.src3), addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
LoongArch64Reg 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 || regs_.IsGPRMappedAsPointer(inst.src1)) {
|
||||
addrReg = regs_.MapGPRAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
// We need to be able to address the whole 16 bytes, so offset of 12.
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant, 12);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::StoreVec4:
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
regs_.MapVec4(inst.src3);
|
||||
VST(regs_.V(inst.src3), addrReg, imm);
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// Spilling is okay, though not ideal.
|
||||
regs_.MapFPR(inst.src3 + i);
|
||||
FST_S(regs_.F(inst.src3 + i), addrReg, imm + 4 * i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,278 @@
|
||||
// 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/Profiler/Profiler.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HLE/ReplaceTables.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.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 LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
void LoongArch64JitBackend::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.
|
||||
regs_.SetGPRImm(inst.dest, (int32_t)inst.constant);
|
||||
break;
|
||||
|
||||
case IROp::SetConstF:
|
||||
regs_.Map(inst);
|
||||
if (inst.constant == 0)
|
||||
MOVGR2FR_W(regs_.F(inst.dest), R_ZERO);
|
||||
else
|
||||
QuickFLI(32, regs_.F(inst.dest), inst.constant, SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::Downcount:
|
||||
if (inst.constant <= 2048) {
|
||||
ADDI_D(DOWNCOUNTREG, DOWNCOUNTREG, -(s32)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH1, inst.constant);
|
||||
SUB_D(DOWNCOUNTREG, DOWNCOUNTREG, SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SetPC:
|
||||
regs_.Map(inst);
|
||||
MovToPC(regs_.R(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::SetPCConst:
|
||||
LI(SCRATCH1, inst.constant);
|
||||
MovToPC(SCRATCH1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Transfer(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::SetCtrlVFPU:
|
||||
regs_.SetGPRImm(IRREG_VFPU_CTRL_BASE + inst.dest, inst.constant);
|
||||
break;
|
||||
|
||||
case IROp::SetCtrlVFPUReg:
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(IRREG_VFPU_CTRL_BASE + inst.dest), regs_.R(inst.src1));
|
||||
regs_.MarkGPRDirty(IRREG_VFPU_CTRL_BASE + inst.dest, regs_.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::SetCtrlVFPUFReg:
|
||||
regs_.Map(inst);
|
||||
MOVGR2FR_W(regs_.R(IRREG_VFPU_CTRL_BASE + inst.dest), regs_.F(inst.src1));
|
||||
regs_.MarkGPRDirty(IRREG_VFPU_CTRL_BASE + inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::FpCondFromReg:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
MOVE(regs_.R(IRREG_FPCOND), regs_.R(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FpCondToReg:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::INIT } });
|
||||
MOVE(regs_.R(inst.dest), regs_.R(IRREG_FPCOND));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(IRREG_FPCOND));
|
||||
break;
|
||||
|
||||
case IROp::FpCtrlFromReg:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::NOINIT } });
|
||||
LI(SCRATCH1, 0x0181FFFF);
|
||||
AND(SCRATCH1, regs_.R(inst.src1), SCRATCH1);
|
||||
// Extract the new fpcond value.
|
||||
BSTRPICK_D(regs_.R(IRREG_FPCOND), regs_.R(IRREG_FPCOND), 23, 23);
|
||||
ST_W(SCRATCH1, CTXREG, IRREG_FCR31 * 4);
|
||||
regs_.MarkGPRDirty(IRREG_FPCOND, true);
|
||||
break;
|
||||
|
||||
case IROp::FpCtrlToReg:
|
||||
regs_.MapWithExtra(inst, { { 'G', IRREG_FPCOND, 1, MIPSMap::INIT } });
|
||||
// Load fcr31 and clear the fpcond bit.
|
||||
LD_W(SCRATCH1, CTXREG, IRREG_FCR31 * 4);
|
||||
LI(SCRATCH2, ~(1 << 23));
|
||||
AND(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
|
||||
// Now get the correct fpcond bit.
|
||||
ANDI(SCRATCH2, regs_.R(IRREG_FPCOND), 1);
|
||||
SLLI_D(SCRATCH2, SCRATCH2, 23);
|
||||
OR(regs_.R(inst.dest), SCRATCH1, SCRATCH2);
|
||||
|
||||
// Also update mips->fcr31 while we're here.
|
||||
ST_W(regs_.R(inst.dest), CTXREG, IRREG_FCR31 * 4);
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
case IROp::VfpuCtrlToReg:
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), regs_.R(IRREG_VFPU_CTRL_BASE + inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, regs_.IsNormalized32(IRREG_VFPU_CTRL_BASE + inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FMovFromGPR:
|
||||
if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) {
|
||||
regs_.MapFPR(inst.dest, MIPSMap::NOINIT);
|
||||
MOVGR2FR_W(regs_.F(inst.dest), R_ZERO);
|
||||
FFINT_S_W(regs_.F(inst.dest), regs_.F(inst.dest));
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
MOVGR2FR_W(regs_.F(inst.dest), regs_.R(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FMovToGPR:
|
||||
regs_.Map(inst);
|
||||
MOVFR2GR_S(regs_.R(inst.dest), regs_.F(inst.src1));
|
||||
regs_.MarkGPRDirty(inst.dest, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_System(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Syscall:
|
||||
FlushAll();
|
||||
SaveStaticRegisters();
|
||||
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::SYSCALL);
|
||||
#ifdef USE_PROFILER
|
||||
// When profiling, we can't skip CallSyscall, since it times syscalls.
|
||||
LI(R4, (int32_t)inst.constant);
|
||||
QuickCallFunction(&CallSyscall, SCRATCH2);
|
||||
#else
|
||||
// Skip the CallSyscall where possible.
|
||||
{
|
||||
MIPSOpcode op(inst.constant);
|
||||
void *quickFunc = GetQuickSyscallFunc(op);
|
||||
if (quickFunc) {
|
||||
LI(R4, (uintptr_t)GetSyscallFuncPointer(op));
|
||||
QuickCallFunction((const u8 *)quickFunc, SCRATCH2);
|
||||
} else {
|
||||
LI(R4, (int32_t)inst.constant);
|
||||
QuickCallFunction(&CallSyscall, SCRATCH2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
LoadStaticRegisters();
|
||||
// This is always followed by an ExitToPC, where we check coreState.
|
||||
break;
|
||||
|
||||
case IROp::CallReplacement:
|
||||
FlushAll();
|
||||
SaveStaticRegisters();
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::REPLACEMENT);
|
||||
QuickCallFunction(GetReplacementFunc(inst.constant)->replaceFunc, SCRATCH2);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
LoadStaticRegisters();
|
||||
|
||||
// Do not violate value in R4
|
||||
MOVE(SCRATCH1, R4);
|
||||
SRAI_W(SCRATCH2, R4, 31);
|
||||
// Absolute value trick: if neg, abs(x) == (x ^ -1) + 1.
|
||||
XOR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
SUB_W(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
SUB_D(DOWNCOUNTREG, DOWNCOUNTREG, SCRATCH1);
|
||||
|
||||
// R4 might be the mapped reg, but there's only one.
|
||||
// Set dest reg to the sign of the result.
|
||||
regs_.Map(inst);
|
||||
MOVE(regs_.R(inst.dest), SCRATCH2);
|
||||
break;
|
||||
|
||||
case IROp::Break:
|
||||
FlushAll();
|
||||
// This doesn't naturally have restore/apply around it.
|
||||
RestoreRoundingMode(true);
|
||||
SaveStaticRegisters();
|
||||
MovFromPC(R4);
|
||||
QuickCallFunction(&Core_BreakException, SCRATCH2);
|
||||
LoadStaticRegisters();
|
||||
ApplyRoundingMode(true);
|
||||
MovFromPC(SCRATCH1);
|
||||
ADDI_D(SCRATCH1, SCRATCH1, 4);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Breakpoint(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Breakpoint:
|
||||
case IROp::MemoryCheck:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::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
|
||||
@@ -0,0 +1,560 @@
|
||||
// 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 <algorithm>
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.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 LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
static bool Overlap(IRReg r1, int l1, IRReg r2, int l2) {
|
||||
return r1 < r2 + l2 && r1 + l1 > r2;
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Init:
|
||||
regs_.Map(inst);
|
||||
|
||||
switch ((Vec4Init)inst.src1) {
|
||||
case Vec4Init::AllZERO:
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VREPLGR2VR_D(regs_.V(inst.dest), R_ZERO);
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), R_ZERO);
|
||||
break;
|
||||
|
||||
case Vec4Init::AllONE:
|
||||
LI(SCRATCH1, 1.0f);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_W(regs_.V(inst.dest), SCRATCH1);
|
||||
} else {
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH1);
|
||||
for (int i = 1; i < 4; ++i)
|
||||
FMOV_S(regs_.F(inst.dest + i), regs_.F(inst.dest));
|
||||
}
|
||||
break;
|
||||
|
||||
case Vec4Init::AllMinusONE:
|
||||
LI(SCRATCH1, -1.0f);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_W(regs_.V(inst.dest), SCRATCH1);
|
||||
} else {
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH1);
|
||||
for (int i = 1; i < 4; ++i)
|
||||
FMOV_S(regs_.F(inst.dest + i), regs_.F(inst.dest));
|
||||
}
|
||||
break;
|
||||
|
||||
case Vec4Init::Set_1000:
|
||||
LI(SCRATCH1, 1.0f);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_D(regs_.V(inst.dest), R_ZERO);
|
||||
VINSGR2VR_W(regs_.V(inst.dest), SCRATCH1, 0);
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i == 0) {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
} else {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), R_ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Vec4Init::Set_0100:
|
||||
LI(SCRATCH1, 1.0f);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_D(regs_.V(inst.dest), R_ZERO);
|
||||
VINSGR2VR_W(regs_.V(inst.dest), SCRATCH1, 1);
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i == 1) {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
} else {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), R_ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Vec4Init::Set_0010:
|
||||
LI(SCRATCH1, 1.0f);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_D(regs_.V(inst.dest), R_ZERO);
|
||||
VINSGR2VR_W(regs_.V(inst.dest), SCRATCH1, 2);
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i == 2) {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
} else {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), R_ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Vec4Init::Set_0001:
|
||||
LI(SCRATCH1, 1.0f);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_D(regs_.V(inst.dest), R_ZERO);
|
||||
VINSGR2VR_W(regs_.V(inst.dest), SCRATCH1, 3);
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i == 3) {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
} else {
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), R_ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec4Shuffle:
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
regs_.Map(inst);
|
||||
if (regs_.GetFPRLaneCount(inst.src1) == 1 && (inst.src1 & 3) == 0 && inst.src2 == 0) {
|
||||
// This is a broadcast. If dest == src1, this won't clear it.
|
||||
regs_.SpillLockFPR(inst.src1);
|
||||
regs_.MapVec4(inst.dest, MIPSMap::NOINIT);
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
}
|
||||
|
||||
VSHUF4I_W(regs_.V(inst.dest), regs_.V(inst.src1), inst.src2);
|
||||
} else {
|
||||
if (inst.dest == inst.src1) {
|
||||
regs_.Map(inst);
|
||||
// Try to find the least swaps needed to move in place, never worse than 6 FMOVs.
|
||||
// Would be better with a vmerge and vector regs.
|
||||
int state[4]{ 0, 1, 2, 3 };
|
||||
int goal[4]{ (inst.src2 >> 0) & 3, (inst.src2 >> 2) & 3, (inst.src2 >> 4) & 3, (inst.src2 >> 6) & 3 };
|
||||
|
||||
static constexpr int NOT_FOUND = 4;
|
||||
auto findIndex = [](int *arr, int val, int start = 0) {
|
||||
return (int)(std::find(arr + start, arr + 4, val) - arr);
|
||||
};
|
||||
auto moveChained = [&](const std::vector<int> &lanes, bool rotate) {
|
||||
int firstState = state[lanes.front()];
|
||||
if (rotate)
|
||||
FMOV_S(SCRATCHF1, regs_.F(inst.dest + lanes.front()));
|
||||
for (size_t i = 1; i < lanes.size(); ++i) {
|
||||
FMOV_S(regs_.F(inst.dest + lanes[i - 1]), regs_.F(inst.dest + lanes[i]));
|
||||
state[lanes[i - 1]] = state[lanes[i]];
|
||||
}
|
||||
if (rotate) {
|
||||
FMOV_S(regs_.F(inst.dest + lanes.back()), SCRATCHF1);
|
||||
state[lanes.back()] = firstState;
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// Overlap, so if they match, nothing to do.
|
||||
if (goal[i] == state[i])
|
||||
continue;
|
||||
|
||||
int neededBy = findIndex(goal, state[i], i + 1);
|
||||
int foundIn = findIndex(state, goal[i], 0);
|
||||
_assert_(foundIn != NOT_FOUND);
|
||||
|
||||
if (neededBy == NOT_FOUND || neededBy == foundIn) {
|
||||
moveChained({ i, foundIn }, neededBy == foundIn);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Maybe we can avoid a swap and move the next thing into place.
|
||||
int neededByDepth2 = findIndex(goal, state[neededBy], i + 1);
|
||||
if (neededByDepth2 == NOT_FOUND || neededByDepth2 == foundIn) {
|
||||
moveChained({ neededBy, i, foundIn }, neededByDepth2 == foundIn);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since we only have 4 items, this is as deep as the chain could go.
|
||||
int neededByDepth3 = findIndex(goal, state[neededByDepth2], i + 1);
|
||||
moveChained({ neededByDepth2, neededBy, i, foundIn }, neededByDepth3 == foundIn);
|
||||
}
|
||||
} else {
|
||||
regs_.Map(inst);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int lane = (inst.src2 >> (i * 2)) & 3;
|
||||
FMOV_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + lane));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec4Blend:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
LoongArch64Reg src = regs_.V(inst.src1);;
|
||||
uint8_t imm = inst.constant;
|
||||
if (inst.dest == inst.src1)
|
||||
src = regs_.V(inst.src2);
|
||||
else if (inst.dest == inst.src2)
|
||||
imm = ~imm;
|
||||
else
|
||||
VOR_V(regs_.V(inst.dest), src, src);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
if (imm & (1 << i)) {
|
||||
VEXTRINS_W(regs_.V(inst.dest), src, (i << 4) | i);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int which = (inst.constant >> i) & 1;
|
||||
IRReg srcReg = which ? inst.src2 : inst.src1;
|
||||
if (inst.dest != srcReg)
|
||||
FMOV_S(regs_.F(inst.dest + i), regs_.F(srcReg + i));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec4Mov:
|
||||
if (inst.dest != inst.src1) {
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VOR_V(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src1));
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FMOV_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecArith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Add:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VFADD_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2));
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FADD_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
||||
break;
|
||||
|
||||
case IROp::Vec4Sub:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VFSUB_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2));
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FSUB_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
||||
break;
|
||||
|
||||
case IROp::Vec4Mul:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VFMUL_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2));
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FMUL_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
||||
break;
|
||||
|
||||
case IROp::Vec4Div:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VFDIV_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2));
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FDIV_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
||||
break;
|
||||
|
||||
case IROp::Vec4Scale:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
if (Overlap(inst.dest, 4, inst.src2, 1) || Overlap(inst.src1, 4, inst.src2, 1))
|
||||
DISABLE;
|
||||
|
||||
VSHUF4I_W(regs_.V(inst.src2), regs_.V(inst.src2), 0);
|
||||
VFMUL_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2));
|
||||
} else {
|
||||
if (Overlap(inst.src2, 1, inst.dest, 3)) {
|
||||
// We have to handle overlap, doing dest == src2 last.
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (inst.src2 != inst.dest + i)
|
||||
FMUL_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2));
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (inst.src2 == inst.dest + i)
|
||||
FMUL_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FMUL_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i), regs_.F(inst.src2));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec4Neg:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VBITREVI_W(regs_.V(inst.dest), regs_.V(inst.src1), 31);
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FNEG_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i));
|
||||
break;
|
||||
|
||||
case IROp::Vec4Abs:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX)
|
||||
VBITSETI_W(regs_.V(inst.dest), regs_.V(inst.src1), 31);
|
||||
else
|
||||
for (int i = 0; i < 4; ++i)
|
||||
FABS_S(regs_.F(inst.dest + i), regs_.F(inst.src1 + i));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecHoriz(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4Dot:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
if (Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4))
|
||||
DISABLE;
|
||||
|
||||
VFMUL_S(regs_.V(inst.dest), regs_.V(inst.src1), regs_.V(inst.src2));
|
||||
VOR_V(EncodeRegToV(SCRATCHF1), regs_.V(inst.dest), regs_.V(inst.dest));
|
||||
VSHUF4I_W(EncodeRegToV(SCRATCHF1), regs_.V(inst.dest), VFPU_SWIZZLE(1, 0, 3, 2));
|
||||
VFADD_S(regs_.V(inst.dest), regs_.V(inst.dest), EncodeRegToV(SCRATCHF1));
|
||||
VEXTRINS_D(EncodeRegToV(SCRATCHF1), regs_.V(inst.dest), 1);
|
||||
// Do we need care about upper 96 bits?
|
||||
VFADD_S(regs_.V(inst.dest), regs_.V(inst.dest), EncodeRegToV(SCRATCHF1));
|
||||
} else {
|
||||
if (Overlap(inst.dest, 1, inst.src1, 4) || Overlap(inst.dest, 1, inst.src2, 4)) {
|
||||
// This means inst.dest overlaps one of src1 or src2. We have to do that one first.
|
||||
// Technically this may impact -0.0 and such, but dots accurately need to be aligned anyway.
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (inst.dest == inst.src1 + i || inst.dest == inst.src2 + i)
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i));
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (inst.dest != inst.src1 + i && inst.dest != inst.src2 + i)
|
||||
FMADD_S(regs_.F(inst.dest), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i), regs_.F(inst.dest));
|
||||
}
|
||||
} else {
|
||||
FMUL_S(regs_.F(inst.dest), regs_.F(inst.src1), regs_.F(inst.src2));
|
||||
for (int i = 1; i < 4; ++i)
|
||||
FMADD_S(regs_.F(inst.dest), regs_.F(inst.src1 + i), regs_.F(inst.src2 + i), regs_.F(inst.dest));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecPack(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec2Unpack16To31:
|
||||
case IROp::Vec2Pack31To16:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::Vec4Pack32To8:
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
regs_.Map(inst);
|
||||
if (Overlap(inst.dest, 1, inst.src1, 4))
|
||||
DISABLE;
|
||||
|
||||
VSRLI_W(EncodeRegToV(SCRATCHF1), regs_.V(inst.src1), 23);
|
||||
VPICKEV_B(EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1));
|
||||
VPICKEV_B(regs_.V(inst.dest), EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1));
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec4Unpack8To32:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
if (Overlap(inst.dest, 1, inst.src1, 4))
|
||||
DISABLE;
|
||||
|
||||
VSLLWIL_HU_BU(regs_.V(inst.dest), regs_.V(inst.src1), 0);
|
||||
VSLLWIL_WU_HU(regs_.V(inst.dest), regs_.V(inst.dest), 0);
|
||||
} else {
|
||||
MOVFR2GR_S(SCRATCH2, regs_.F(inst.src1));
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// Mask using walls.
|
||||
if (i != 0) {
|
||||
SRLI_D(SCRATCH1, SCRATCH2, i * 8);
|
||||
SLLI_D(SCRATCH1, SCRATCH1, 24);
|
||||
} else {
|
||||
SLLI_D(SCRATCH1, SCRATCH2, 24);
|
||||
}
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec2Unpack16To32:
|
||||
// TODO: This works for now, but may need to handle aliasing for vectors.
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
}
|
||||
regs_.Map(inst);
|
||||
MOVFR2GR_S(SCRATCH2, regs_.F(inst.src1));
|
||||
SLLI_D(SCRATCH1, SCRATCH2, 16);
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH1);
|
||||
SRLI_D(SCRATCH1, SCRATCH2, 16);
|
||||
SLLI_D(SCRATCH1, SCRATCH1, 16);
|
||||
MOVGR2FR_W(regs_.F(inst.dest + 1), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::Vec4DuplicateUpperBitsAndShift1:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VSRLI_W(EncodeRegToV(SCRATCHF1), regs_.V(inst.src1), 16);
|
||||
VOR_V(EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1), regs_.V(inst.src1));
|
||||
VSRLI_W(regs_.V(inst.dest), EncodeRegToV(SCRATCHF1), 8);
|
||||
VOR_V(regs_.V(inst.dest), regs_.V(inst.dest), EncodeRegToV(SCRATCHF1));
|
||||
VSRLI_W(regs_.V(inst.dest), regs_.V(inst.dest), 1);
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
MOVFR2GR_S(SCRATCH1, regs_.F(inst.src1 + i));
|
||||
SRLI_W(SCRATCH2, SCRATCH1, 8);
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
SRLI_W(SCRATCH2, SCRATCH1, 16);
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
SRLI_W(SCRATCH1, SCRATCH1, 1);
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec4Pack31To8:
|
||||
// TODO: This works for now, but may need to handle aliasing for vectors.
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
if (Overlap(inst.dest, 1, inst.src1, 4))
|
||||
DISABLE;
|
||||
|
||||
VSRLI_W(EncodeRegToV(SCRATCHF1), regs_.V(inst.src1), 23);
|
||||
VPICKEV_B(EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1));
|
||||
VPICKEV_B(regs_.V(inst.dest), EncodeRegToV(SCRATCHF1), EncodeRegToV(SCRATCHF1));
|
||||
}else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
MOVFR2GR_S(SCRATCH1, regs_.F(inst.src1 + i));
|
||||
SRLI_D(SCRATCH1, SCRATCH1, 23);
|
||||
if (i == 0) {
|
||||
ANDI(SCRATCH2, SCRATCH1, 0xFF);
|
||||
} else {
|
||||
ANDI(SCRATCH1, SCRATCH1, 0xFF);
|
||||
SLLI_D(SCRATCH1, SCRATCH1, 8 * i);
|
||||
OR(SCRATCH2, SCRATCH2, SCRATCH1);
|
||||
}
|
||||
}
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec2Pack32To16:
|
||||
// TODO: This works for now, but may need to handle aliasing for vectors.
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
}
|
||||
regs_.Map(inst);
|
||||
MOVFR2GR_S(SCRATCH1, regs_.F(inst.src1));
|
||||
MOVFR2GR_S(SCRATCH2, regs_.F(inst.src1 + 1));
|
||||
// Keep in mind, this was sign-extended, so we have to zero the upper.
|
||||
SLLI_D(SCRATCH1, SCRATCH1, 32);
|
||||
// Now we just set (SCRATCH2 & 0xFFFF0000) | SCRATCH1.
|
||||
SRLI_D(SCRATCH1, SCRATCH1, 48);
|
||||
// Use a wall to mask. We can ignore the upper 32 here.
|
||||
SRLI_D(SCRATCH2, SCRATCH2, 16);
|
||||
SLLI_D(SCRATCH2, SCRATCH2, 16);
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
// Okay, to the floating point register.
|
||||
MOVGR2FR_W(regs_.F(inst.dest), SCRATCH1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_VecClamp(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Vec4ClampToZero:
|
||||
regs_.Map(inst);
|
||||
if (cpu_info.LOONGARCH_LSX) {
|
||||
VREPLGR2VR_D(EncodeRegToV(SCRATCHF1), R_ZERO);
|
||||
VMAX_W(regs_.V(inst.dest), regs_.V(inst.src1), EncodeRegToV(SCRATCHF1));
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
MOVFR2GR_S(SCRATCH1, regs_.F(inst.src1 + i));
|
||||
SRAI_W(SCRATCH2, SCRATCH1, 31);
|
||||
ORN(SCRATCH2, R_ZERO, SCRATCH2);
|
||||
AND(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
MOVGR2FR_W(regs_.F(inst.dest + i), SCRATCH1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Vec2ClampToZero:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,411 @@
|
||||
// Copyright (c) 2025- 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 <cstddef>
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/MIPSTables.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64Jit.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.h"
|
||||
|
||||
#include <algorithm>
|
||||
// for std::min
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
// Needs space for a LI and J which might both be 32-bit offsets.
|
||||
static constexpr int MIN_BLOCK_NORMAL_LEN = 16;
|
||||
static constexpr int MIN_BLOCK_EXIT_LEN = 8;
|
||||
|
||||
LoongArch64JitBackend::LoongArch64JitBackend(JitOptions &jitopt, IRBlockCache &blocks)
|
||||
: IRNativeBackend(blocks), jo(jitopt), regs_(&jo) {
|
||||
// Automatically disable incompatible options.
|
||||
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
|
||||
jo.enablePointerify = false;
|
||||
}
|
||||
jo.optimizeForInterpreter = false;
|
||||
|
||||
// Since we store the offset, this is as big as it can be.
|
||||
// We could shift off one bit to double it, would need to change LoongArch64Asm.
|
||||
AllocCodeSpace(1024 * 1024 * 16);
|
||||
|
||||
regs_.Init(this);
|
||||
}
|
||||
|
||||
LoongArch64JitBackend::~LoongArch64JitBackend(){
|
||||
}
|
||||
|
||||
static void NoBlockExits() {
|
||||
_assert_msg_(false, "Never exited block, invalid IR?");
|
||||
}
|
||||
|
||||
bool LoongArch64JitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num) {
|
||||
if (GetSpaceLeft() < 0x800)
|
||||
return false;
|
||||
|
||||
IRBlock *block = irBlockCache->GetBlock(block_num);
|
||||
BeginWrite(std::min(GetSpaceLeft(), (size_t)block->GetNumIRInstructions() * 32));
|
||||
|
||||
u32 startPC = block->GetOriginalStart();
|
||||
bool wroteCheckedOffset = false;
|
||||
if (jo.enableBlocklink && !jo.useBackJump) {
|
||||
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
|
||||
wroteCheckedOffset = true;
|
||||
|
||||
WriteDebugPC(startPC);
|
||||
|
||||
FixupBranch normalEntry = BGE(DOWNCOUNTREG, R_ZERO);
|
||||
LI(SCRATCH1, startPC);
|
||||
QuickJ(R_RA, outerLoopPCInSCRATCH1_);
|
||||
SetJumpTarget(normalEntry);
|
||||
}
|
||||
|
||||
// Don't worry, the codespace isn't large enough to overflow offsets.
|
||||
const u8 *blockStart = GetCodePointer();
|
||||
block->SetNativeOffset((int)GetOffset(blockStart));
|
||||
compilingBlockNum_ = block_num;
|
||||
|
||||
regs_.Start(irBlockCache, block_num);
|
||||
|
||||
std::vector<const u8 *> addresses;
|
||||
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
|
||||
for (int i = 0; i < block->GetNumIRInstructions(); ++i) {
|
||||
const IRInst &inst = instructions[i];
|
||||
regs_.SetIRIndex(i);
|
||||
addresses.push_back(GetCodePtr());
|
||||
|
||||
CompileIRInst(inst);
|
||||
|
||||
if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR))
|
||||
regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR));
|
||||
|
||||
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
|
||||
if (GetSpaceLeft() < 0x800) {
|
||||
compilingBlockNum_ = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We should've written an exit above. If we didn't, bad things will happen.
|
||||
// Only check if debug stats are enabled - needlessly wastes jit space.
|
||||
if (DebugStatsEnabled()) {
|
||||
QuickCallFunction(&NoBlockExits, SCRATCH2);
|
||||
QuickJ(R_RA, hooks_.crashHandler);
|
||||
}
|
||||
|
||||
int len = (int)GetOffset(GetCodePointer()) - block->GetNativeOffset();
|
||||
if (len < MIN_BLOCK_NORMAL_LEN) {
|
||||
// We need at least 16 bytes to invalidate blocks with, but larger doesn't need to align.
|
||||
ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - len);
|
||||
}
|
||||
|
||||
if (!wroteCheckedOffset) {
|
||||
// Always record this, even if block link disabled - it's used for size calc.
|
||||
SetBlockCheckedOffset(block_num, (int)GetOffset(GetCodePointer()));
|
||||
}
|
||||
|
||||
if (jo.enableBlocklink && jo.useBackJump) {
|
||||
WriteDebugPC(startPC);
|
||||
|
||||
// Most blocks shouldn't be >= 256KB, so usually we can just BGE.
|
||||
if (BranchInRange(blockStart)) {
|
||||
BGE(DOWNCOUNTREG, R_ZERO, blockStart);
|
||||
} else {
|
||||
FixupBranch skip = BLT(DOWNCOUNTREG, R_ZERO);
|
||||
B(blockStart);
|
||||
SetJumpTarget(skip);
|
||||
}
|
||||
LI(SCRATCH1, startPC);
|
||||
QuickJ(R_RA, outerLoopPCInSCRATCH1_);
|
||||
}
|
||||
|
||||
if (logBlocks_ > 0) {
|
||||
--logBlocks_;
|
||||
|
||||
std::map<const u8 *, int> addressesLookup;
|
||||
for (int i = 0; i < (int)addresses.size(); ++i)
|
||||
addressesLookup[addresses[i]] = i;
|
||||
|
||||
INFO_LOG(Log::JIT, "=============== LoongArch64 (%08x, %d bytes) ===============", startPC, len);
|
||||
const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block);
|
||||
for (const u8 *p = blockStart; p < GetCodePointer(); ) {
|
||||
auto it = addressesLookup.find(p);
|
||||
if (it != addressesLookup.end()) {
|
||||
const IRInst &inst = instructions[it->second];
|
||||
|
||||
char temp[512];
|
||||
DisassembleIR(temp, sizeof(temp), inst);
|
||||
INFO_LOG(Log::JIT, "IR: #%d %s", it->second, temp);
|
||||
}
|
||||
|
||||
auto next = std::next(it);
|
||||
const u8 *nextp = next == addressesLookup.end() ? GetCodePointer() : next->first;
|
||||
|
||||
#if PPSSPP_ARCH(LOONGARCH64)
|
||||
auto lines = DisassembleLA64(p, (int)(nextp - p));
|
||||
for (const auto &line : lines)
|
||||
INFO_LOG(Log::JIT, "LA: %s", line.c_str());
|
||||
#endif
|
||||
p = nextp;
|
||||
}
|
||||
}
|
||||
|
||||
EndWrite();
|
||||
FlushIcache();
|
||||
compilingBlockNum_ = -1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::WriteConstExit(uint32_t pc) {
|
||||
int block_num = blocks_.GetBlockNumberFromStartAddress(pc);
|
||||
const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);
|
||||
|
||||
int exitStart = (int)GetOffset(GetCodePointer());
|
||||
if (block_num >= 0 && jo.enableBlocklink && nativeBlock && nativeBlock->checkedOffset != 0) {
|
||||
QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);
|
||||
} else {
|
||||
LI(SCRATCH1, pc);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
}
|
||||
|
||||
if (jo.enableBlocklink) {
|
||||
// In case of compression or early link, make sure it's large enough.
|
||||
int len = (int)GetOffset(GetCodePointer()) - exitStart;
|
||||
if (len < MIN_BLOCK_EXIT_LEN) {
|
||||
ReserveCodeSpace(MIN_BLOCK_EXIT_LEN - len);
|
||||
len = MIN_BLOCK_EXIT_LEN;
|
||||
}
|
||||
|
||||
AddLinkableExit(compilingBlockNum_, pc, exitStart, len);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::OverwriteExit(int srcOffset, int len, int block_num) {
|
||||
_dbg_assert_(len >= MIN_BLOCK_EXIT_LEN);
|
||||
|
||||
const IRNativeBlock *nativeBlock = GetNativeBlock(block_num);
|
||||
if (nativeBlock) {
|
||||
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + srcOffset;
|
||||
if (PlatformIsWXExclusive()) {
|
||||
ProtectMemoryPages(writable, len, MEM_PROT_READ | MEM_PROT_WRITE);
|
||||
}
|
||||
|
||||
LoongArch64Emitter emitter(GetBasePtr() + srcOffset, writable);
|
||||
emitter.QuickJ(SCRATCH1, GetBasePtr() + nativeBlock->checkedOffset);
|
||||
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
|
||||
if (bytesWritten < len)
|
||||
emitter.ReserveCodeSpace(len - bytesWritten);
|
||||
emitter.FlushIcache();
|
||||
|
||||
if (PlatformIsWXExclusive()) {
|
||||
ProtectMemoryPages(writable, 16, MEM_PROT_READ | MEM_PROT_EXEC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Generic(IRInst inst) {
|
||||
// If we got here, we're going the slow way.
|
||||
uint64_t value;
|
||||
memcpy(&value, &inst, sizeof(inst));
|
||||
|
||||
FlushAll();
|
||||
LI(R4, value);
|
||||
SaveStaticRegisters();
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IR_INTERPRET);
|
||||
QuickCallFunction(&DoIRInst, SCRATCH2);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
LoadStaticRegisters();
|
||||
|
||||
// We only need to check the return value if it's a potential exit.
|
||||
if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) {
|
||||
MOVE(SCRATCH1, R4);
|
||||
|
||||
if (BranchZeroInRange(dispatcherPCInSCRATCH1_)) {
|
||||
BNEZ(R4, dispatcherPCInSCRATCH1_);
|
||||
} else {
|
||||
FixupBranch skip = BEQZ(R4);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
SetJumpTarget(skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::CompIR_Interpret(IRInst inst) {
|
||||
MIPSOpcode op(inst.constant);
|
||||
|
||||
// IR protects us against this being a branching instruction (well, hopefully.)
|
||||
FlushAll();
|
||||
SaveStaticRegisters();
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::INTERPRET);
|
||||
if (DebugStatsEnabled()) {
|
||||
LI(R4, MIPSGetName(op));
|
||||
QuickCallFunction(&NotifyMIPSInterpret, SCRATCH2);
|
||||
}
|
||||
LI(R4, (int32_t)inst.constant);
|
||||
QuickCallFunction((const u8 *)MIPSGetInterpretFunc(op), SCRATCH2);
|
||||
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
|
||||
LoadStaticRegisters();
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::FlushAll() {
|
||||
regs_.FlushAll();
|
||||
}
|
||||
|
||||
bool LoongArch64JitBackend::DescribeCodePtr(const u8 *ptr, std::string &name) const {
|
||||
// Used in disassembly viewer.
|
||||
// Don't use spaces; profilers get confused or truncate them.
|
||||
if (ptr == dispatcherPCInSCRATCH1_) {
|
||||
name = "dispatcherPCInSCRATCH1";
|
||||
} else if (ptr == outerLoopPCInSCRATCH1_) {
|
||||
name = "outerLoopPCInSCRATCH1";
|
||||
} else if (ptr == dispatcherNoCheck_) {
|
||||
name = "dispatcherNoCheck";
|
||||
} else if (ptr == saveStaticRegisters_) {
|
||||
name = "saveStaticRegisters";
|
||||
} else if (ptr == loadStaticRegisters_) {
|
||||
name = "loadStaticRegisters";
|
||||
} else if (ptr == applyRoundingMode_) {
|
||||
name = "applyRoundingMode";
|
||||
} else if (ptr >= GetBasePtr() && ptr < GetBasePtr() + jitStartOffset_) {
|
||||
name = "fixedCode";
|
||||
} else {
|
||||
return IRNativeBackend::DescribeCodePtr(ptr, name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::ClearAllBlocks() {
|
||||
ClearCodeSpace(jitStartOffset_);
|
||||
FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_);
|
||||
EraseAllLinks(-1);
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::InvalidateBlock(IRBlockCache *irBlockCache, int block_num) {
|
||||
IRBlock *block = irBlockCache->GetBlock(block_num);
|
||||
int offset = block->GetNativeOffset();
|
||||
u8 *writable = GetWritablePtrFromCodePtr(GetBasePtr()) + offset;
|
||||
|
||||
// Overwrite the block with a jump to compile it again.
|
||||
u32 pc = block->GetOriginalStart();
|
||||
if (pc != 0) {
|
||||
// Hopefully we always have at least 16 bytes, which should be all we need.
|
||||
if (PlatformIsWXExclusive()) {
|
||||
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_WRITE);
|
||||
}
|
||||
|
||||
LoongArch64Emitter emitter(GetBasePtr() + offset, writable);
|
||||
// We sign extend to ensure it will fit in 32-bit and 8 bytes LI.
|
||||
// TODO: May need to change if dispatcher doesn't reload PC.
|
||||
emitter.LI(SCRATCH1, (int32_t)pc);
|
||||
emitter.QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
int bytesWritten = (int)(emitter.GetWritableCodePtr() - writable);
|
||||
if (bytesWritten < MIN_BLOCK_NORMAL_LEN)
|
||||
emitter.ReserveCodeSpace(MIN_BLOCK_NORMAL_LEN - bytesWritten);
|
||||
emitter.FlushIcache();
|
||||
|
||||
if (PlatformIsWXExclusive()) {
|
||||
ProtectMemoryPages(writable, MIN_BLOCK_NORMAL_LEN, MEM_PROT_READ | MEM_PROT_EXEC);
|
||||
}
|
||||
}
|
||||
|
||||
EraseAllLinks(block_num);
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::RestoreRoundingMode(bool force) {
|
||||
MOVGR2FCSR(FCSR3, R_ZERO); // 0 = RNE - Round Nearest Even
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::ApplyRoundingMode(bool force) {
|
||||
QuickCallFunction(applyRoundingMode_);
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::MovFromPC(LoongArch64Reg r) {
|
||||
LD_WU(r, CTXREG, offsetof(MIPSState, pc));
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::MovToPC(LoongArch64Reg r) {
|
||||
ST_W(r, CTXREG, offsetof(MIPSState, pc));
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::WriteDebugPC(uint32_t pc) {
|
||||
if (hooks_.profilerPC) {
|
||||
int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();
|
||||
LI(SCRATCH2, hooks_.profilerPC);
|
||||
LI(R_RA, (int32_t)pc);
|
||||
ST_W(R_RA, SCRATCH2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::WriteDebugPC(LoongArch64Reg r) {
|
||||
if (hooks_.profilerPC) {
|
||||
int offset = (const u8 *)hooks_.profilerPC - GetBasePtr();
|
||||
LI(SCRATCH2, hooks_.profilerPC);
|
||||
ST_W(r, SCRATCH2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::WriteDebugProfilerStatus(IRProfilerStatus status) {
|
||||
if (hooks_.profilerPC) {
|
||||
int offset = (const u8 *)hooks_.profilerStatus - GetBasePtr();
|
||||
LI(SCRATCH2, hooks_.profilerStatus);
|
||||
LI(R_RA, (int)status);
|
||||
ST_W(R_RA, SCRATCH2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::SaveStaticRegisters() {
|
||||
if (jo.useStaticAlloc) {
|
||||
QuickCallFunction(saveStaticRegisters_);
|
||||
} else {
|
||||
// Inline the single operation
|
||||
ST_W(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::LoadStaticRegisters() {
|
||||
if (jo.useStaticAlloc) {
|
||||
QuickCallFunction(loadStaticRegisters_);
|
||||
} else {
|
||||
LD_W(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::NormalizeSrc1(IRInst inst, LoongArch64Reg *reg, LoongArch64Reg tempReg, bool allowOverlap) {
|
||||
*reg = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, tempReg);
|
||||
}
|
||||
|
||||
void LoongArch64JitBackend::NormalizeSrc12(IRInst inst, LoongArch64Reg *lhs, LoongArch64Reg *rhs, LoongArch64Reg lhsTempReg, LoongArch64Reg rhsTempReg, bool allowOverlap) {
|
||||
*lhs = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, lhsTempReg);
|
||||
*rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg);
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64JitBackend::NormalizeR(IRReg rs, IRReg rd, LoongArch64Reg 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 (regs_.IsGPRImm(rs) && regs_.GetGPRImm(rs) == 0) {
|
||||
return R_ZERO;
|
||||
} else if (regs_.IsGPRMappedAsPointer(rs) || rs == rd) {
|
||||
return regs_.Normalize32(rs, tempReg);
|
||||
} else {
|
||||
return regs_.Normalize32(rs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2025- 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/LoongArch64Emitter.h"
|
||||
#include "Core/MIPS/IR/IRJit.h"
|
||||
#include "Core/MIPS/IR/IRNativeCommon.h"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
class LoongArch64JitBackend : public LoongArch64Gen::LoongArch64CodeBlock, public IRNativeBackend {
|
||||
public:
|
||||
LoongArch64JitBackend(JitOptions &jo, IRBlockCache &blocks);
|
||||
~LoongArch64JitBackend();
|
||||
|
||||
bool DescribeCodePtr(const u8 *ptr, std::string &name) const override;
|
||||
|
||||
void GenerateFixedCode(MIPSState *mipsState) override;
|
||||
bool CompileBlock(IRBlockCache *irBlockCache, int block_num) override;
|
||||
void ClearAllBlocks() override;
|
||||
void InvalidateBlock(IRBlockCache *irBlockCache, int block_num) override;
|
||||
|
||||
protected:
|
||||
const CodeBlockCommon &CodeBlock() const override {
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
void RestoreRoundingMode(bool force = false);
|
||||
void ApplyRoundingMode(bool force = false);
|
||||
void MovFromPC(LoongArch64Gen::LoongArch64Reg r);
|
||||
void MovToPC(LoongArch64Gen::LoongArch64Reg r);
|
||||
void WriteDebugPC(uint32_t pc);
|
||||
void WriteDebugPC(LoongArch64Gen::LoongArch64Reg r);
|
||||
void WriteDebugProfilerStatus(IRProfilerStatus status);
|
||||
|
||||
void SaveStaticRegisters();
|
||||
void LoadStaticRegisters();
|
||||
|
||||
// Note: destroys SCRATCH1.
|
||||
void FlushAll();
|
||||
|
||||
void WriteConstExit(uint32_t pc);
|
||||
void OverwriteExit(int srcOffset, int len, int block_num) override;
|
||||
|
||||
void CompIR_Arith(IRInst inst) override;
|
||||
void CompIR_Assign(IRInst inst) override;
|
||||
void CompIR_Basic(IRInst inst) override;
|
||||
void CompIR_Bits(IRInst inst) override;
|
||||
void CompIR_Breakpoint(IRInst inst) override;
|
||||
void CompIR_Compare(IRInst inst) override;
|
||||
void CompIR_CondAssign(IRInst inst) override;
|
||||
void CompIR_CondStore(IRInst inst) override;
|
||||
void CompIR_Div(IRInst inst) override;
|
||||
void CompIR_Exit(IRInst inst) override;
|
||||
void CompIR_ExitIf(IRInst inst) override;
|
||||
void CompIR_FArith(IRInst inst) override;
|
||||
void CompIR_FAssign(IRInst inst) override;
|
||||
void CompIR_FCompare(IRInst inst) override;
|
||||
void CompIR_FCondAssign(IRInst inst) override;
|
||||
void CompIR_FCvt(IRInst inst) override;
|
||||
void CompIR_FLoad(IRInst inst) override;
|
||||
void CompIR_FRound(IRInst inst) override;
|
||||
void CompIR_FSat(IRInst inst) override;
|
||||
void CompIR_FSpecial(IRInst inst) override;
|
||||
void CompIR_FStore(IRInst inst) override;
|
||||
void CompIR_Generic(IRInst inst) override;
|
||||
void CompIR_HiLo(IRInst inst) override;
|
||||
void CompIR_Interpret(IRInst inst) override;
|
||||
void CompIR_Load(IRInst inst) override;
|
||||
void CompIR_LoadShift(IRInst inst) override;
|
||||
void CompIR_Logic(IRInst inst) override;
|
||||
void CompIR_Mult(IRInst inst) override;
|
||||
void CompIR_RoundingMode(IRInst inst) override;
|
||||
void CompIR_Shift(IRInst inst) override;
|
||||
void CompIR_Store(IRInst inst) override;
|
||||
void CompIR_StoreShift(IRInst inst) override;
|
||||
void CompIR_System(IRInst inst) override;
|
||||
void CompIR_Transfer(IRInst inst) override;
|
||||
void CompIR_VecArith(IRInst inst) override;
|
||||
void CompIR_VecAssign(IRInst inst) override;
|
||||
void CompIR_VecClamp(IRInst inst) override;
|
||||
void CompIR_VecHoriz(IRInst inst) override;
|
||||
void CompIR_VecLoad(IRInst inst) override;
|
||||
void CompIR_VecPack(IRInst inst) override;
|
||||
void CompIR_VecStore(IRInst inst) override;
|
||||
void CompIR_ValidateAddress(IRInst inst) override;
|
||||
|
||||
void SetScratch1ToSrc1Address(IRReg src1);
|
||||
// Modifies SCRATCH regs.
|
||||
int32_t AdjustForAddressOffset(LoongArch64Gen::LoongArch64Reg *reg, int32_t constant, int32_t range = 0);
|
||||
void NormalizeSrc1(IRInst inst, LoongArch64Gen::LoongArch64Reg *reg, LoongArch64Gen::LoongArch64Reg tempReg, bool allowOverlap);
|
||||
void NormalizeSrc12(IRInst inst, LoongArch64Gen::LoongArch64Reg *lhs, LoongArch64Gen::LoongArch64Reg *rhs, LoongArch64Gen::LoongArch64Reg lhsTempReg, LoongArch64Gen::LoongArch64Reg rhsTempReg, bool allowOverlap);
|
||||
LoongArch64Gen::LoongArch64Reg NormalizeR(IRReg rs, IRReg rd, LoongArch64Gen::LoongArch64Reg tempReg);
|
||||
|
||||
JitOptions &jo;
|
||||
LoongArch64RegCache regs_;
|
||||
|
||||
const u8 *outerLoop_ = nullptr;
|
||||
const u8 *outerLoopPCInSCRATCH1_ = nullptr;
|
||||
const u8 *dispatcherCheckCoreState_ = nullptr;
|
||||
const u8 *dispatcherPCInSCRATCH1_ = nullptr;
|
||||
const u8 *dispatcherNoCheck_ = nullptr;
|
||||
const u8 *applyRoundingMode_ = nullptr;
|
||||
|
||||
const u8 *saveStaticRegisters_ = nullptr;
|
||||
const u8 *loadStaticRegisters_ = nullptr;
|
||||
|
||||
int jitStartOffset_ = 0;
|
||||
int compilingBlockNum_ = -1;
|
||||
int logBlocks_ = 0;
|
||||
};
|
||||
|
||||
class LoongArch64Jit : public IRNativeJit{
|
||||
public:
|
||||
LoongArch64Jit(MIPSState *mipsState) : IRNativeJit(mipsState), la64Backend_(jo, blocks_) {
|
||||
Init(la64Backend_);
|
||||
}
|
||||
private:
|
||||
LoongArch64JitBackend la64Backend_;
|
||||
};
|
||||
|
||||
} // namespace MIPSComp
|
||||
@@ -0,0 +1,721 @@
|
||||
// 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/.
|
||||
|
||||
#ifndef offsetof
|
||||
#include <cstddef>
|
||||
#endif
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
#include "Core/MIPS/IR/IRAnalysis.h"
|
||||
#include "Core/MIPS/LoongArch64/LoongArch64RegCache.h"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
#include "Core/Reporting.h"
|
||||
|
||||
using namespace LoongArch64Gen;
|
||||
using namespace LoongArch64JitConstants;
|
||||
|
||||
LoongArch64RegCache::LoongArch64RegCache(MIPSComp::JitOptions *jo)
|
||||
: IRNativeRegCacheBase(jo) {
|
||||
// The V(LSX) regs overlap F regs, so we just use one slot.
|
||||
config_.totalNativeRegs = NUM_LAGPR + NUM_LAFPR;
|
||||
// F regs are used for both FPU and Vec, so we don't need VREGs.
|
||||
config_.mapUseVRegs = false;
|
||||
config_.mapFPUSIMD = true;
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::Init(LoongArch64Emitter *emitter) {
|
||||
emit_ = emitter;
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::SetupInitialRegs() {
|
||||
IRNativeRegCacheBase::SetupInitialRegs();
|
||||
|
||||
// Treat R_ZERO a bit specially, but it's basically static alloc too.
|
||||
nrInitial_[R_ZERO].mipsReg = MIPS_REG_ZERO;
|
||||
nrInitial_[R_ZERO].normalized32 = true;
|
||||
|
||||
// Since we also have a fixed zero, mark it as a static allocation.
|
||||
mrInitial_[MIPS_REG_ZERO].loc = MIPSLoc::REG_IMM;
|
||||
mrInitial_[MIPS_REG_ZERO].nReg = R_ZERO;
|
||||
mrInitial_[MIPS_REG_ZERO].imm = 0;
|
||||
mrInitial_[MIPS_REG_ZERO].isStatic = true;
|
||||
}
|
||||
|
||||
const int *LoongArch64RegCache::GetAllocationOrder(MIPSLoc type, MIPSMap flags, int &count, int &base) const {
|
||||
base = R0;
|
||||
|
||||
if (type == MIPSLoc::REG) {
|
||||
// R22-R26 (Also R27) are most suitable for static allocation. Those that are chosen for static allocation
|
||||
static const int allocationOrder[] = {
|
||||
R22, R23, R24, R25, R26, R27, R4, R5, R6, R7, R8, R9, R10, R11, R14, R15, R16, R17, R18, R19, R20,
|
||||
};
|
||||
static const int allocationOrderStaticAlloc[] = {
|
||||
R4, R5, R6, R7, R8, R9, R10, R11, R14, R15, R16, R17, R18, R19, R20,
|
||||
};
|
||||
|
||||
if (jo_->useStaticAlloc) {
|
||||
count = ARRAY_SIZE(allocationOrderStaticAlloc);
|
||||
return allocationOrderStaticAlloc;
|
||||
} else {
|
||||
count = ARRAY_SIZE(allocationOrder);
|
||||
return allocationOrder;
|
||||
}
|
||||
} else if (type == MIPSLoc::FREG) {
|
||||
static const int allocationOrder[] = {
|
||||
F24, F25, F26, F27, F28, F29, F30, F31,
|
||||
F0, F1, F2, F3, F4, F5, F6, F7,
|
||||
F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23,
|
||||
};
|
||||
|
||||
count = ARRAY_SIZE(allocationOrder);
|
||||
return allocationOrder;
|
||||
} else {
|
||||
_assert_msg_(false, "Allocation order not yet implemented");
|
||||
count = 0;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const LoongArch64RegCache::StaticAllocation *LoongArch64RegCache::GetStaticAllocations(int &count) const {
|
||||
static const StaticAllocation allocs[] = {
|
||||
{ MIPS_REG_SP, R22, MIPSLoc::REG, true },
|
||||
{ MIPS_REG_V0, R23, MIPSLoc::REG },
|
||||
{ MIPS_REG_V1, R24, MIPSLoc::REG },
|
||||
{ MIPS_REG_A0, R25, MIPSLoc::REG },
|
||||
{ MIPS_REG_A1, R26, MIPSLoc::REG },
|
||||
{ MIPS_REG_RA, R27, MIPSLoc::REG },
|
||||
};
|
||||
|
||||
if (jo_->useStaticAlloc) {
|
||||
count = ARRAY_SIZE(allocs);
|
||||
return allocs;
|
||||
}
|
||||
return IRNativeRegCacheBase::GetStaticAllocations(count);
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::EmitLoadStaticRegisters() {
|
||||
int count;
|
||||
const StaticAllocation *allocs = GetStaticAllocations(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int offset = GetMipsRegOffset(allocs[i].mr);
|
||||
if (allocs[i].pointerified && jo_->enablePointerify) {
|
||||
emit_->LD_WU((LoongArch64Reg)allocs[i].nr, CTXREG, offset);
|
||||
emit_->ADD_D((LoongArch64Reg)allocs[i].nr, (LoongArch64Reg)allocs[i].nr, MEMBASEREG);
|
||||
} else {
|
||||
emit_->LD_W((LoongArch64Reg)allocs[i].nr, CTXREG, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::EmitSaveStaticRegisters() {
|
||||
int count;
|
||||
const StaticAllocation *allocs = GetStaticAllocations(count);
|
||||
// This only needs to run once (by Asm) so checks don't need to be fast.
|
||||
for (int i = 0; i < count; i++) {
|
||||
int offset = GetMipsRegOffset(allocs[i].mr);
|
||||
emit_->ST_W((LoongArch64Reg)allocs[i].nr, CTXREG, offset);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::FlushBeforeCall() {
|
||||
// These registers are not preserved by function calls.
|
||||
// They match between R0 and F0, conveniently.
|
||||
for (int i = 4; i <= 20; ++i) {
|
||||
FlushNativeReg(R0 + i);
|
||||
}
|
||||
for (int i = 0; i <= 23; ++i) {
|
||||
FlushNativeReg(F0 + i);
|
||||
}
|
||||
}
|
||||
|
||||
bool LoongArch64RegCache::IsNormalized32(IRReg mipsReg) {
|
||||
_dbg_assert_(IsValidGPR(mipsReg));
|
||||
if (mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM) {
|
||||
return nr[mr[mipsReg].nReg].normalized32;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LoongArch64Gen::LoongArch64Reg LoongArch64RegCache::Normalize32(IRReg mipsReg, LoongArch64Gen::LoongArch64Reg destReg) {
|
||||
_dbg_assert_(IsValidGPR(mipsReg));
|
||||
_dbg_assert_(destReg == INVALID_REG || (destReg > R0 && destReg <= R31));
|
||||
|
||||
LoongArch64Reg reg = (LoongArch64Reg)mr[mipsReg].nReg;
|
||||
|
||||
switch (mr[mipsReg].loc) {
|
||||
case MIPSLoc::IMM:
|
||||
case MIPSLoc::MEM:
|
||||
_assert_msg_(false, "Cannot normalize an imm or mem");
|
||||
return INVALID_REG;
|
||||
|
||||
case MIPSLoc::REG:
|
||||
case MIPSLoc::REG_IMM:
|
||||
if (!nr[mr[mipsReg].nReg].normalized32) {
|
||||
if (destReg == INVALID_REG) {
|
||||
emit_->ADDI_W((LoongArch64Reg)mr[mipsReg].nReg, (LoongArch64Reg)mr[mipsReg].nReg, 0);
|
||||
nr[mr[mipsReg].nReg].normalized32 = true;
|
||||
nr[mr[mipsReg].nReg].pointerified = false;
|
||||
} else {
|
||||
emit_->ADDI_W(destReg, (LoongArch64Reg)mr[mipsReg].nReg, 0);
|
||||
}
|
||||
} else if (destReg != INVALID_REG) {
|
||||
emit_->ADDI_W(destReg, (LoongArch64Reg)mr[mipsReg].nReg, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case MIPSLoc::REG_AS_PTR:
|
||||
_dbg_assert_(nr[mr[mipsReg].nReg].normalized32 == false);
|
||||
if (destReg == INVALID_REG) {
|
||||
// If we can pointerify, ADDI_W will be enough.
|
||||
if (!jo_->enablePointerify)
|
||||
AdjustNativeRegAsPtr(mr[mipsReg].nReg, false);
|
||||
emit_->ADDI_W((LoongArch64Reg)mr[mipsReg].nReg, (LoongArch64Reg)mr[mipsReg].nReg, 0);
|
||||
mr[mipsReg].loc = MIPSLoc::REG;
|
||||
nr[mr[mipsReg].nReg].normalized32 = true;
|
||||
nr[mr[mipsReg].nReg].pointerified = false;
|
||||
} else if (!jo_->enablePointerify) {
|
||||
emit_->SUB_D(destReg, (LoongArch64Reg)mr[mipsReg].nReg, MEMBASEREG);
|
||||
emit_->ADDI_W(destReg, destReg, 0);
|
||||
} else {
|
||||
emit_->ADDI_W(destReg, (LoongArch64Reg)mr[mipsReg].nReg, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
_assert_msg_(false, "Should not normalize32 floats");
|
||||
break;
|
||||
}
|
||||
|
||||
return destReg == INVALID_REG ? reg : destReg;
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::TryMapTempImm(IRReg r) {
|
||||
_dbg_assert_(IsValidGPR(r));
|
||||
// If already mapped, no need for a temporary.
|
||||
if (IsGPRMapped(r)) {
|
||||
return R(r);
|
||||
}
|
||||
|
||||
if (mr[r].loc == MIPSLoc::IMM) {
|
||||
if (mr[r].imm == 0) {
|
||||
return R_ZERO;
|
||||
}
|
||||
|
||||
// Try our luck - check for an exact match in another LoongArch reg.
|
||||
for (int i = 0; i < TOTAL_MAPPABLE_IRREGS; ++i) {
|
||||
if (mr[i].loc == MIPSLoc::REG_IMM && mr[i].imm == mr[r].imm) {
|
||||
// Awesome, let's just use this reg.
|
||||
return (LoongArch64Reg)mr[i].nReg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_REG;
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::GetAndLockTempGPR() {
|
||||
LoongArch64Reg reg = (LoongArch64Reg)AllocateReg(MIPSLoc::REG, MIPSMap::INIT);
|
||||
if (reg != INVALID_REG) {
|
||||
nr[reg].tempLockIRIndex = irIndex_;
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::MapWithFPRTemp(const IRInst &inst) {
|
||||
return (LoongArch64Reg)MapWithTemp(inst, MIPSLoc::FREG);
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::MapGPR(IRReg mipsReg, MIPSMap mapFlags) {
|
||||
_dbg_assert_(IsValidGPR(mipsReg));
|
||||
|
||||
// Okay, not mapped, so we need to allocate an LA register.
|
||||
IRNativeReg nreg = MapNativeReg(MIPSLoc::REG, mipsReg, 1, mapFlags);
|
||||
return (LoongArch64Reg)nreg;
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::MapGPRAsPointer(IRReg reg) {
|
||||
return (LoongArch64Reg)MapNativeRegAsPointer(reg);
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::MapFPR(IRReg mipsReg, MIPSMap mapFlags) {
|
||||
_dbg_assert_(IsValidFPR(mipsReg));
|
||||
_dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::MEM || mr[mipsReg + 32].loc == MIPSLoc::FREG);
|
||||
|
||||
IRNativeReg nreg = MapNativeReg(MIPSLoc::FREG, mipsReg + 32, 1, mapFlags);
|
||||
if (nreg != -1)
|
||||
return (LoongArch64Reg)nreg;
|
||||
return INVALID_REG;
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::MapVec4(IRReg first, MIPSMap mapFlags) {
|
||||
_dbg_assert_(IsValidFPR(first));
|
||||
_dbg_assert_((first & 3) == 0);
|
||||
_dbg_assert_(mr[first + 32].loc == MIPSLoc::MEM || mr[first + 32].loc == MIPSLoc::FREG);
|
||||
|
||||
IRNativeReg nreg = MapNativeReg(MIPSLoc::FREG, first + 32, 4, mapFlags);
|
||||
if (nreg != -1)
|
||||
return EncodeRegToV((LoongArch64Reg)nreg);
|
||||
return INVALID_REG;
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) {
|
||||
LoongArch64Reg r = (LoongArch64Reg)(R0 + nreg);
|
||||
_assert_(r >= R0 && r <= R31);
|
||||
if (state) {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
// This destroys the value...
|
||||
_dbg_assert_(!nr[nreg].isDirty);
|
||||
emit_->SLLI_W(r, r, 2);
|
||||
emit_->SRLI_W(r, r, 2);
|
||||
emit_->ADD_D(r, r, MEMBASEREG);
|
||||
#else
|
||||
// Clear the top bits to be safe.
|
||||
emit_->SLLI_D(r, r, 32);
|
||||
emit_->SRLI_D(r, r, 32);
|
||||
emit_->ADD_D(r, r, MEMBASEREG);
|
||||
#endif
|
||||
nr[nreg].normalized32 = false;
|
||||
} else {
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
_dbg_assert_(!nr[nreg].isDirty);
|
||||
#endif
|
||||
emit_->SUB_D(r, r, MEMBASEREG);
|
||||
nr[nreg].normalized32 = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool LoongArch64RegCache::IsNativeRegCompatible(IRNativeReg nreg, MIPSLoc type, MIPSMap flags, int lanes) {
|
||||
// No special flags except VREG, skip the check for a little speed.
|
||||
if (type != MIPSLoc::VREG)
|
||||
return true;
|
||||
return IRNativeRegCacheBase::IsNativeRegCompatible(nreg, type, flags, lanes);
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) {
|
||||
LoongArch64Reg r = (LoongArch64Reg)(R0 + nreg);
|
||||
_dbg_assert_(r > R0);
|
||||
_dbg_assert_(first != MIPS_REG_ZERO);
|
||||
if (r <= R31) {
|
||||
_assert_(lanes == 1 || (lanes == 2 && first == IRREG_LO));
|
||||
if (lanes == 1)
|
||||
emit_->LD_W(r, CTXREG, GetMipsRegOffset(first));
|
||||
else if (lanes == 2)
|
||||
emit_->LD_D(r, CTXREG, GetMipsRegOffset(first));
|
||||
else
|
||||
_assert_(false);
|
||||
nr[nreg].normalized32 = true;
|
||||
} else {
|
||||
_dbg_assert_(r >= F0 && r <= F31);
|
||||
_assert_msg_(mr[first].loc == MIPSLoc::FREG, "Cannot store this type: %d", (int)mr[first].loc);
|
||||
if (lanes == 1)
|
||||
emit_->FLD_S(r, CTXREG, GetMipsRegOffset(first));
|
||||
else if (lanes == 2)
|
||||
emit_->FLD_D(r, CTXREG, GetMipsRegOffset(first));
|
||||
else if (lanes == 4)
|
||||
emit_->VLD(EncodeRegToV(r), CTXREG, GetMipsRegOffset(first));
|
||||
else
|
||||
_assert_(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) {
|
||||
LoongArch64Reg r = (LoongArch64Reg)(R0 + nreg);
|
||||
_dbg_assert_(r > R0);
|
||||
_dbg_assert_(first != MIPS_REG_ZERO);
|
||||
if (r <= R31) {
|
||||
_assert_(lanes == 1 || (lanes == 2 && first == IRREG_LO));
|
||||
_assert_(mr[first].loc == MIPSLoc::REG || mr[first].loc == MIPSLoc::REG_IMM);
|
||||
if (lanes == 1)
|
||||
emit_->ST_W(r, CTXREG, GetMipsRegOffset(first));
|
||||
else if (lanes == 2)
|
||||
emit_->ST_D(r, CTXREG, GetMipsRegOffset(first));
|
||||
else
|
||||
_assert_(false);
|
||||
} else {
|
||||
_dbg_assert_(r >= F0 && r <= F31);
|
||||
_assert_msg_(mr[first].loc == MIPSLoc::FREG, "Cannot store this type: %d", (int)mr[first].loc);
|
||||
if (lanes == 1)
|
||||
emit_->FST_S(r, CTXREG, GetMipsRegOffset(first));
|
||||
else if (lanes == 2)
|
||||
emit_->FST_D(r, CTXREG, GetMipsRegOffset(first));
|
||||
else if (lanes == 4)
|
||||
emit_->VST(EncodeRegToV(r), CTXREG, GetMipsRegOffset(first));
|
||||
else
|
||||
_assert_(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::SetNativeRegValue(IRNativeReg nreg, uint32_t imm) {
|
||||
LoongArch64Reg r = (LoongArch64Reg)(R0 + nreg);
|
||||
if (r == R_ZERO && imm == 0)
|
||||
return;
|
||||
_dbg_assert_(r > R0 && r <= R31);
|
||||
emit_->LI(r, (int32_t)imm);
|
||||
|
||||
// We always use 32-bit immediates, so this is normalized now.
|
||||
nr[nreg].normalized32 = true;
|
||||
}
|
||||
|
||||
void LoongArch64RegCache::StoreRegValue(IRReg mreg, uint32_t imm) {
|
||||
_assert_(IsValidGPRNoZero(mreg));
|
||||
// Try to optimize using a different reg.
|
||||
LoongArch64Reg storeReg = INVALID_REG;
|
||||
|
||||
// Zero is super easy.
|
||||
if (imm == 0) {
|
||||
storeReg = R_ZERO;
|
||||
} else {
|
||||
// Could we get lucky? Check for an exact match in another lareg.
|
||||
for (int i = 0; i < TOTAL_MAPPABLE_IRREGS; ++i) {
|
||||
if (mr[i].loc == MIPSLoc::REG_IMM && mr[i].imm == imm) {
|
||||
// Awesome, let's just store this reg.
|
||||
storeReg = (LoongArch64Reg)mr[i].nReg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (storeReg == INVALID_REG) {
|
||||
emit_->LI(SCRATCH1, imm);
|
||||
storeReg = SCRATCH1;
|
||||
}
|
||||
}
|
||||
|
||||
emit_->ST_W(storeReg, CTXREG, GetMipsRegOffset(mreg));
|
||||
}
|
||||
|
||||
bool LoongArch64RegCache::TransferNativeReg(IRNativeReg nreg, IRNativeReg dest, MIPSLoc type, IRReg first, int lanes, MIPSMap flags) {
|
||||
bool allowed = !mr[nr[nreg].mipsReg].isStatic;
|
||||
// There's currently no support for non-FREGs here.
|
||||
allowed = allowed && type == MIPSLoc::FREG;
|
||||
|
||||
if (dest == -1)
|
||||
dest = nreg;
|
||||
|
||||
if (allowed && (flags == MIPSMap::INIT || flags == MIPSMap::DIRTY)) {
|
||||
// Alright, changing lane count (possibly including lane position.)
|
||||
IRReg oldfirst = nr[nreg].mipsReg;
|
||||
int oldlanes = 0;
|
||||
while (mr[oldfirst + oldlanes].nReg == nreg)
|
||||
oldlanes++;
|
||||
_assert_msg_(oldlanes != 0, "TransferNativeReg encountered nreg mismatch");
|
||||
_assert_msg_(oldlanes != lanes, "TransferNativeReg transfer to same lanecount, misaligned?");
|
||||
|
||||
if (lanes == 1 && TransferVecTo1(nreg, dest, first, oldlanes))
|
||||
return true;
|
||||
if (oldlanes == 1 && Transfer1ToVec(nreg, dest, first, lanes))
|
||||
return true;
|
||||
}
|
||||
|
||||
return IRNativeRegCacheBase::TransferNativeReg(nreg, dest, type, first, lanes, flags);
|
||||
}
|
||||
|
||||
bool LoongArch64RegCache::TransferVecTo1(IRNativeReg nreg, IRNativeReg dest, IRReg first, int oldlanes) {
|
||||
IRReg oldfirst = nr[nreg].mipsReg;
|
||||
|
||||
// Is it worth preserving any of the old regs?
|
||||
int numKept = 0;
|
||||
for (int i = 0; i < oldlanes; ++i) {
|
||||
// Skip whichever one this is extracting.
|
||||
if (oldfirst + i == first)
|
||||
continue;
|
||||
// If 0 isn't being transfered, easy to keep in its original reg.
|
||||
if (i == 0 && dest != nreg) {
|
||||
numKept++;
|
||||
continue;
|
||||
}
|
||||
|
||||
IRNativeReg freeReg = FindFreeReg(MIPSLoc::FREG, MIPSMap::INIT);
|
||||
if (freeReg != -1 && IsRegRead(MIPSLoc::FREG, oldfirst + i)) {
|
||||
// If there's one free, use it. Don't modify nreg, though.
|
||||
emit_->VREPLVEI_W(FromNativeReg(freeReg), FromNativeReg(nreg), i);
|
||||
|
||||
// Update accounting.
|
||||
nr[freeReg].isDirty = nr[nreg].isDirty;
|
||||
nr[freeReg].mipsReg = oldfirst + i;
|
||||
mr[oldfirst + i].lane = -1;
|
||||
mr[oldfirst + i].nReg = freeReg;
|
||||
numKept++;
|
||||
}
|
||||
}
|
||||
|
||||
// Unless all other lanes were kept, store.
|
||||
if (nr[nreg].isDirty && numKept < oldlanes - 1) {
|
||||
StoreNativeReg(nreg, oldfirst, oldlanes);
|
||||
// Set false even for regs that were split out, since they were flushed too.
|
||||
for (int i = 0; i < oldlanes; ++i) {
|
||||
if (mr[oldfirst + i].nReg != -1)
|
||||
nr[mr[oldfirst + i].nReg].isDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Next, shuffle the desired element into first place.
|
||||
if (mr[first].lane > 0) {
|
||||
emit_->VREPLVEI_W(FromNativeReg(dest), FromNativeReg(nreg), mr[first].lane);
|
||||
} else if (mr[first].lane <= 0 && dest != nreg) {
|
||||
emit_->VREPLVEI_W(FromNativeReg(dest), FromNativeReg(nreg), 0);
|
||||
}
|
||||
|
||||
// Now update accounting.
|
||||
for (int i = 0; i < oldlanes; ++i) {
|
||||
auto &mreg = mr[oldfirst + i];
|
||||
if (oldfirst + i == first) {
|
||||
mreg.lane = -1;
|
||||
mreg.nReg = dest;
|
||||
} else if (mreg.nReg == nreg && i == 0 && nreg != dest) {
|
||||
// Still in the same register, but no longer a vec.
|
||||
mreg.lane = -1;
|
||||
} else if (mreg.nReg == nreg) {
|
||||
// No longer in a register.
|
||||
mreg.nReg = -1;
|
||||
mreg.lane = -1;
|
||||
mreg.loc = MIPSLoc::MEM;
|
||||
}
|
||||
}
|
||||
|
||||
if (dest != nreg) {
|
||||
nr[dest].isDirty = nr[nreg].isDirty;
|
||||
if (oldfirst == first) {
|
||||
nr[nreg].mipsReg = -1;
|
||||
nr[nreg].isDirty = false;
|
||||
}
|
||||
}
|
||||
nr[dest].mipsReg = first;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoongArch64RegCache::Transfer1ToVec(IRNativeReg nreg, IRNativeReg dest, IRReg first, int lanes) {
|
||||
LoongArch64Reg destReg = FromNativeReg(dest);
|
||||
LoongArch64Reg cur[4]{};
|
||||
int numInRegs = 0;
|
||||
u8 blendMask = 0;
|
||||
for (int i = 0; i < lanes; ++i) {
|
||||
if (mr[first + i].lane != -1 || (i != 0 && mr[first + i].spillLockIRIndex >= irIndex_)) {
|
||||
// Can't do it, either double mapped or overlapping vec.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mr[first + i].nReg == -1) {
|
||||
cur[i] = INVALID_REG;
|
||||
blendMask |= 1 << i;
|
||||
} else {
|
||||
cur[i] = FromNativeReg(mr[first + i].nReg);
|
||||
numInRegs++;
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't happen, this should only get called to transfer one in a reg.
|
||||
if (numInRegs == 0)
|
||||
return false;
|
||||
|
||||
// If everything's currently in a reg, move it into this reg.
|
||||
if (lanes == 4) {
|
||||
// Go with an exhaustive approach, only 15 possibilities...
|
||||
if (blendMask == 0) {
|
||||
// y = yw##, x = xz##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[3]), EncodeRegToV(cur[1]));
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[2]), EncodeRegToV(cur[0]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), EncodeRegToV(cur[0]));
|
||||
} else if (blendMask == 0b0001) {
|
||||
// y = yw##, w = x###, w = xz##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[3]), EncodeRegToV(cur[1]));
|
||||
emit_->FLD_S( SCRATCHF1, CTXREG, GetMipsRegOffset(first + 0));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(cur[3]), EncodeRegToV(SCRATCHF1), 0);
|
||||
emit_->VILVL_W(EncodeRegToV(cur[3]), EncodeRegToV(cur[2]), EncodeRegToV(cur[3]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), EncodeRegToV(cur[3]));
|
||||
} else if (blendMask == 0b0010) {
|
||||
// x = xz##, z = y###, z = yw##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[2]), EncodeRegToV(cur[0]));
|
||||
emit_->FLD_S( SCRATCHF1, CTXREG, GetMipsRegOffset(first + 1));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(cur[2]), EncodeRegToV(SCRATCHF1), 0);
|
||||
emit_->VILVL_W(EncodeRegToV(cur[2]), EncodeRegToV(cur[3]), EncodeRegToV(cur[2]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), EncodeRegToV(cur[0]));
|
||||
} else if (blendMask == 0b0011 && (first & 1) == 0) {
|
||||
// z = zw##, w = xy##, dest = xyzw. Mixed lane sizes.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[2]), EncodeRegToV(cur[3]), EncodeRegToV(cur[2]));
|
||||
emit_->FLD_D( SCRATCHF1, CTXREG, GetMipsRegOffset(first + 0));
|
||||
emit_->VEXTRINS_D(EncodeRegToV(cur[3]), EncodeRegToV(SCRATCHF1), 0);
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), EncodeRegToV(cur[3]));
|
||||
} else if (blendMask == 0b0100) {
|
||||
// y = yw##, w = z###, x = xz##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[3]), EncodeRegToV(cur[1]));
|
||||
emit_->FLD_S( SCRATCHF1, CTXREG, GetMipsRegOffset(first + 2));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(cur[3]), EncodeRegToV(SCRATCHF1), 0);
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[3]), EncodeRegToV(cur[0]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), EncodeRegToV(cur[0]));
|
||||
} else if (blendMask == 0b0101 && (first & 3) == 0) {
|
||||
// y = yw##, w=x#z#, w = xz##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[3]), EncodeRegToV(cur[1]));
|
||||
emit_->VLD(EncodeRegToV(cur[3]), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VPICKEV_W(EncodeRegToV(cur[3]), EncodeRegToV(cur[3]), EncodeRegToV(cur[3]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), EncodeRegToV(cur[3]));
|
||||
} else if (blendMask == 0b0110 && (first & 3) == 0) {
|
||||
if (destReg == cur[0]) {
|
||||
// w = wx##, dest = #yz#, dest = xyz#, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[3]), EncodeRegToV(cur[0]), EncodeRegToV(cur[3]));
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[3]), 1);
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[3]), (3 << 4));
|
||||
} else {
|
||||
// Assumes destReg may equal cur[3].
|
||||
// x = xw##, dest = #yz#, dest = xyz#, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[3]), EncodeRegToV(cur[0]));
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[0]), 0);
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[0]), (3 << 4 | 1));
|
||||
}
|
||||
} else if (blendMask == 0b0111 && (first & 3) == 0 && destReg != cur[3]) {
|
||||
// dest = xyz#, dest = xyzw.
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[3]), (3 << 4));
|
||||
} else if (blendMask == 0b1000) {
|
||||
// x = xz##, z = w###, y = yw##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[2]), EncodeRegToV(cur[0]));
|
||||
emit_->FLD_S(SCRATCHF1, CTXREG, GetMipsRegOffset(first + 3));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(cur[2]), EncodeRegToV(SCRATCHF1), 0);
|
||||
emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[2]), EncodeRegToV(cur[1]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), EncodeRegToV(cur[0]));
|
||||
} else if (blendMask == 0b1001 && (first & 3) == 0) {
|
||||
if (destReg == cur[1]) {
|
||||
// w = zy##, dest = x##w, dest = xy#w, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[2]), EncodeRegToV(cur[1]), EncodeRegToV(cur[2]));
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), (1 << 4 | 1));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), (2 << 4));
|
||||
} else {
|
||||
// Assumes destReg may equal cur[2].
|
||||
// y = yz##, dest = x##w, dest = xy#w, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[1]), EncodeRegToV(cur[2]), EncodeRegToV(cur[1]));
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), (1 << 4));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), (2 << 4 | 1));
|
||||
}
|
||||
} else if (blendMask == 0b1010 && (first & 3) == 0) {
|
||||
// x = xz##, z = #y#w, z=yw##, dest = xyzw.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[2]), EncodeRegToV(cur[0]));
|
||||
emit_->VLD(EncodeRegToV(cur[2]), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VPICKOD_W(EncodeRegToV(cur[2]), EncodeRegToV(cur[2]), EncodeRegToV(cur[2]));
|
||||
emit_->VILVL_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), EncodeRegToV(cur[0]));
|
||||
} else if (blendMask == 0b1011 && (first & 3) == 0 && destReg != cur[2]) {
|
||||
// dest = xy#w, dest = xyzw.
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[2]), (2 << 4));
|
||||
} else if (blendMask == 0b1100 && (first & 1) == 0) {
|
||||
// x = xy##, y = zw##, dest = xyzw. Mixed lane sizes.
|
||||
emit_->VILVL_W(EncodeRegToV(cur[0]), EncodeRegToV(cur[1]), EncodeRegToV(cur[0]));
|
||||
emit_->FLD_D(SCRATCHF1, CTXREG, GetMipsRegOffset(first + 2));
|
||||
emit_->VEXTRINS_D(EncodeRegToV(cur[1]), EncodeRegToV(SCRATCHF1), 0);
|
||||
emit_->VILVL_D(EncodeRegToV(destReg), EncodeRegToV(cur[1]), EncodeRegToV(cur[0]));
|
||||
} else if (blendMask == 0b1101 && (first & 3) == 0 && destReg != cur[1]) {
|
||||
// dest = x#zw, dest = xyzw.
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[1]), (1 << 4));
|
||||
} else if (blendMask == 0b1110 && (first & 3) == 0 && destReg != cur[0]) {
|
||||
// dest = #yzw, dest = xyzw.
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(cur[0]), 0);
|
||||
} else if (blendMask == 0b1110 && (first & 3) == 0) {
|
||||
// If dest == cur[0] (which may be common), we need a temp...
|
||||
IRNativeReg freeReg = FindFreeReg(MIPSLoc::FREG, MIPSMap::INIT);
|
||||
// Very unfortunate.
|
||||
if (freeReg == INVALID_REG)
|
||||
return false;
|
||||
|
||||
// free = x###, dest = #yzw, dest = xyzw.
|
||||
emit_->VREPLVEI_W(EncodeRegToV(FromNativeReg(freeReg)), EncodeRegToV(cur[0]), 0);
|
||||
emit_->VLD(EncodeRegToV(destReg), CTXREG, GetMipsRegOffset(first));
|
||||
emit_->VEXTRINS_W(EncodeRegToV(destReg), EncodeRegToV(FromNativeReg(freeReg)), 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
mr[first].lane = 0;
|
||||
for (int i = 0; i < lanes; ++i) {
|
||||
if (mr[first + i].nReg != -1) {
|
||||
// If this was dirty, the combined reg is now dirty.
|
||||
if (nr[mr[first + i].nReg].isDirty)
|
||||
nr[dest].isDirty = true;
|
||||
|
||||
// Throw away the other register we're no longer using.
|
||||
if (i != 0)
|
||||
DiscardNativeReg(mr[first + i].nReg);
|
||||
}
|
||||
|
||||
// And set it as using the new one.
|
||||
mr[first + i].lane = i;
|
||||
mr[first + i].loc = MIPSLoc::FREG;
|
||||
mr[first + i].nReg = dest;
|
||||
}
|
||||
|
||||
if (dest != nreg) {
|
||||
nr[dest].mipsReg = first;
|
||||
nr[nreg].mipsReg = -1;
|
||||
nr[nreg].isDirty = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::R(IRReg mipsReg) {
|
||||
_dbg_assert_(IsValidGPR(mipsReg));
|
||||
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM);
|
||||
if (mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM) {
|
||||
return (LoongArch64Reg)mr[mipsReg].nReg;
|
||||
} else {
|
||||
ERROR_LOG_REPORT(Log::JIT, "Reg %i not in LoongArch64 reg", mipsReg);
|
||||
return INVALID_REG; // BAAAD
|
||||
}
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::RPtr(IRReg mipsReg) {
|
||||
_dbg_assert_(IsValidGPR(mipsReg));
|
||||
_dbg_assert_(mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM || mr[mipsReg].loc == MIPSLoc::REG_AS_PTR);
|
||||
if (mr[mipsReg].loc == MIPSLoc::REG_AS_PTR) {
|
||||
return (LoongArch64Reg)mr[mipsReg].nReg;
|
||||
} else if (mr[mipsReg].loc == MIPSLoc::REG || mr[mipsReg].loc == MIPSLoc::REG_IMM) {
|
||||
int la = mr[mipsReg].nReg;
|
||||
_dbg_assert_(nr[la].pointerified);
|
||||
if (nr[la].pointerified) {
|
||||
return (LoongArch64Reg)mr[mipsReg].nReg;
|
||||
} else {
|
||||
ERROR_LOG(Log::JIT, "Tried to use a non-pointer register as a pointer");
|
||||
return INVALID_REG;
|
||||
}
|
||||
} else {
|
||||
ERROR_LOG_REPORT(Log::JIT, "Reg %i not in LoongArch64 reg", mipsReg);
|
||||
return INVALID_REG; // BAAAD
|
||||
}
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::F(IRReg mipsReg) {
|
||||
_dbg_assert_(IsValidFPR(mipsReg));
|
||||
_dbg_assert_(mr[mipsReg + 32].loc == MIPSLoc::FREG);
|
||||
if (mr[mipsReg + 32].loc == MIPSLoc::FREG) {
|
||||
return (LoongArch64Reg)mr[mipsReg + 32].nReg;
|
||||
} else {
|
||||
ERROR_LOG_REPORT(Log::JIT, "Reg %i not in LoongArch64 reg", mipsReg);
|
||||
return INVALID_REG; // BAAAD
|
||||
}
|
||||
}
|
||||
|
||||
LoongArch64Reg LoongArch64RegCache::V(IRReg mipsReg) {
|
||||
return EncodeRegToV(F(mipsReg));
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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/LoongArch64Emitter.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/IR/IRJit.h"
|
||||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
|
||||
namespace LoongArch64JitConstants {
|
||||
|
||||
const LoongArch64Gen::LoongArch64Reg DOWNCOUNTREG = LoongArch64Gen::R28;
|
||||
const LoongArch64Gen::LoongArch64Reg JITBASEREG = LoongArch64Gen::R29;
|
||||
const LoongArch64Gen::LoongArch64Reg CTXREG = LoongArch64Gen::R30;
|
||||
const LoongArch64Gen::LoongArch64Reg MEMBASEREG = LoongArch64Gen::R31;
|
||||
const LoongArch64Gen::LoongArch64Reg SCRATCH1 = LoongArch64Gen::R12;
|
||||
const LoongArch64Gen::LoongArch64Reg SCRATCH2 = LoongArch64Gen::R13;
|
||||
const LoongArch64Gen::LoongArch64Reg SCRATCHF1 = LoongArch64Gen::F8;
|
||||
const LoongArch64Gen::LoongArch64Reg SCRATCHF2 = LoongArch64Gen::F9;
|
||||
|
||||
} // namespace LoongArch64JitConstants
|
||||
|
||||
class LoongArch64RegCache : public IRNativeRegCacheBase {
|
||||
public:
|
||||
LoongArch64RegCache(MIPSComp::JitOptions *jo);
|
||||
|
||||
void Init(LoongArch64Gen::LoongArch64Emitter *emitter);
|
||||
|
||||
// May fail and return INVALID_REG if it needs flushing.
|
||||
LoongArch64Gen::LoongArch64Reg TryMapTempImm(IRReg reg);
|
||||
|
||||
// Returns an LA register containing the requested MIPS register.
|
||||
LoongArch64Gen::LoongArch64Reg MapGPR(IRReg reg, MIPSMap mapFlags = MIPSMap::INIT);
|
||||
LoongArch64Gen::LoongArch64Reg MapGPRAsPointer(IRReg reg);
|
||||
LoongArch64Gen::LoongArch64Reg MapFPR(IRReg reg, MIPSMap mapFlags = MIPSMap::INIT);
|
||||
LoongArch64Gen::LoongArch64Reg MapVec4(IRReg first, MIPSMap mapFlags = MIPSMap::INIT);
|
||||
|
||||
LoongArch64Gen::LoongArch64Reg MapWithFPRTemp(const IRInst &inst);
|
||||
|
||||
bool IsNormalized32(IRReg reg);
|
||||
|
||||
// Copies to another reg if specified, otherwise same reg.
|
||||
LoongArch64Gen::LoongArch64Reg Normalize32(IRReg reg, LoongArch64Gen::LoongArch64Reg destReg = LoongArch64Gen::INVALID_REG);
|
||||
|
||||
void FlushBeforeCall();
|
||||
|
||||
LoongArch64Gen::LoongArch64Reg GetAndLockTempGPR();
|
||||
|
||||
LoongArch64Gen::LoongArch64Reg R(IRReg preg); // Returns a cached register, while checking that it's NOT mapped as a pointer
|
||||
LoongArch64Gen::LoongArch64Reg RPtr(IRReg preg); // Returns a cached register, if it has been mapped as a pointer
|
||||
LoongArch64Gen::LoongArch64Reg F(IRReg preg);
|
||||
LoongArch64Gen::LoongArch64Reg V(IRReg preg);
|
||||
|
||||
// These are called once on startup to generate functions, that you should then call.
|
||||
void EmitLoadStaticRegisters();
|
||||
void EmitSaveStaticRegisters();
|
||||
|
||||
protected:
|
||||
void SetupInitialRegs() override;
|
||||
const StaticAllocation *GetStaticAllocations(int &count) const override;
|
||||
const int *GetAllocationOrder(MIPSLoc type, MIPSMap flags, int &count, int &base) const override;
|
||||
void AdjustNativeRegAsPtr(IRNativeReg nreg, bool state) override;
|
||||
|
||||
bool IsNativeRegCompatible(IRNativeReg nreg, MIPSLoc type, MIPSMap flags, int lanes) override;
|
||||
void LoadNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
|
||||
void StoreNativeReg(IRNativeReg nreg, IRReg first, int lanes) override;
|
||||
void SetNativeRegValue(IRNativeReg nreg, uint32_t imm) override;
|
||||
void StoreRegValue(IRReg mreg, uint32_t imm) override;
|
||||
bool TransferNativeReg(IRNativeReg nreg, IRNativeReg dest, MIPSLoc type, IRReg first, int lanes, MIPSMap flags) override;
|
||||
|
||||
private:
|
||||
bool TransferVecTo1(IRNativeReg nreg, IRNativeReg dest, IRReg first, int oldlanes);
|
||||
bool Transfer1ToVec(IRNativeReg nreg, IRNativeReg dest, IRReg first, int lanes);
|
||||
|
||||
LoongArch64Gen::LoongArch64Reg FromNativeReg(IRNativeReg r) {
|
||||
if (r >= NUM_LAGPR)
|
||||
return (LoongArch64Gen::LoongArch64Reg)(LoongArch64Gen::V0 + (r - NUM_LAGPR));
|
||||
return (LoongArch64Gen::LoongArch64Reg)(LoongArch64Gen::R0 + r);
|
||||
}
|
||||
|
||||
LoongArch64Gen::LoongArch64Emitter *emit_ = nullptr;
|
||||
|
||||
enum {
|
||||
NUM_LAGPR = 32,
|
||||
NUM_LAFPR = 32,
|
||||
};
|
||||
};
|
||||
@@ -98,6 +98,12 @@ static bool DisassembleNativeAt(const uint8_t *codePtr, int instructionSize, std
|
||||
*dest = lines[0];
|
||||
return true;
|
||||
}
|
||||
#elif PPSSPP_ARCH(LOONGARCH64)
|
||||
auto lines = DisassembleLA64(codePtr, instructionSize);
|
||||
if (!lines.empty()) {
|
||||
*dest = lines[0];
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,7 @@
|
||||
<ClInclude Include="..\..\ext\cityhash\citycrc.h" />
|
||||
<ClInclude Include="..\..\ext\disarm.h" />
|
||||
<ClInclude Include="..\..\ext\riscv-disas.h" />
|
||||
<ClInclude Include="..\..\ext\loongarch-disasm.h" />
|
||||
<ClInclude Include="..\..\ext\gason\gason.h" />
|
||||
<ClInclude Include="..\..\ext\jpge\jpgd.h" />
|
||||
<ClInclude Include="..\..\ext\jpge\jpge.h" />
|
||||
@@ -628,6 +629,7 @@
|
||||
<ClCompile Include="..\..\ext\cityhash\city.cpp" />
|
||||
<ClCompile Include="..\..\ext\disarm.cpp" />
|
||||
<ClCompile Include="..\..\ext\riscv-disas.cpp" />
|
||||
<ClCompile Include="..\..\ext\loongarch-disasm.cpp" />
|
||||
<ClCompile Include="..\..\ext\gason\gason.cpp" />
|
||||
<ClCompile Include="..\..\ext\jpge\jpgd.cpp" />
|
||||
<ClCompile Include="..\..\ext\jpge\jpge.cpp" />
|
||||
|
||||
@@ -276,6 +276,7 @@
|
||||
<ClCompile Include="..\..\ext\cityhash\city.cpp" />
|
||||
<ClCompile Include="..\..\ext\disarm.cpp" />
|
||||
<ClCompile Include="..\..\ext\riscv-disas.cpp" />
|
||||
<ClCompile Include="..\..\ext\loongarch-disasm.cpp" />
|
||||
<ClCompile Include="..\..\ext\gason\gason.cpp" />
|
||||
<ClCompile Include="..\..\ext\jpge\jpgd.cpp" />
|
||||
<ClCompile Include="..\..\ext\jpge\jpge.cpp" />
|
||||
@@ -650,6 +651,7 @@
|
||||
<ClInclude Include="..\..\ext\cityhash\citycrc.h" />
|
||||
<ClInclude Include="..\..\ext\disarm.h" />
|
||||
<ClInclude Include="..\..\ext\riscv-disas.h" />
|
||||
<ClInclude Include="..\..\ext\loongarch-disasm.h" />
|
||||
<ClInclude Include="..\..\ext\gason\gason.h" />
|
||||
<ClInclude Include="..\..\ext\jpge\jpgd.h" />
|
||||
<ClInclude Include="..\..\ext\jpge\jpge.h" />
|
||||
|
||||
@@ -976,13 +976,16 @@ ifeq ($(UNITTEST),1)
|
||||
$(SRC)/Common/ArmEmitter.cpp \
|
||||
$(SRC)/Common/Arm64Emitter.cpp \
|
||||
$(SRC)/Common/RiscVEmitter.cpp \
|
||||
$(SRC)/Common/LoongArch64Emitter.cpp \
|
||||
$(SRC)/Core/MIPS/ARM/ArmRegCacheFPU.cpp \
|
||||
$(SRC)/Core/Util/DisArm64.cpp \
|
||||
$(SRC)/ext/disarm.cpp \
|
||||
$(SRC)/ext/riscv-disas.cpp \
|
||||
$(SRC)/ext/loongarch-disasm.cpp \
|
||||
$(SRC)/unittest/TestArmEmitter.cpp \
|
||||
$(SRC)/unittest/TestArm64Emitter.cpp \
|
||||
$(SRC)/unittest/TestRiscVEmitter.cpp \
|
||||
$(SRC)/unittest/TestLoongArch64Emitter.cpp \
|
||||
$(SRC)/unittest/TestX64Emitter.cpp
|
||||
endif
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2025- 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 <cstdio>
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/LoongArch64Emitter.h"
|
||||
#include "UnitTest.h"
|
||||
|
||||
bool TestLoongArch64Emitter(){
|
||||
using namespace LoongArch64Gen;
|
||||
|
||||
cpu_info.LOONGARCH_COMPLEX = true;
|
||||
cpu_info.LOONGARCH_LAM = true;
|
||||
cpu_info.LOONGARCH_UAL = true;
|
||||
cpu_info.LOONGARCH_FPU = true;
|
||||
cpu_info.LOONGARCH_LSX = true;
|
||||
cpu_info.LOONGARCH_LASX = true;
|
||||
cpu_info.LOONGARCH_CRC32 = true;
|
||||
cpu_info.LOONGARCH_COMPLEX = true;
|
||||
cpu_info.LOONGARCH_CRYPTO = true;
|
||||
cpu_info.LOONGARCH_LVZ = true;
|
||||
cpu_info.LOONGARCH_LBT_X86 = true;
|
||||
cpu_info.LOONGARCH_LBT_ARM = true;
|
||||
cpu_info.LOONGARCH_LBT_MIPS = true;
|
||||
cpu_info.LOONGARCH_PTW = true;
|
||||
|
||||
u32 code[1024];
|
||||
LoongArch64Emitter emitter((u8 *)code, (u8 *)code);
|
||||
|
||||
emitter.ADD_W(R12, R13, R14); // t0, t1, t2
|
||||
emitter.ADD_D(R12, R13, R14); // t0, t1, t2
|
||||
emitter.SUB_W(R12, R13, R14); // t0, t1, t2
|
||||
emitter.SUB_D(R12, R13, R14); // t0, t1, t2
|
||||
|
||||
emitter.ADDI_W(R12, R13, 1024); // t0, t1, 1024
|
||||
emitter.ADDI_D(R12, R13, 1024); // t0, t1, 1024
|
||||
emitter.ADDU16I_D(R12, R13, 16384); // t0, t1, 1024
|
||||
|
||||
emitter.ALSL_W(R12, R13, R14, 4); // t0, t1, t2, 4
|
||||
emitter.ALSL_D(R12, R13, R14, 4); // t0, t1, t2, 4
|
||||
emitter.ALSL_WU(R12, R13, R14, 4); // t0, t1, t2, 4
|
||||
|
||||
static constexpr uint32_t expected[] = {
|
||||
0x001039ac, // add.w
|
||||
0x0010b9ac, // add.d
|
||||
0x001139ac, // sub.w
|
||||
0x0011b9ac, // sub.d
|
||||
|
||||
0x029001ac, // addi.w
|
||||
0x02d001ac, // addi.d
|
||||
0x110001ac, // addiu16i.d
|
||||
|
||||
0x0005b9ac, // alsl.w
|
||||
0x002db9ac, // alsl.d
|
||||
0x0007b9ac, // alsl.wu
|
||||
};
|
||||
|
||||
ptrdiff_t len = (u32 *)emitter.GetWritableCodePtr() - code;
|
||||
EXPECT_EQ_INT(len, ARRAY_SIZE(expected));
|
||||
|
||||
for (ptrdiff_t i = 0; i < len; ++i) {
|
||||
EXPECT_EQ_HEX(code[i], expected[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1244,6 +1244,7 @@ bool TestArmEmitter();
|
||||
bool TestArm64Emitter();
|
||||
bool TestX64Emitter();
|
||||
bool TestRiscVEmitter();
|
||||
bool TestLoongArch64Emitter();
|
||||
bool TestShaderGenerators();
|
||||
bool TestSoftwareGPUJit();
|
||||
bool TestIRPassSimplify();
|
||||
@@ -1262,6 +1263,9 @@ TestItem availableTests[] = {
|
||||
#endif
|
||||
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(RISCV64)
|
||||
TEST_ITEM(RiscVEmitter),
|
||||
#endif
|
||||
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(LOONGARCH64)
|
||||
TEST_ITEM(LoongArch64Emitter),
|
||||
#endif
|
||||
TEST_ITEM(VertexJit),
|
||||
TEST_ITEM(Asin),
|
||||
|
||||
@@ -294,6 +294,7 @@
|
||||
<ClCompile Include="JitHarness.cpp" />
|
||||
<ClCompile Include="TestArm64Emitter.cpp" />
|
||||
<ClCompile Include="TestIRPassSimplify.cpp" />
|
||||
<ClCompile Include="TestLoongArch64Emitter.cpp" />
|
||||
<ClCompile Include="TestRiscVEmitter.cpp" />
|
||||
<ClCompile Include="TestShaderGenerators.cpp" />
|
||||
<ClCompile Include="TestSoftwareGPUJit.cpp" />
|
||||
@@ -347,4 +348,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -17,6 +17,7 @@
|
||||
<ClCompile Include="TestIRPassSimplify.cpp" />
|
||||
<ClCompile Include="TestRiscVEmitter.cpp" />
|
||||
<ClCompile Include="TestVFS.cpp" />
|
||||
<ClCompile Include="TestLoongArch64Emitter.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="JitHarness.h" />
|
||||
|
||||
Reference in New Issue
Block a user