Merge pull request #22563 from hrydgard/remove-legacy-arm64-x64-jits

Remove the legacy (non-IR) x86-64 and ARM64 JITs
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-14 17:08:38 +02:00
commit 400a97f0ad
46 files changed
+38 -19314

No files matched your search

-28
View File
@@ -41,19 +41,6 @@ list(APPEND CoreExtra
)
list(APPEND CoreExtra
MIPS/ARM64/Arm64Asm.cpp
MIPS/ARM64/Arm64CompALU.cpp
MIPS/ARM64/Arm64CompBranch.cpp
MIPS/ARM64/Arm64CompFPU.cpp
MIPS/ARM64/Arm64CompLoadStore.cpp
MIPS/ARM64/Arm64CompVFPU.cpp
MIPS/ARM64/Arm64CompReplace.cpp
MIPS/ARM64/Arm64Jit.cpp
MIPS/ARM64/Arm64Jit.h
MIPS/ARM64/Arm64RegCache.cpp
MIPS/ARM64/Arm64RegCache.h
MIPS/ARM64/Arm64RegCacheFPU.cpp
MIPS/ARM64/Arm64RegCacheFPU.h
MIPS/ARM64/Arm64IRAsm.cpp
MIPS/ARM64/Arm64IRCompALU.cpp
MIPS/ARM64/Arm64IRCompBranch.cpp
@@ -69,21 +56,6 @@ list(APPEND CoreExtra
)
list(APPEND CoreExtra
MIPS/x86/Asm.cpp
MIPS/x86/CompALU.cpp
MIPS/x86/CompBranch.cpp
MIPS/x86/CompFPU.cpp
MIPS/x86/CompLoadStore.cpp
MIPS/x86/CompVFPU.cpp
MIPS/x86/CompReplace.cpp
MIPS/x86/Jit.cpp
MIPS/x86/Jit.h
MIPS/x86/JitSafeMem.cpp
MIPS/x86/JitSafeMem.h
MIPS/x86/RegCache.cpp
MIPS/x86/RegCache.h
MIPS/x86/RegCacheFPU.cpp
MIPS/x86/RegCacheFPU.h
MIPS/x86/X64IRAsm.cpp
MIPS/x86/X64IRCompALU.cpp
MIPS/x86/X64IRCompBranch.cpp
+6
View File
@@ -1587,6 +1587,12 @@ bool Config::Save(const char *saveReason) {
// A lot more cleanup tasks should be moved into here, and some of these are severely outdated.
void Config::PostLoadCleanup() {
// JIT_IR is the same core as JIT now that the legacy x86 and ARM64 JITs are gone, and the
// settings no longer offer it.
if (iCpuCore == (int)CPUCore::JIT_IR) {
iCpuCore = (int)CPUCore::JIT;
}
// Override ppsspp.ini JIT value to prevent crashing
jitForcedOff = DefaultCpuCore() != (int)CPUCore::JIT && (g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR);
if (jitForcedOff) {
-164
View File
@@ -685,86 +685,6 @@
<ClCompile Include="Loaders.cpp" />
<ClCompile Include="MemMap.cpp" />
<ClCompile Include="MemmapFunctions.cpp" />
<ClCompile Include="MIPS\ARM64\Arm64Asm.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompALU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompBranch.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompFPU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompLoadStore.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompReplace.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompVFPU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64Jit.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64RegCache.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64RegCacheFPU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\ARM\ArmAsm.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -861,50 +781,6 @@
<ClCompile Include="MIPS\MIPSTables.cpp" />
<ClCompile Include="MIPS\MIPSVFPUUtils.cpp" />
<ClCompile Include="MIPS\MIPSTracer.cpp" />
<ClCompile Include="MIPS\x86\Asm.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\CompALU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\CompBranch.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\CompFPU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\CompLoadStore.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\CompReplace.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\CompVFPU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\JitSafeMem.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\RegCacheFPU.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\Jit.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="MIPS\x86\RegCache.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="PSPLoaders.cpp" />
<ClCompile Include="Reporting.cpp" />
<ClCompile Include="RetroAchievements.cpp" />
@@ -1204,30 +1080,6 @@
<ClInclude Include="Loaders.h" />
<ClInclude Include="MemMap.h" />
<ClInclude Include="MemMapHelpers.h" />
<ClInclude Include="MIPS\ARM64\Arm64Jit.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="MIPS\ARM64\Arm64RegCache.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="MIPS\ARM64\Arm64RegCacheFPU.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="MIPS\ARM\ArmJit.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -1261,22 +1113,6 @@
<ClInclude Include="MIPS\MIPSTables.h" />
<ClInclude Include="MIPS\MIPSVFPUUtils.h" />
<ClInclude Include="MIPS\MIPSTracer.h" />
<ClInclude Include="MIPS\x86\JitSafeMem.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="MIPS\x86\RegCacheFPU.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="MIPS\x86\Jit.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="MIPS\x86\RegCache.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="Opcode.h" />
<ClInclude Include="PSPLoaders.h" />
<ClInclude Include="Reporting.h" />
-84
View File
@@ -135,30 +135,6 @@
<ClCompile Include="MIPS\MIPSCodeUtils.cpp">
<Filter>MIPS</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\CompALU.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\CompBranch.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\CompFPU.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\Jit.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\CompLoadStore.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\Asm.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\RegCache.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\CompVFPU.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\JitCommon\JitCommon.cpp">
<Filter>MIPS\JitCommon</Filter>
</ClCompile>
@@ -471,9 +447,6 @@
<ClCompile Include="..\ext\disarm.cpp">
<Filter>Ext</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\RegCacheFPU.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM\ArmRegCacheFPU.cpp">
<Filter>MIPS\ARM</Filter>
</ClCompile>
@@ -571,18 +544,12 @@
<ClCompile Include="HLE\ReplaceTables.cpp">
<Filter>HLE</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\CompReplace.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM\ArmCompReplace.cpp">
<Filter>MIPS\ARM</Filter>
</ClCompile>
<ClCompile Include="HW\SimpleAudioDec.cpp">
<Filter>HW</Filter>
</ClCompile>
<ClCompile Include="MIPS\x86\JitSafeMem.cpp">
<Filter>MIPS\x86</Filter>
</ClCompile>
<ClCompile Include="HLE\HLEHelperThread.cpp">
<Filter>HLE</Filter>
</ClCompile>
@@ -625,36 +592,6 @@
<ClCompile Include="HLE\sceSfmt19937.cpp">
<Filter>HLE\Libraries</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64Asm.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompALU.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompBranch.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompFPU.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompLoadStore.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompReplace.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64CompVFPU.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64Jit.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64RegCache.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="MIPS\ARM64\Arm64RegCacheFPU.cpp">
<Filter>MIPS\ARM64</Filter>
</ClCompile>
<ClCompile Include="Util\DisArm64.cpp">
<Filter>Util</Filter>
</ClCompile>
@@ -1485,12 +1422,6 @@
<ClInclude Include="MIPS\MIPSCodeUtils.h">
<Filter>MIPS</Filter>
</ClInclude>
<ClInclude Include="MIPS\x86\Jit.h">
<Filter>MIPS\x86</Filter>
</ClInclude>
<ClInclude Include="MIPS\x86\RegCache.h">
<Filter>MIPS\x86</Filter>
</ClInclude>
<ClInclude Include="MIPS\JitCommon\JitCommon.h">
<Filter>MIPS\JitCommon</Filter>
</ClInclude>
@@ -1797,9 +1728,6 @@
<ClInclude Include="MIPS\ARM\ArmRegCache.h">
<Filter>MIPS\ARM</Filter>
</ClInclude>
<ClInclude Include="MIPS\x86\RegCacheFPU.h">
<Filter>MIPS\x86</Filter>
</ClInclude>
<ClInclude Include="MIPS\ARM\ArmRegCacheFPU.h">
<Filter>MIPS\ARM</Filter>
</ClInclude>
@@ -1908,9 +1836,6 @@
<ClInclude Include="HW\SimpleAudioDec.h">
<Filter>HW</Filter>
</ClInclude>
<ClInclude Include="MIPS\x86\JitSafeMem.h">
<Filter>MIPS\x86</Filter>
</ClInclude>
<ClInclude Include="HLE\HLEHelperThread.h">
<Filter>HLE</Filter>
</ClInclude>
@@ -1968,15 +1893,6 @@
<ClInclude Include="MemMapHelpers.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="MIPS\ARM64\Arm64Jit.h">
<Filter>MIPS\ARM64</Filter>
</ClInclude>
<ClInclude Include="MIPS\ARM64\Arm64RegCache.h">
<Filter>MIPS\ARM64</Filter>
</ClInclude>
<ClInclude Include="MIPS\ARM64\Arm64RegCacheFPU.h">
<Filter>MIPS\ARM64</Filter>
</ClInclude>
<ClInclude Include="Util\DisArm64.h">
<Filter>Util</Filter>
</ClInclude>
+1 -1
View File
@@ -1722,7 +1722,7 @@ static const ReplacementTableEntry entries[] = {
{ "strcmp", &Replace_strcmp, 0, REPFLAG_DISABLED },
{ "strncmp", &Replace_strncmp, 0, REPFLAG_DISABLED },
{ "fabsf", &Replace_fabsf, JITFUNC(Replace_fabsf), REPFLAG_DISABLED },
{ "dl_write_matrix", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED }, // &MIPSComp::Jit::Replace_dl_write_matrix, REPFLAG_DISABLED },
{ "dl_write_matrix", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED },
{ "dl_write_matrix_2", &Replace_dl_write_matrix, 0, REPFLAG_DISABLED },
{ "gta_dl_write_matrix", &Replace_gta_dl_write_matrix, 0, REPFLAG_DISABLED },
// dl_write_matrix_3 doesn't take the dl as a parameter, it accesses a global instead. Need to extract the address of the global from the code when replacing...
-331
View File
@@ -1,331 +0,0 @@
// Copyright (c) 2015- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Common/Log.h"
#include "Common/MemoryUtil.h"
#include "Common/CPUDetect.h"
#include "Common/Arm64Emitter.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
using namespace Arm64Gen;
//static int temp32; // unused?
static const bool enableDebug = false;
static const bool enableDisasm = false;
//static bool enableStatistics = false; //unused?
// ARM64 calling conventions
// Standard: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
// Apple: https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
// Summary:
// ===========
// SP ("x31") is not a GPR so irrelevant.
// x0-x7: 8 parameter/result registers
// x8: "Indirect result location register" (points to struct return values? I think we can map this)
// x9-x15: 7 temporary registers (no need to save)
// x16: temporary register/procedure call scratch register 1
// x17: temporary register/procedure call scratch register 2
// x18: unavailable (reserved for use by the OS or linker or whatever - iOS, for example, uses it)
// x19-x28: 10 callee-saved registers
// x29: the frame pointer register
// x30: link register for procedure calls
// So: Scratch registers: x16, x17
// Mappable registers in priority order:
// x19, x20, x21, x22, x23, (x24, x25, x26, x27, x28), x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x0, x1,
// That's a whole lot of registers so we might be able to statically allocate a bunch of common MIPS registers.
// We should put statically allocated registers in the 7 callee-save regs that are left over after the system regs (x19-x25), so we don't have to bother with
// saving them when we call out of the JIT. We will perform regular dynamic register allocation in the rest (x0-x15)
// STATIC ALLOCATION ARM64 (these are all callee-save registers):
// x23 : Down counter
// x24 : PC save on JR with non-nice delay slot (to be eliminated later?)
// x25 : MSR/MRS temporary (to be eliminated later)
// x26 : JIT base reg
// x27 : MIPS state (Could eliminate by placing the MIPS state right at the memory base)
// x28 : Memory base pointer.
extern volatile CoreState coreState;
void ShowPC(u32 downcount, void *membase, void *jitbase) {
static int count = 0;
if (currentMIPS) {
ERROR_LOG(Log::JIT, "ShowPC : %08x Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase);
} else {
ERROR_LOG(Log::JIT, "Universe corrupt?");
}
//if (count > 2000)
// exit(0);
count++;
}
void DisassembleArm(const u8 *data, int size);
// PLAN: no more block numbers - crazy opcodes just contain offset within
// dynarec buffer
// At this offset - 4, there is an int specifying the block number.
namespace MIPSComp {
using namespace Arm64JitConstants;
void Arm64Jit::GenerateFixedCode(const JitOptions &jo) {
BeginWrite(GetMemoryProtectPageSize());
const u8 *start = AlignCodePage();
if (jo.useStaticAlloc) {
saveStaticRegisters = AlignCode16();
STR(INDEX_UNSIGNED, DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
gpr.EmitSaveStaticRegisters();
RET();
loadStaticRegisters = AlignCode16();
gpr.EmitLoadStaticRegisters();
LDR(INDEX_UNSIGNED, DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
RET();
start = saveStaticRegisters;
} else {
saveStaticRegisters = nullptr;
loadStaticRegisters = nullptr;
}
restoreRoundingMode = AlignCode16(); {
MRS(SCRATCH2_64, FIELD_FPCR);
// We are not in flush-to-zero mode outside the JIT, so let's turn it off.
uint32_t mask = ~(4 << 22);
// Assume we're always in round-to-nearest mode beforehand.
mask &= ~(3 << 22);
ANDI2R(SCRATCH2, SCRATCH2, mask);
_MSR(FIELD_FPCR, SCRATCH2_64);
RET();
}
applyRoundingMode = AlignCode16(); {
LDR(INDEX_UNSIGNED, SCRATCH2, CTXREG, offsetof(MIPSState, fcr31));
TSTI2R(SCRATCH2, 1 << 24);
ANDI2R(SCRATCH2, SCRATCH2, 3);
FixupBranch skip1 = B(CC_EQ);
ADDI2R(SCRATCH2, SCRATCH2, 4);
SetJumpTarget(skip1);
// We can skip if the rounding mode is nearest (0) and flush is not set.
// (as restoreRoundingMode cleared it out anyway)
CMPI2R(SCRATCH2, 0);
FixupBranch skip = B(CC_EQ);
// MIPS Rounding Mode: ARM Rounding Mode
// 0: Round nearest 0
// 1: Round to zero 3
// 2: Round up (ceil) 1
// 3: Round down (floor) 2
ANDI2R(SCRATCH1, SCRATCH2, 3);
CMPI2R(SCRATCH1, 1);
FixupBranch skipadd = B(CC_NEQ);
ADDI2R(SCRATCH2, SCRATCH2, 2);
SetJumpTarget(skipadd);
FixupBranch skipsub = B(CC_LE);
SUBI2R(SCRATCH2, SCRATCH2, 1);
SetJumpTarget(skipsub);
// Actually change the system FPCR register
MRS(SCRATCH1_64, FIELD_FPCR);
// Clear both flush-to-zero and rounding before re-setting them.
ANDI2R(SCRATCH1, SCRATCH1, ~((4 | 3) << 22));
ORR(SCRATCH1, SCRATCH1, SCRATCH2, ArithOption(SCRATCH2, ST_LSL, 22));
_MSR(FIELD_FPCR, SCRATCH1_64);
SetJumpTarget(skip);
RET();
}
updateRoundingMode = AlignCode16(); {
LDR(INDEX_UNSIGNED, SCRATCH2, CTXREG, offsetof(MIPSState, fcr31));
// Set SCRATCH2 to FZ:RM (FZ is bit 24, and RM are lowest 2 bits.)
TSTI2R(SCRATCH2, 1 << 24);
ANDI2R(SCRATCH2, SCRATCH2, 3);
FixupBranch skip = B(CC_EQ);
ADDI2R(SCRATCH2, SCRATCH2, 4);
SetJumpTarget(skip);
// Let's update js.currentRoundingFunc with the right convertS0ToSCRATCH1 func.
MOVP2R(SCRATCH1_64, convertS0ToSCRATCH1);
LSL(SCRATCH2, SCRATCH2, 3);
LDR(SCRATCH2_64, SCRATCH1_64, SCRATCH2);
MOVP2R(SCRATCH1_64, &js.currentRoundingFunc);
STR(INDEX_UNSIGNED, SCRATCH2_64, SCRATCH1_64, 0);
RET();
}
enterDispatcher = AlignCode16();
uint32_t regs_to_save = Arm64Gen::ALL_CALLEE_SAVED;
uint32_t regs_to_save_fp = Arm64Gen::ALL_CALLEE_SAVED_FP;
fp.ABI_PushRegisters(regs_to_save, regs_to_save_fp);
// Fixed registers, these are always kept when in Jit context.
MOVP2R(MEMBASEREG, Memory::base);
MOVP2R(CTXREG, mips_);
MOVP2R(JITBASEREG, GetBasePtr());
LoadStaticRegisters();
MovFromPC(SCRATCH1);
outerLoopPCInSCRATCH1 = GetCodePtr();
MovToPC(SCRATCH1);
outerLoop = GetCodePtr();
SaveStaticRegisters(); // Advance can change the downcount, so must save/restore
RestoreRoundingMode(true);
QuickCallFunctionR(SCRATCH1_64, &CoreTiming::Advance, CTXREG);
ApplyRoundingMode(true);
LoadStaticRegisters();
FixupBranch skipToCoreStateCheck = B(); //skip the downcount check
dispatcherCheckCoreState = GetCodePtr();
// The result of slice decrementation should be in flags if somebody jumped here
// IMPORTANT - We jump on negative, not carry!!!
FixupBranch bailCoreState = B(CC_MI);
SetJumpTarget(skipToCoreStateCheck);
MOVP2R(SCRATCH1_64, &coreState);
LDR(INDEX_UNSIGNED, SCRATCH1, SCRATCH1_64, 0);
CMP(SCRATCH1, 0);
FixupBranch badCoreState = B(CC_NEQ);
FixupBranch skipToRealDispatch2 = B(); //skip the sync and compare first time
dispatcherPCInSCRATCH1 = GetCodePtr();
// TODO: Do we always need to write PC to RAM here?
MovToPC(SCRATCH1);
// At this point : flags = EQ. Fine for the next check, no need to jump over it.
dispatcher = GetCodePtr();
// The result of slice decrementation should be in flags if somebody jumped here
// IMPORTANT - We jump on negative, not carry!!!
FixupBranch bail = B(CC_MI);
SetJumpTarget(skipToRealDispatch2);
dispatcherNoCheck = GetCodePtr();
// Debug
if (enableDebug) {
MOV(W0, DOWNCOUNTREG);
MOV(X1, MEMBASEREG);
MOV(X2, JITBASEREG);
QuickCallFunction(SCRATCH1_64, (void *)&ShowPC);
}
LDR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, pc));
ANDI2R(SCRATCH1, SCRATCH1, Memory::BLOCK_SHADOW_ENTRY_MASK);
MOVP2R(SCRATCH2_64, Memory::blockShadow);
dispatcherFetch = GetCodePtr();
LDR(SCRATCH1, SCRATCH2_64, SCRATCH1_64);
// The entry is the block's offset into the code space, or 0 for none.
FixupBranch skipJump = CBZ(SCRATCH1);
ADD(SCRATCH1_64, JITBASEREG, SCRATCH1_64);
BR(SCRATCH1_64);
SetJumpTarget(skipJump);
// No block found, let's jit. I don't think we actually need to save static regs that are in callee-save regs here but whatever.
// Also, rounding mode gotta be irrelevant here..
// A replacement hook's block shadow entry jumps here too (GetBlockShadowHookValue()). JitAt
// replaces it with the new block's entry, and destroying that block puts the hook value back.
blockShadowHook = GetCodePtr();
SaveStaticRegisters();
RestoreRoundingMode(true);
QuickCallFunctionR(SCRATCH1_64, (void *)&MIPSComp::JitAt, CTXREG);
ApplyRoundingMode(true);
LoadStaticRegisters();
B(dispatcherNoCheck); // no point in special casing this
SetJumpTarget(bail);
SetJumpTarget(bailCoreState);
MOVP2R(SCRATCH1_64, &coreState);
LDR(INDEX_UNSIGNED, SCRATCH1, SCRATCH1_64, 0);
CMP(SCRATCH1, 0);
B(CC_EQ, outerLoop);
const uint8_t *quitLoop = GetCodePtr();
SetJumpTarget(badCoreState);
SaveStaticRegisters();
RestoreRoundingMode(true);
fp.ABI_PopRegisters(regs_to_save, regs_to_save_fp);
RET();
crashHandler = GetCodePtr();
MOVP2R(SCRATCH1_64, &coreState);
MOVI2R(SCRATCH2, CORE_RUNTIME_ERROR);
STR(INDEX_UNSIGNED, SCRATCH2, SCRATCH1_64, 0);
B(quitLoop);
// Generate some integer conversion funcs.
// MIPS order!
static const RoundingMode roundModes[8] = { ROUND_N, ROUND_Z, ROUND_P, ROUND_M, ROUND_N, ROUND_Z, ROUND_P, ROUND_M };
for (size_t i = 0; i < ARRAY_SIZE(roundModes); ++i) {
convertS0ToSCRATCH1[i] = AlignCode16();
fp.FCMP(S0, S0); // Detect NaN
fp.FCVTS(S0, S0, roundModes[i]);
FixupBranch skip = B(CC_VC);
MOVI2R(SCRATCH2, 0x7FFFFFFF);
fp.FMOV(S0, SCRATCH2);
SetJumpTarget(skip);
RET();
}
// Leave this at the end, add more stuff above.
if (enableDisasm) {
std::vector<std::string> lines = DisassembleArm64(start, (int)(GetCodePtr() - start));
for (auto s : lines) {
INFO_LOG(Log::JIT, "%s", s.c_str());
}
}
// Let's spare the pre-generated code from unprotect-reprotect.
AlignCodePage();
jitStartOffset = (int)(GetCodePtr() - start);
// Don't forget to zap the instruction cache! This must stay at the end of this function.
FlushIcache();
EndWrite();
}
} // namespace MIPSComp
#endif // PPSSPP_ARCH(ARM64)
-744
View File
@@ -1,744 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include <algorithm>
#include "Common/BitSet.h"
#include "Common/CPUDetect.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
using namespace MIPSAnalyst;
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM16 (signed short)(op & 0xFFFF)
#define _IMM26 (op & 0x03FFFFFF)
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE.
//#define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; }
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
#define DISABLE { Comp_Generic(op); return; }
namespace MIPSComp {
using namespace Arm64Gen;
using namespace Arm64JitConstants;
static u32 EvalOr(u32 a, u32 b) { return a | b; }
static u32 EvalEor(u32 a, u32 b) { return a ^ b; }
static u32 EvalAnd(u32 a, u32 b) { return a & b; }
static u32 EvalAdd(u32 a, u32 b) { return a + b; }
static u32 EvalSub(u32 a, u32 b) { return a - b; }
void Arm64Jit::CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg src, ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg src, u64 val), u32 (*eval)(u32 a, u32 b)) {
if (gpr.IsImm(rs)) {
gpr.SetImm(rt, (*eval)(gpr.GetImm(rs), uimm));
} else {
gpr.MapDirtyIn(rt, rs);
if (!(this->*tryArithI2R)(gpr.R(rt), gpr.R(rs), uimm)) {
MOVI2R(SCRATCH1, uimm);
(this->*arith)(gpr.R(rt), gpr.R(rs), SCRATCH1);
}
}
}
void Arm64Jit::Comp_IType(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU_IMM);
u32 uimm = op & 0xFFFF;
s32 simm = SignExtend16ToS32(op);
u32 suimm = SignExtend16ToU32(op);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
// noop, won't write to ZERO.
if (rt == 0)
return;
switch (op >> 26) {
case 8: // same as addiu?
case 9: // R(rt) = R(rs) + simm; break; //addiu
// Special-case for small adjustments of pointerified registers. Commonly for SP but happens for others.
if (rs == rt && gpr.IsMappedAsPointer(rs) && IsImmArithmetic(simm < 0 ? -simm : simm, nullptr, nullptr)) {
ARM64Reg r32 = gpr.RPtr(rs);
gpr.MarkDirty(r32);
ARM64Reg r = EncodeRegTo64(r32);
ADDI2R(r, r, simm);
} else {
if (simm >= 0) {
CompImmLogic(rs, rt, simm, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd);
} else if (simm < 0) {
CompImmLogic(rs, rt, -simm, &ARM64XEmitter::SUB, &ARM64XEmitter::TrySUBI2R, &EvalSub);
}
}
break;
case 12: CompImmLogic(rs, rt, uimm, &ARM64XEmitter::AND, &ARM64XEmitter::TryANDI2R, &EvalAnd); break;
case 13: CompImmLogic(rs, rt, uimm, &ARM64XEmitter::ORR, &ARM64XEmitter::TryORRI2R, &EvalOr); break;
case 14: CompImmLogic(rs, rt, uimm, &ARM64XEmitter::EOR, &ARM64XEmitter::TryEORI2R, &EvalEor); break;
case 10: // R(rt) = (s32)R(rs) < simm; break; //slti
if (gpr.IsImm(rs)) {
gpr.SetImm(rt, (s32)gpr.GetImm(rs) < simm ? 1 : 0);
break;
} else if (simm == 0) {
gpr.MapDirtyIn(rt, rs);
// Grab the sign bit (< 0) as 1/0. Slightly faster than a shift.
UBFX(gpr.R(rt), gpr.R(rs), 31, 1);
break;
}
gpr.MapDirtyIn(rt, rs);
if (!TryCMPI2R(gpr.R(rs), (u32)simm)) {
MOVI2R(SCRATCH1, simm);
CMP(gpr.R(rs), SCRATCH1);
}
CSET(gpr.R(rt), CC_LT);
break;
case 11: // R(rt) = R(rs) < suimm; break; //sltiu
if (gpr.IsImm(rs)) {
gpr.SetImm(rt, gpr.GetImm(rs) < suimm ? 1 : 0);
break;
}
gpr.MapDirtyIn(rt, rs);
if (!TryCMPI2R(gpr.R(rs), suimm)) {
MOVI2R(SCRATCH1, suimm);
CMP(gpr.R(rs), SCRATCH1);
}
CSET(gpr.R(rt), CC_LO);
break;
case 15: // R(rt) = uimm << 16; //lui
gpr.SetImm(rt, uimm << 16);
break;
default:
Comp_Generic(op);
break;
}
}
void Arm64Jit::Comp_RType2(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU_BIT);
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
// Don't change $zr.
if (rd == 0)
return;
switch (op & 63) {
case 22: //clz
if (gpr.IsImm(rs)) {
u32 value = gpr.GetImm(rs);
int x = 31;
int count = 0;
while (x >= 0 && !(value & (1 << x))) {
count++;
x--;
}
gpr.SetImm(rd, count);
break;
}
gpr.MapDirtyIn(rd, rs);
CLZ(gpr.R(rd), gpr.R(rs));
break;
case 23: //clo
if (gpr.IsImm(rs)) {
u32 value = gpr.GetImm(rs);
int x = 31;
int count = 0;
while (x >= 0 && (value & (1 << x))) {
count++;
x--;
}
gpr.SetImm(rd, count);
break;
}
gpr.MapDirtyIn(rd, rs);
MVN(gpr.R(rd), gpr.R(rs));
CLZ(gpr.R(rd), gpr.R(rd));
break;
default:
DISABLE;
}
}
void Arm64Jit::CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg rm, ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg rm, u64 val), u32(*eval)(u32 a, u32 b), bool symmetric) {
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
gpr.SetImm(rd, (*eval)(gpr.GetImm(rs), gpr.GetImm(rt)));
return;
}
// Optimize anything against zero.
if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0) {
gpr.MapDirtyIn(rd, rt);
(this->*arith)(gpr.R(rd), WZR, gpr.R(rt));
return;
}
if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0) {
gpr.MapDirtyIn(rd, rs);
(this->*arith)(gpr.R(rd), gpr.R(rs), WZR);
return;
}
if (gpr.IsImm(rt) || (gpr.IsImm(rs) && symmetric)) {
MIPSGPReg lhs = gpr.IsImm(rs) ? rt : rs;
MIPSGPReg rhs = gpr.IsImm(rs) ? rs : rt;
u32 rhsImm = gpr.GetImm(rhs);
gpr.MapDirtyIn(rd, lhs);
if ((this->*tryArithI2R)(gpr.R(rd), gpr.R(lhs), rhsImm)) {
return;
}
// If rd is rhs, we may have lost it in the MapDirtyIn(). lhs was kept.
// This means the rhsImm value was never flushed to rhs, and would be garbage.
if (rd == rhs) {
// Luckily, it was just an imm.
gpr.SetImm(rhs, rhsImm);
}
}
// Can't do the RSB optimization on ARM64 - no RSB!
// Generic solution. If it's an imm, better to flush at this point.
gpr.MapDirtyInIn(rd, rs, rt);
(this->*arith)(gpr.R(rd), gpr.R(rs), gpr.R(rt));
}
void Arm64Jit::Comp_RType3(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
// noop, won't write to ZERO.
if (rd == 0)
return;
switch (op & 63) {
case 10: //if (!R(rt)) R(rd) = R(rs); break; //movz
gpr.MapDirtyInIn(rd, rt, rs, false);
CMP(gpr.R(rt), 0);
CSEL(gpr.R(rd), gpr.R(rs), gpr.R(rd), CC_EQ);
break;
case 11:// if (R(rt)) R(rd) = R(rs); break; //movn
gpr.MapDirtyInIn(rd, rt, rs, false);
CMP(gpr.R(rt), 0);
CSEL(gpr.R(rd), gpr.R(rs), gpr.R(rd), CC_NEQ);
break;
case 32: //R(rd) = R(rs) + R(rt); break; //add
case 33: //R(rd) = R(rs) + R(rt); break; //addu
if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0 && !gpr.IsImm(rt)) {
// Special case: actually a mov, avoid arithmetic.
gpr.MapDirtyIn(rd, rt);
MOV(gpr.R(rd), gpr.R(rt));
} else if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0 && !gpr.IsImm(rs)) {
gpr.MapDirtyIn(rd, rs);
MOV(gpr.R(rd), gpr.R(rs));
} else {
CompType3(rd, rs, rt, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd, true);
}
break;
case 34: //R(rd) = R(rs) - R(rt); break; //sub
case 35: //R(rd) = R(rs) - R(rt); break; //subu
CompType3(rd, rs, rt, &ARM64XEmitter::SUB, &ARM64XEmitter::TrySUBI2R, &EvalSub, false);
break;
case 36: //R(rd) = R(rs) & R(rt); break; //and
CompType3(rd, rs, rt, &ARM64XEmitter::AND, &ARM64XEmitter::TryANDI2R, &EvalAnd, true);
break;
case 37: //R(rd) = R(rs) | R(rt); break; //or
CompType3(rd, rs, rt, &ARM64XEmitter::ORR, &ARM64XEmitter::TryORRI2R, &EvalOr, true);
break;
case 38: //R(rd) = R(rs) ^ R(rt); break; //xor/eor
CompType3(rd, rs, rt, &ARM64XEmitter::EOR, &ARM64XEmitter::TryEORI2R, &EvalEor, true);
break;
case 39: // R(rd) = ~(R(rs) | R(rt)); break; //nor
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
gpr.SetImm(rd, ~(gpr.GetImm(rs) | gpr.GetImm(rt)));
} else if (gpr.IsImm(rs) || gpr.IsImm(rt)) {
MIPSGPReg lhs = gpr.IsImm(rs) ? rt : rs;
MIPSGPReg rhs = gpr.IsImm(rs) ? rs : rt;
u32 rhsImm = gpr.GetImm(rhs);
if (rhsImm == 0) {
gpr.MapDirtyIn(rd, lhs);
MVN(gpr.R(rd), gpr.R(lhs));
} else {
// Ignored, just for IsImmLogical.
unsigned int n, imm_s, imm_r;
if (IsImmLogical(rhsImm, 32, &n, &imm_s, &imm_r)) {
// Great, we can avoid flushing a reg.
gpr.MapDirtyIn(rd, lhs);
ORRI2R(gpr.R(rd), gpr.R(lhs), rhsImm);
} else {
gpr.MapDirtyInIn(rd, rs, rt);
ORR(gpr.R(rd), gpr.R(rs), gpr.R(rt));
}
MVN(gpr.R(rd), gpr.R(rd));
}
} else {
gpr.MapDirtyInIn(rd, rs, rt);
ORR(gpr.R(rd), gpr.R(rs), gpr.R(rt));
MVN(gpr.R(rd), gpr.R(rd));
}
break;
case 42: //R(rd) = (int)R(rs) < (int)R(rt); break; //slt
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
gpr.SetImm(rd, (s32)gpr.GetImm(rs) < (s32)gpr.GetImm(rt));
} else {
gpr.MapDirtyInIn(rd, rs, rt);
CMP(gpr.R(rs), gpr.R(rt));
CSET(gpr.R(rd), CC_LT);
}
break;
case 43: //R(rd) = R(rs) < R(rt); break; //sltu
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
gpr.SetImm(rd, gpr.GetImm(rs) < gpr.GetImm(rt));
} else {
gpr.MapDirtyInIn(rd, rs, rt);
CMP(gpr.R(rs), gpr.R(rt));
CSET(gpr.R(rd), CC_LO);
}
break;
case 44: //R(rd) = max(R(rs), R(rt); break; //max
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
gpr.SetImm(rd, (u32)std::max((int)gpr.GetImm(rs), (int)gpr.GetImm(rt)));
break;
}
gpr.MapDirtyInIn(rd, rs, rt);
CMP(gpr.R(rs), gpr.R(rt));
CSEL(gpr.R(rd), gpr.R(rs), gpr.R(rt), CC_GE);
break;
case 45: //R(rd) = min(R(rs), R(rt)); break; //min
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
gpr.SetImm(rd, (u32)std::min((int)gpr.GetImm(rs), (int)gpr.GetImm(rt)));
break;
}
gpr.MapDirtyInIn(rd, rs, rt);
CMP(gpr.R(rs), gpr.R(rt));
CSEL(gpr.R(rd), gpr.R(rs), gpr.R(rt), CC_LE);
break;
default:
Comp_Generic(op);
break;
}
}
void Arm64Jit::CompShiftImm(MIPSOpcode op, Arm64Gen::ShiftType shiftType, int sa) {
MIPSGPReg rd = _RD;
MIPSGPReg rt = _RT;
if (gpr.IsImm(rt)) {
switch (shiftType) {
case ST_LSL:
gpr.SetImm(rd, gpr.GetImm(rt) << sa);
break;
case ST_LSR:
gpr.SetImm(rd, gpr.GetImm(rt) >> sa);
break;
case ST_ASR:
gpr.SetImm(rd, (int)gpr.GetImm(rt) >> sa);
break;
case ST_ROR:
gpr.SetImm(rd, (gpr.GetImm(rt) >> sa) | (gpr.GetImm(rt) << (32 - sa)));
break;
default:
DISABLE;
}
} else {
gpr.MapDirtyIn(rd, rt);
MOV(gpr.R(rd), gpr.R(rt), ArithOption(gpr.R(rd), shiftType, sa));
}
}
void Arm64Jit::CompShiftVar(MIPSOpcode op, Arm64Gen::ShiftType shiftType) {
MIPSGPReg rd = _RD;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
if (gpr.IsImm(rs)) {
int sa = gpr.GetImm(rs) & 0x1F;
CompShiftImm(op, shiftType, sa);
return;
}
gpr.MapDirtyInIn(rd, rs, rt);
switch (shiftType) {
case ST_LSL: LSLV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break;
case ST_LSR: LSRV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break;
case ST_ASR: ASRV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break;
case ST_ROR: RORV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break;
}
}
void Arm64Jit::Comp_ShiftType(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU);
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
int fd = _FD;
int sa = _SA;
// noop, won't write to ZERO.
if (rd == 0)
return;
// WARNING : ROTR
switch (op & 0x3f) {
case 0: CompShiftImm(op, ST_LSL, sa); break; //sll
case 2: CompShiftImm(op, (rs & 1) ? ST_ROR : ST_LSR, sa); break; //srl
case 3: CompShiftImm(op, ST_ASR, sa); break; //sra
case 4: CompShiftVar(op, ST_LSL); break; //sllv
case 6: CompShiftVar(op, (fd & 1) ? ST_ROR : ST_LSR); break; //srlv
case 7: CompShiftVar(op, ST_ASR); break; //srav
default:
DISABLE;
break;
}
}
void Arm64Jit::Comp_Special3(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU_BIT);
MIPSGPReg rs = _RS;
MIPSGPReg rt = _RT;
int pos = _POS;
int size = _SIZE + 1;
u32 mask = 0xFFFFFFFFUL >> (32 - size);
// Don't change $zr.
if (rt == 0)
return;
switch (op & 0x3f) {
case 0x0: //ext
if (gpr.IsImm(rs)) {
gpr.SetImm(rt, (gpr.GetImm(rs) >> pos) & mask);
return;
}
gpr.MapDirtyIn(rt, rs);
// pos + size can go past 32 (the field then just ends at bit 31), which UBFX can't encode.
UBFX(gpr.R(rt), gpr.R(rs), pos, std::min(size, 32 - pos));
break;
case 0x4: //ins
{
const u32 sourcemask = mask >> pos;
// With msb below pos there's nothing to insert, and no BFI for it.
if (sourcemask == 0)
return;
if (gpr.IsImm(rs)) {
const u32 inserted = (gpr.GetImm(rs) & sourcemask) << pos;
if (gpr.IsImm(rt)) {
const u32 destmask = ~(sourcemask << pos);
gpr.SetImm(rt, (gpr.GetImm(rt) & destmask) | inserted);
return;
}
// It might be nice to avoid flushing rs, but it's a little slower and
// usually more instructions. Not worth it.
gpr.MapDirtyIn(rt, rs, false);
BFI(gpr.R(rt), gpr.R(rs), pos, size - pos);
} else {
gpr.MapDirtyIn(rt, rs, false);
BFI(gpr.R(rt), gpr.R(rs), pos, size - pos);
}
}
break;
}
}
void Arm64Jit::Comp_Allegrex(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU_BIT);
MIPSGPReg rt = _RT;
MIPSGPReg rd = _RD;
// Don't change $zr.
if (rd == 0)
return;
switch (AllegrexBitOp(op)) {
case 16: // seb // R(rd) = SignExtend8ToU32(R(rt));
if (gpr.IsImm(rt)) {
gpr.SetImm(rd, SignExtend8ToU32(gpr.GetImm(rt)));
return;
}
gpr.MapDirtyIn(rd, rt);
SXTB(gpr.R(rd), gpr.R(rt));
break;
case 24: // seh
if (gpr.IsImm(rt)) {
gpr.SetImm(rd, SignExtend16ToU32(gpr.GetImm(rt)));
return;
}
gpr.MapDirtyIn(rd, rt);
SXTH(gpr.R(rd), gpr.R(rt));
break;
case 20: //bitrev
if (gpr.IsImm(rt)) {
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
u32 v = gpr.GetImm(rt);
v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); // odd<->even
v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); // pair<->pair
v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); // nibb<->nibb
v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); // byte<->byte
v = (v >> 16) | (v << 16); // hword<->hword
gpr.SetImm(rd, v);
return;
}
gpr.MapDirtyIn(rd, rt);
RBIT(gpr.R(rd), gpr.R(rt));
break;
default:
Comp_Generic(op);
return;
}
}
void Arm64Jit::Comp_Allegrex2(MIPSOpcode op) {
CONDITIONAL_DISABLE(ALU_BIT);
MIPSGPReg rt = _RT;
MIPSGPReg rd = _RD;
// Don't change $zr.
if (rd == 0)
return;
switch (AllegrexBitOp(op)) {
case 2: //wsbh
if (gpr.IsImm(rt)) {
gpr.SetImm(rd, ((gpr.GetImm(rt) & 0xFF00FF00) >> 8) | ((gpr.GetImm(rt) & 0x00FF00FF) << 8));
} else {
gpr.MapDirtyIn(rd, rt);
REV16(gpr.R(rd), gpr.R(rt));
}
break;
case 3: //wsbw
if (gpr.IsImm(rt)) {
gpr.SetImm(rd, swap32(gpr.GetImm(rt)));
} else {
gpr.MapDirtyIn(rd, rt);
REV32(gpr.R(rd), gpr.R(rt));
}
break;
default:
Comp_Generic(op);
break;
}
}
void Arm64Jit::Comp_MulDivType(MIPSOpcode op) {
CONDITIONAL_DISABLE(MULDIV);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
// Note that in all cases below, LO is actually mapped to HI:LO.
// That is, the host reg is 64 bits and has HI at the top.
// HI is not mappable.
switch (op & 63) {
case 16: // R(rd) = HI; //mfhi
if (rd == MIPS_REG_ZERO) {
break;
}
// LO and HI are in the same reg.
if (gpr.IsImm(MIPS_REG_LO)) {
gpr.SetImm(rd, gpr.GetImm(MIPS_REG_LO) >> 32);
break;
}
gpr.MapDirtyIn(rd, MIPS_REG_LO);
UBFX(EncodeRegTo64(gpr.R(rd)), EncodeRegTo64(gpr.R(MIPS_REG_LO)), 32, 32);
break;
case 17: // HI = R(rs); //mthi
if (gpr.IsImm(rs) && gpr.IsImm(MIPS_REG_LO)) {
gpr.SetImm(MIPS_REG_LO, (gpr.GetImm(rs) << 32) | (gpr.GetImm(MIPS_REG_LO) & 0xFFFFFFFFULL));
break;
}
gpr.MapDirtyIn(MIPS_REG_LO, rs, false);
BFI(EncodeRegTo64(gpr.R(MIPS_REG_LO)), EncodeRegTo64(gpr.R(rs)), 32, 32);
break;
case 18: // R(rd) = LO; break; //mflo
if (rd == MIPS_REG_ZERO) {
break;
}
if (gpr.IsImm(MIPS_REG_LO)) {
gpr.SetImm(rd, gpr.GetImm(MIPS_REG_LO) & 0xFFFFFFFFULL);
break;
}
gpr.MapDirtyIn(rd, MIPS_REG_LO);
MOV(gpr.R(rd), gpr.R(MIPS_REG_LO));
break;
case 19: // LO = R(rs); break; //mtlo
if (gpr.IsImm(rs) && gpr.IsImm(MIPS_REG_LO)) {
gpr.SetImm(MIPS_REG_LO, gpr.GetImm(rs) | (gpr.GetImm(MIPS_REG_LO) & ~0xFFFFFFFFULL));
break;
}
gpr.MapDirtyIn(MIPS_REG_LO, rs, false);
BFI(EncodeRegTo64(gpr.R(MIPS_REG_LO)), EncodeRegTo64(gpr.R(rs)), 0, 32);
break;
case 24: //mult (the most popular one). lo,hi = signed mul (rs * rt)
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
s64 result = (s64)(s32)gpr.GetImm(rs) * (s64)(s32)gpr.GetImm(rt);
gpr.SetImm(MIPS_REG_LO, (u64)result);
break;
}
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt);
SMULL(EncodeRegTo64(gpr.R(MIPS_REG_LO)), gpr.R(rs), gpr.R(rt));
break;
case 25: //multu (2nd) lo,hi = unsigned mul (rs * rt)
if (gpr.IsImm(rs) && gpr.IsImm(rt)) {
u64 resultBits = (u64)gpr.GetImm(rs) * (u64)gpr.GetImm(rt);
gpr.SetImm(MIPS_REG_LO, resultBits);
break;
}
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt);
// In case of pointerification, let's use UMULL.
UMULL(EncodeRegTo64(gpr.R(MIPS_REG_LO)), gpr.R(rs), gpr.R(rt));
break;
case 26: //div
{
// TODO: Does this handle INT_MAX, 0, etc. correctly?
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt);
SDIV(gpr.R(MIPS_REG_LO), gpr.R(rs), gpr.R(rt));
MSUB(SCRATCH1, gpr.R(rt), gpr.R(MIPS_REG_LO), gpr.R(rs));
CMPI2R(gpr.R(rt), 0);
FixupBranch skipZero = B(CC_NEQ);
// HI set properly already, we just need to set LO.
MOVI2R(gpr.R(MIPS_REG_LO), -1);
CMPI2R(gpr.R(rs), 0);
FixupBranch moreThan16Bit = B(CC_GE);
MOVI2R(gpr.R(MIPS_REG_LO), 1);
SetJumpTarget(moreThan16Bit);
SetJumpTarget(skipZero);
BFI(EncodeRegTo64(gpr.R(MIPS_REG_LO)), SCRATCH1_64, 32, 32);
break;
}
case 27: //divu
// Do we have a known power-of-two denominator? Yes, this happens.
if (gpr.IsImm(rt) && (gpr.GetImm(rt) & (gpr.GetImm(rt) - 1)) == 0 && gpr.GetImm(rt) != 0) {
u32 denominator = gpr.GetImm(rt);
gpr.MapDirtyIn(MIPS_REG_LO, rs);
// Remainder is just an AND, neat.
ANDI2R(SCRATCH1, gpr.R(rs), denominator - 1, SCRATCH1);
int shift = 0;
while (denominator != 0) {
++shift;
denominator >>= 1;
}
// The shift value is one too much for the divide by the same value.
if (shift > 1) {
LSR(gpr.R(MIPS_REG_LO), gpr.R(rs), shift - 1);
} else {
MOV(gpr.R(MIPS_REG_LO), gpr.R(rs));
}
BFI(EncodeRegTo64(gpr.R(MIPS_REG_LO)), SCRATCH1_64, 32, 32);
} else {
// TODO: Does this handle INT_MAX, 0, etc. correctly?
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt);
UDIV(gpr.R(MIPS_REG_LO), gpr.R(rs), gpr.R(rt));
MSUB(SCRATCH1, gpr.R(rt), gpr.R(MIPS_REG_LO), gpr.R(rs));
CMPI2R(gpr.R(rt), 0);
FixupBranch skipZero = B(CC_NEQ);
// HI set properly, we just need to set LO.
MOVI2R(SCRATCH2, 0xFFFF);
MOVI2R(gpr.R(MIPS_REG_LO), -1);
CMP(gpr.R(rs), SCRATCH2);
FixupBranch moreThan16Bit = B(CC_HI);
MOV(gpr.R(MIPS_REG_LO), SCRATCH2);
SetJumpTarget(moreThan16Bit);
SetJumpTarget(skipZero);
BFI(EncodeRegTo64(gpr.R(MIPS_REG_LO)), SCRATCH1_64, 32, 32);
}
break;
case 28: //madd
{
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt, false);
ARM64Reg lo64 = EncodeRegTo64(gpr.R(MIPS_REG_LO));
SMADDL(lo64, gpr.R(rs), gpr.R(rt), lo64); // Operands are backwards in the emitter!
}
break;
case 29: //maddu
{
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt, false);
ARM64Reg lo64 = EncodeRegTo64(gpr.R(MIPS_REG_LO));
UMADDL(lo64, gpr.R(rs), gpr.R(rt), lo64); // Operands are backwards in the emitter!
}
break;
case 46: // msub
{
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt, false);
ARM64Reg lo64 = EncodeRegTo64(gpr.R(MIPS_REG_LO));
SMSUBL(lo64, gpr.R(rs), gpr.R(rt), lo64); // Operands are backwards in the emitter!
}
break;
case 47: // msubu
{
gpr.MapDirtyInIn(MIPS_REG_LO, rs, rt, false);
ARM64Reg lo64 = EncodeRegTo64(gpr.R(MIPS_REG_LO));
UMSUBL(lo64, gpr.R(rs), gpr.R(rt), lo64); // Operands are backwards in the emitter!
break;
}
default:
DISABLE;
}
}
}
#endif // PPSSPP_ARCH(ARM64)
-670
View File
@@ -1,670 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Profiler/Profiler.h"
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/Reporting.h"
#include "Core/MemMap.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/HLETables.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Common/Arm64Emitter.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM26 (op & 0x03FFFFFF)
#define TARGET16 ((int)(SignExtend16ToU32(op) << 2))
#define TARGET26 (_IMM26 << 2)
#define LOOPOPTIMIZATION 0
// We can disable nice delay slots.
// #define CONDITIONAL_NICE_DELAYSLOT branchInfo.delaySlotIsNice = false;
#define CONDITIONAL_NICE_DELAYSLOT ;
using namespace MIPSAnalyst;
namespace MIPSComp
{
using namespace Arm64Gen;
using namespace Arm64JitConstants;
void Arm64Jit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely)
{
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rt, rs);
CONDITIONAL_NICE_DELAYSLOT;
bool immBranch = false;
bool immBranchTaken = false;
if (gpr.IsImm(rs) && gpr.IsImm(rt) && !branchInfo.delaySlotIsBranch) {
// The cc flags are opposites: when NOT to take the branch.
bool immBranchNotTaken;
s32 rsImm = (s32)gpr.GetImm(rs);
s32 rtImm = (s32)gpr.GetImm(rt);
switch (cc)
{
case CC_EQ: immBranchNotTaken = rsImm == rtImm; break;
case CC_NEQ: immBranchNotTaken = rsImm != rtImm; break;
default: immBranchNotTaken = false; _dbg_assert_msg_(false, "Bad cc flag in BranchRSRTComp().");
}
immBranch = true;
immBranchTaken = !immBranchNotTaken;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
if (immBranch) {
// Continuing is handled above, this is just static jumping.
if (immBranchTaken || !likely)
CompileDelaySlot(DELAYSLOT_FLUSH);
else
FlushAll();
const u32 destAddr = immBranchTaken ? targetAddr : notTakenTarget;
WriteExit(destAddr, js.nextExit++);
} else {
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
// We might be able to flip the condition (EQ/NEQ are easy.)
const bool canFlip = cc == CC_EQ || cc == CC_NEQ;
const bool rsIsZero = gpr.IsImm(rs) && gpr.GetImm(rs) == 0;
const bool rtIsZero = gpr.IsImm(rt) && gpr.GetImm(rt) == 0;
Arm64Gen::FixupBranch ptr;
if ((likely || branchInfo.delaySlotIsNice) && (rsIsZero || rtIsZero) && canFlip) {
// Special case, we can just use CBZ/CBNZ directly.
MIPSGPReg r = rsIsZero ? rt : rs;
gpr.MapReg(r);
// Flush should keep r in the same armreg.
ARM64Reg ar = gpr.R(r);
FlushAll();
if (cc == CC_EQ) {
ptr = CBZ(ar);
} else {
ptr = CBNZ(ar);
}
} else {
u32 val;
bool shift;
if (gpr.IsImm(rt) && IsImmArithmetic(gpr.GetImm(rt), &val, &shift)) {
gpr.MapReg(rs);
CMP(gpr.R(rs), val, shift);
} else if (gpr.IsImm(rt) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rt), &val, &shift)) {
gpr.MapReg(rs);
CMN(gpr.R(rs), val, shift);
} else if (gpr.IsImm(rs) && IsImmArithmetic(gpr.GetImm(rs), &val, &shift) && canFlip) {
gpr.MapReg(rt);
CMP(gpr.R(rt), val, shift);
} else if (gpr.IsImm(rs) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rs), &val, &shift) && canFlip) {
gpr.MapReg(rt);
CMN(gpr.R(rt), val, shift);
} else {
gpr.MapInIn(rs, rt);
CMP(gpr.R(rs), gpr.R(rt));
}
if (!likely) {
if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
else
FlushAll();
ptr = B(cc);
} else {
FlushAll();
ptr = B(cc);
}
}
if (likely && !branchInfo.delaySlotIsBranch) {
// Only executed when taking the branch.
CompileDelaySlot(DELAYSLOT_FLUSH);
}
if (branchInfo.delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
FlushAll();
}
// Take the branch
WriteExit(targetAddr, js.nextExit++);
SetJumpTarget(ptr);
// Not taken
WriteExit(notTakenTarget, js.nextExit++);
}
js.compiling = false;
}
void Arm64Jit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool likely)
{
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), andLink, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rs);
CONDITIONAL_NICE_DELAYSLOT;
bool immBranch = false;
bool immBranchTaken = false;
if (gpr.IsImm(rs) && !branchInfo.delaySlotIsBranch) {
// The cc flags are opposites: when NOT to take the branch.
bool immBranchNotTaken;
s32 imm = (s32)gpr.GetImm(rs);
switch (cc)
{
case CC_GT: immBranchNotTaken = imm > 0; break;
case CC_GE: immBranchNotTaken = imm >= 0; break;
case CC_LT: immBranchNotTaken = imm < 0; break;
case CC_LE: immBranchNotTaken = imm <= 0; break;
default: immBranchNotTaken = false; _dbg_assert_msg_(false, "Bad cc flag in BranchRSZeroComp().");
}
immBranch = true;
immBranchTaken = !immBranchNotTaken;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
if (immBranch) {
// Continuing is handled above, this is just static jumping.
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
if (immBranchTaken || !likely)
CompileDelaySlot(DELAYSLOT_FLUSH);
else
FlushAll();
const u32 destAddr = immBranchTaken ? targetAddr : notTakenTarget;
WriteExit(destAddr, js.nextExit++);
} else {
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
gpr.MapReg(rs);
CMP(gpr.R(rs), 0);
if (andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
Arm64Gen::FixupBranch ptr;
if (!likely)
{
if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
else
FlushAll();
ptr = B(cc);
}
else
{
FlushAll();
ptr = B(cc);
if (!branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_FLUSH);
}
if (branchInfo.delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
FlushAll();
}
// Take the branch
WriteExit(targetAddr, js.nextExit++);
SetJumpTarget(ptr);
// Not taken
WriteExit(notTakenTarget, js.nextExit++);
}
js.compiling = false;
}
void Arm64Jit::Comp_RelBranch(MIPSOpcode op)
{
// The CC flags here should be opposite of the actual branch becuase they skip the branching action.
switch (op >> 26)
{
case 4: BranchRSRTComp(op, CC_NEQ, false); break;//beq
case 5: BranchRSRTComp(op, CC_EQ, false); break;//bne
case 6: BranchRSZeroComp(op, CC_GT, false, false); break;//blez
case 7: BranchRSZeroComp(op, CC_LE, false, false); break;//bgtz
case 20: BranchRSRTComp(op, CC_NEQ, true); break;//beql
case 21: BranchRSRTComp(op, CC_EQ, true); break;//bnel
case 22: BranchRSZeroComp(op, CC_GT, false, true); break;//blezl
case 23: BranchRSZeroComp(op, CC_LE, false, true); break;//bgtzl
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
}
void Arm64Jit::Comp_RelBranchRI(MIPSOpcode op)
{
switch ((op >> 16) & 0x1F)
{
case 0: BranchRSZeroComp(op, CC_GE, false, false); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz
case 1: BranchRSZeroComp(op, CC_LT, false, false); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez
case 2: BranchRSZeroComp(op, CC_GE, false, true); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl
case 3: BranchRSZeroComp(op, CC_LT, false, true); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl
case 16: BranchRSZeroComp(op, CC_GE, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltzal
case 17: BranchRSZeroComp(op, CC_LT, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgezal
case 18: BranchRSZeroComp(op, CC_GE, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else SkipLikely(); break;//bltzall
case 19: BranchRSZeroComp(op, CC_LT, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else SkipLikely(); break;//bgezall
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
}
// If likely is set, discard the branch slot if NOT taken.
void Arm64Jit::BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceFPU(op, branchInfo.delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
gpr.MapReg(MIPS_REG_FPCOND);
Arm64Gen::FixupBranch ptr;
if (likely || branchInfo.delaySlotIsNice) {
// FlushAll() won't actually change the reg.
ARM64Reg ar = gpr.R(MIPS_REG_FPCOND);
FlushAll();
if (cc == CC_EQ) {
ptr = TBZ(ar, 0);
} else {
ptr = TBNZ(ar, 0);
}
} else {
TSTI2R(gpr.R(MIPS_REG_FPCOND), 1, SCRATCH1);
if (!branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
ptr = B(cc);
}
if (likely && !branchInfo.delaySlotIsBranch) {
CompileDelaySlot(DELAYSLOT_FLUSH);
}
if (branchInfo.delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
FlushAll();
}
// Take the branch
WriteExit(targetAddr, js.nextExit++);
SetJumpTarget(ptr);
// Not taken
WriteExit(ResolveNotTakenTarget(branchInfo), js.nextExit++);
js.compiling = false;
}
void Arm64Jit::Comp_FPUBranch(MIPSOpcode op) {
switch((op >> 16) & 0x1f) {
case 0: BranchFPFlag(op, CC_NEQ, false); break; // bc1f
case 1: BranchFPFlag(op, CC_EQ, false); break; // bc1t
case 2: BranchFPFlag(op, CC_NEQ, true); break; // bc1fl
case 3: BranchFPFlag(op, CC_EQ, true); break; // bc1tl
default:
_dbg_assert_msg_( 0, "Trying to interpret instruction that can't be interpreted");
break;
}
}
// If likely is set, discard the branch slot if NOT taken.
void Arm64Jit::BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
// Sometimes there's a VFPU branch in a delay slot (Disgaea 2: Dark Hero Days, Zettai Hero Project, La Pucelle)
// The behavior is undefined - the CPU may take the second branch even if the first one passes.
// However, it does consistently try each branch, which these games seem to expect.
branchInfo.delaySlotIsNice = IsDelaySlotNiceVFPU(op, branchInfo.delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
if (!likely && branchInfo.delaySlotIsNice)
CompileDelaySlot(DELAYSLOT_NICE);
int imm3 = (op >> 18) & 7;
gpr.MapReg(MIPS_REG_VFPUCC);
Arm64Gen::FixupBranch ptr;
if (likely || branchInfo.delaySlotIsNice || branchInfo.delaySlotIsBranch) {
// FlushAll() won't actually change the reg.
ARM64Reg ar = gpr.R(MIPS_REG_VFPUCC);
FlushAll();
if (cc == CC_EQ) {
ptr = TBZ(ar, imm3);
} else {
ptr = TBNZ(ar, imm3);
}
} else {
TSTI2R(gpr.R(MIPS_REG_VFPUCC), 1ULL << imm3, SCRATCH1);
if (!branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
ptr = B(cc);
}
if (likely && !branchInfo.delaySlotIsBranch) {
CompileDelaySlot(DELAYSLOT_FLUSH);
}
if (branchInfo.delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
FlushAll();
}
// Take the branch
WriteExit(targetAddr, js.nextExit++);
SetJumpTarget(ptr);
// Not taken
WriteExit(ResolveNotTakenTarget(branchInfo), js.nextExit++);
js.compiling = false;
}
void Arm64Jit::Comp_VBranch(MIPSOpcode op)
{
switch ((op >> 16) & 3)
{
case 0: BranchVFPUFlag(op, CC_NEQ, false); break; // bvf
case 1: BranchVFPUFlag(op, CC_EQ, false); break; // bvt
case 2: BranchVFPUFlag(op, CC_NEQ, true); break; // bvfl
case 3: BranchVFPUFlag(op, CC_EQ, true); break; // bvtl
}
}
static void HitInvalidJump(uint32_t dest) {
Core_ExecException(dest, currentMIPS->pc - 8, ExecExceptionType::JUMP);
}
void Arm64Jit::Comp_Jump(MIPSOpcode op) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
u32 off = TARGET26;
u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off;
// Might be a stubbed address or something?
if (!Memory::IsValidAddress(targetAddr) || (targetAddr & 3) != 0) {
if (js.nextExit == 0) {
ERROR_LOG(Log::JIT, "Jump to invalid address: %08x", targetAddr);
} else {
js.compiling = false;
}
// TODO: Mark this block dirty or something? May be indication it will be changed by imports.
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
MOVI2R(SCRATCH1, GetCompilerPC() + 8);
MovToPC(SCRATCH1);
MOVI2R(W0, targetAddr);
QuickCallFunction(SCRATCH1, (const void *)&HitInvalidJump);
WriteSyscallExit();
return;
}
switch (op >> 26) {
case 2: //j
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
WriteExit(targetAddr, js.nextExit++);
break;
case 3: //jal
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
WriteExit(targetAddr, js.nextExit++);
break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
js.compiling = false;
}
void Arm64Jit::Comp_JumpReg(MIPSOpcode op)
{
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in JumpReg delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
bool andLink = (op & 0x3f) == 9 && rd != MIPS_REG_ZERO;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
bool delaySlotIsNice = IsDelaySlotNiceReg(op, delaySlotOp, rs);
if (andLink && rs == rd)
delaySlotIsNice = false;
CONDITIONAL_NICE_DELAYSLOT;
ARM64Reg destReg = INVALID_REG;
if (IsSyscall(delaySlotOp)) {
gpr.MapReg(rs);
MovToPC(gpr.R(rs)); // For syscall to be able to return.
if (andLink)
gpr.SetImm(rd, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_FLUSH);
return; // Syscall (delay slot) wrote exit code.
} else if (delaySlotIsNice) {
if (andLink)
gpr.SetImm(rd, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
if (!andLink && rs == MIPS_REG_RA && g_Config.bDiscardRegsOnJRRA) {
// According to the MIPS ABI, there are some regs we don't need to preserve.
// Let's discard them so we don't need to write them back.
// NOTE: Not all games follow the MIPS ABI! Tekken 6, for example, will crash
// with this enabled.
gpr.DiscardR(MIPS_REG_COMPILER_SCRATCH);
for (int i = MIPS_REG_A0; i <= MIPS_REG_T7; i++)
gpr.DiscardR((MIPSGPReg)i);
gpr.DiscardR(MIPS_REG_T8);
gpr.DiscardR(MIPS_REG_T9);
}
gpr.MapReg(rs);
destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs
FlushAll();
} else {
// Since we can't be in a delay slot, should be safe to steal FLAGTEMPREG for a temp reg.
// It will be saved, even if a function is called.
destReg = DecodeReg(FLAGTEMPREG);
gpr.MapReg(rs);
MOV(destReg, gpr.R(rs));
if (andLink)
gpr.SetImm(rd, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
}
switch (op & 0x3f)
{
case 8: //jr
break;
case 9: //jalr
break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
WriteExitDestInR(destReg);
js.compiling = false;
}
void Arm64Jit::Comp_Syscall(MIPSOpcode op)
{
if (op.encoding == 0x03FFFFcc) {
WARN_LOG(Log::JIT, "Encountered bad syscall instruction at %08x (%08x)", js.compilerPC, op.encoding);
}
if (!g_Config.bSkipDeadbeefFilling)
{
// All of these will be overwritten with DEADBEEF anyway.
gpr.DiscardR(MIPS_REG_COMPILER_SCRATCH);
// We need to keep A0 - T3, which are used for args.
gpr.DiscardR(MIPS_REG_T4);
gpr.DiscardR(MIPS_REG_T5);
gpr.DiscardR(MIPS_REG_T6);
gpr.DiscardR(MIPS_REG_T7);
gpr.DiscardR(MIPS_REG_T8);
gpr.DiscardR(MIPS_REG_T9);
gpr.DiscardR(MIPS_REG_HI);
gpr.DiscardR(MIPS_REG_LO);
}
// If we're in a delay slot, this is off by one.
const int offset = js.inDelaySlot ? -1 : 0;
WriteDownCount(offset, false);
RestoreRoundingMode();
js.downcountAmount = -offset;
if (!js.inDelaySlot) {
MOVI2R(SCRATCH1, GetCompilerPC() + 4);
MovToPC(SCRATCH1);
}
FlushAll();
SaveStaticRegisters();
#ifdef USE_PROFILER
// When profiling, we can't skip CallSyscall, since it times syscalls.
MOVI2R(W0, op.encoding);
QuickCallFunction(X1, (void *)&CallSyscall);
#else
// Skip the CallSyscall where possible.
const HLEFunction *func = GetSyscallFunctionData(op, js.compilerPC);
if (func) {
void *quickFunc = GetQuickSyscallFunc(func, op);
if (quickFunc) {
MOVI2R(X0, (uintptr_t)func);
// Already flushed, so X1 is safe.
QuickCallFunction(X1, quickFunc);
} else {
MOVI2R(W0, op.encoding);
QuickCallFunction(X1, (void *)&CallSyscall);
}
} else {
MOVI2R(W0, js.compilerPC);
QuickCallFunction(X1, (void *)&CallSyscallUnresolvedAtPC);
}
#endif
LoadStaticRegisters();
ApplyRoundingMode();
WriteSyscallExit();
js.compiling = false;
}
void Arm64Jit::Comp_Break(MIPSOpcode op)
{
Comp_Generic(op);
WriteSyscallExit();
js.compiling = false;
}
} // namespace Mipscomp
#endif // PPSSPP_ARCH(ARM64)
-422
View File
@@ -1,422 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Core/Config.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Common/CPUDetect.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM16 (signed short)(op & 0xFFFF)
#define _IMM26 (op & 0x03FFFFFF)
// FPCR interesting bits:
// 24: FZ (flush-to-zero)
// 23:22: RMode (0 = nearest, 1 = +inf, 2 = -inf, 3 = zero)
// not much else is interesting for us, but should be preserved.
// To access: MRS Xt, FPCR ; MSR FPCR, Xt
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE.
// #define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; }
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
#define DISABLE { Comp_Generic(op); return; }
namespace MIPSComp {
using namespace Arm64Gen;
using namespace Arm64JitConstants;
void Arm64Jit::Comp_FPU3op(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU);
int ft = _FT;
int fs = _FS;
int fd = _FD;
fpr.MapDirtyInIn(fd, fs, ft);
switch (op & 0x3f) {
case 0: fp.FADD(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) + F(ft); //add
case 1: fp.FSUB(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) - F(ft); //sub
case 2: fp.FMUL(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) * F(ft); //mul
case 3: fp.FDIV(fpr.R(fd), fpr.R(fs), fpr.R(ft)); break; //F(fd) = F(fs) / F(ft); //div
default:
DISABLE;
return;
}
}
void Arm64Jit::Comp_FPULS(MIPSOpcode op)
{
CONDITIONAL_DISABLE(LSU_FPU);
CheckMemoryBreakpoint();
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
s32 offset = SignExtend16ToS32(op & 0xFFFF);
int ft = _FT;
MIPSGPReg rs = _RS;
std::vector<FixupBranch> skips;
switch (op >> 26) {
case 49: // lwc1
if (!gpr.IsImm(rs) && jo.cachePointers && g_Config.bFastMemory && (offset & 3) == 0 && offset <= 16380 && offset >= 0) {
gpr.MapRegAsPointer(rs);
fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY);
fp.LDR(32, INDEX_UNSIGNED, fpr.R(ft), gpr.RPtr(rs), offset);
break;
}
fpr.SpillLock(ft);
fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY);
if (gpr.IsImm(rs)) {
#ifdef MASKED_PSP_MEMORY
u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF;
#else
u32 addr = offset + gpr.GetImm(rs);
#endif
MOVI2R(SCRATCH1, addr);
} else {
gpr.MapReg(rs);
if (g_Config.bFastMemory) {
SetScratch1ToEffectiveAddress(rs, offset);
} else {
skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2);
}
}
fp.LDR(32, fpr.R(ft), SCRATCH1_64, ArithOption(MEMBASEREG));
for (auto skip : skips) {
SetJumpTarget(skip);
}
fpr.ReleaseSpillLocksAndDiscardTemps();
break;
case 57: // swc1
if (!gpr.IsImm(rs) && jo.cachePointers && g_Config.bFastMemory && (offset & 3) == 0 && offset <= 16380 && offset >= 0) {
gpr.MapRegAsPointer(rs);
fpr.MapReg(ft, 0);
fp.STR(32, INDEX_UNSIGNED, fpr.R(ft), gpr.RPtr(rs), offset);
break;
}
fpr.MapReg(ft);
if (gpr.IsImm(rs)) {
#ifdef MASKED_PSP_MEMORY
u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF;
#else
u32 addr = offset + gpr.GetImm(rs);
#endif
MOVI2R(SCRATCH1, addr);
} else {
gpr.MapReg(rs);
if (g_Config.bFastMemory) {
SetScratch1ToEffectiveAddress(rs, offset);
} else {
skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2);
}
}
fp.STR(32, fpr.R(ft), SCRATCH1_64, ArithOption(MEMBASEREG));
for (auto skip : skips) {
SetJumpTarget(skip);
}
break;
default:
Comp_Generic(op);
return;
}
}
void Arm64Jit::Comp_FPUComp(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU_COMP);
int opc = op & 0xF;
if (opc >= 8) opc -= 8; // alias
if (opc == 0) { // f, sf (signalling false)
gpr.SetImm(MIPS_REG_FPCOND, 0);
return;
}
int fs = _FS;
int ft = _FT;
gpr.MapReg(MIPS_REG_FPCOND, MAP_DIRTY | MAP_NOINIT);
fpr.MapInIn(fs, ft);
fp.FCMP(fpr.R(fs), fpr.R(ft));
switch (opc) {
case 1: // un, ngle (unordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_VS);
break;
case 2: // eq, seq (equal, ordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_EQ);
break;
case 3: // ueq, ngl (equal, unordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_EQ);
// If ordered, use the above result. If unordered, use ZR+1 (being 1.)
CSINC(gpr.R(MIPS_REG_FPCOND), gpr.R(MIPS_REG_FPCOND), WZR, CC_VC);
return;
case 4: // olt, lt (less than, ordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_LO);
break;
case 5: // ult, nge (less than, unordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_LT);
break;
case 6: // ole, le (less equal, ordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_LS);
break;
case 7: // ule, ngt (less equal, unordered)
CSET(gpr.R(MIPS_REG_FPCOND), CC_LE);
break;
default:
Comp_Generic(op);
return;
}
}
void Arm64Jit::Comp_FPU2op(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU);
int fs = _FS;
int fd = _FD;
switch (op & 0x3f) {
case 4: //F(fd) = sqrtf(F(fs)); break; //sqrt
fpr.MapDirtyIn(fd, fs);
fp.FSQRT(fpr.R(fd), fpr.R(fs));
break;
case 5: //F(fd) = fabsf(F(fs)); break; //abs
fpr.MapDirtyIn(fd, fs);
fp.FABS(fpr.R(fd), fpr.R(fs));
break;
case 6: //F(fd) = F(fs); break; //mov
fpr.MapDirtyIn(fd, fs);
fp.FMOV(fpr.R(fd), fpr.R(fs));
break;
case 7: //F(fd) = -F(fs); break; //neg
fpr.MapDirtyIn(fd, fs);
fp.FNEG(fpr.R(fd), fpr.R(fs));
break;
case 12: //FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s
{
fpr.MapDirtyIn(fd, fs);
fp.FCMP(fpr.R(fs), fpr.R(fs)); // Detect NaN
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_N); // to nearest, ties to even
FixupBranch skip = B(CC_VC);
MOVI2R(SCRATCH1, 0x7FFFFFFF);
fp.FMOV(fpr.R(fd), SCRATCH1);
SetJumpTarget(skip);
break;
}
case 13: //FsI(fd) = Rto0(F(fs))); break; //trunc.w.s
{
fpr.MapDirtyIn(fd, fs);
fp.FCMP(fpr.R(fs), fpr.R(fs));
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_Z);
FixupBranch skip = B(CC_VC);
MOVI2R(SCRATCH1, 0x7FFFFFFF);
fp.FMOV(fpr.R(fd), SCRATCH1);
SetJumpTarget(skip);
break;
}
case 14://FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s
{
fpr.MapDirtyIn(fd, fs);
fp.FCMP(fpr.R(fs), fpr.R(fs));
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_P); // towards +inf
FixupBranch skip = B(CC_VC);
MOVI2R(SCRATCH1, 0x7FFFFFFF);
fp.FMOV(fpr.R(fd), SCRATCH1);
SetJumpTarget(skip);
break;
}
case 15: //FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s
{
fpr.MapDirtyIn(fd, fs);
fp.FCMP(fpr.R(fs), fpr.R(fs));
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_M); // towards -inf
FixupBranch skip = B(CC_VC);
MOVI2R(SCRATCH1, 0x7FFFFFFF);
fp.FMOV(fpr.R(fd), SCRATCH1);
SetJumpTarget(skip);
break;
}
case 32: //F(fd) = (float)FsI(fs); break; //cvt.s.w
fpr.MapDirtyIn(fd, fs);
fp.SCVTF(fpr.R(fd), fpr.R(fs));
break;
case 36: //FsI(fd) = (int) F(fs); break; //cvt.w.s
fpr.MapDirtyIn(fd, fs);
if (js.hasSetRounding) {
// We're just going to defer to our cached func. Here's the arg.
fp.FMOV(S0, fpr.R(fs));
MOVP2R(SCRATCH1_64, &js.currentRoundingFunc);
LDR(INDEX_UNSIGNED, SCRATCH1_64, SCRATCH1_64, 0);
BLR(SCRATCH1_64);
fp.FMOV(fpr.R(fd), S0);
} else {
fp.FCMP(fpr.R(fs), fpr.R(fs));
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_N);
FixupBranch skip_nan = B(CC_VC);
MOVI2R(SCRATCH1, 0x7FFFFFFF);
fp.FMOV(fpr.R(fd), SCRATCH1);
SetJumpTarget(skip_nan);
}
break;
default:
DISABLE;
}
}
void Arm64Jit::Comp_mxc1(MIPSOpcode op)
{
CONDITIONAL_DISABLE(FPU_XFER);
int fs = _FS;
MIPSGPReg rt = _RT;
switch ((op >> 21) & 0x1f) {
case 0: // R(rt) = FI(fs); break; //mfc1
if (rt == MIPS_REG_ZERO) {
return;
}
gpr.MapReg(rt, MAP_DIRTY | MAP_NOINIT);
if (fpr.IsMapped(fs)) {
fp.FMOV(gpr.R(rt), fpr.R(fs));
} else {
LDR(INDEX_UNSIGNED, gpr.R(rt), CTXREG, fpr.GetMipsRegOffset(fs));
}
return;
case 2: //cfc1
if (rt == MIPS_REG_ZERO) {
return;
}
if (fs == 31) {
if (gpr.IsImm(MIPS_REG_FPCOND)) {
gpr.MapReg(rt, MAP_DIRTY | MAP_NOINIT);
LDR(INDEX_UNSIGNED, gpr.R(rt), CTXREG, offsetof(MIPSState, fcr31));
if (gpr.GetImm(MIPS_REG_FPCOND) & 1) {
ORRI2R(gpr.R(rt), gpr.R(rt), 0x1 << 23, SCRATCH2);
} else {
ANDI2R(gpr.R(rt), gpr.R(rt), ~(0x1 << 23), SCRATCH2);
}
} else {
gpr.MapDirtyIn(rt, MIPS_REG_FPCOND);
LDR(INDEX_UNSIGNED, gpr.R(rt), CTXREG, offsetof(MIPSState, fcr31));
BFI(gpr.R(rt), gpr.R(MIPS_REG_FPCOND), 23, 1);
}
} else if (fs == 0) {
gpr.SetImm(rt, MIPSState::FCR0_VALUE);
} else {
// Unsupported regs are always 0.
gpr.SetImm(rt, 0);
}
return;
case 4: //FI(fs) = R(rt); break; //mtc1
if (gpr.IsImm(rt)) {
// This can't be run on LO/HI.
uint32_t ival = (uint32_t)gpr.GetImm(rt);
float floatval;
memcpy(&floatval, &ival, sizeof(floatval));
uint8_t imm8;
// If zero, just zero it.
fpr.MapReg(fs, MAP_NOINIT | MAP_DIRTY);
if (ival == 0) {
fp.FMOV(fpr.R(fs), WZR); // This is supposedly special cased in hardware to be fast.
} else if (FPImm8FromFloat(floatval, &imm8)) {
fp.FMOV(fpr.R(fs), imm8);
} else {
// Materialize the register and do a cross move.
gpr.MapReg(rt);
fp.FMOV(fpr.R(fs), gpr.R(rt));
}
} else {
gpr.MapReg(rt);
fpr.MapReg(fs, MAP_NOINIT | MAP_DIRTY);
fp.FMOV(fpr.R(fs), gpr.R(rt));
}
return;
case 6: //ctc1
if (fs == 31) {
// Must clear before setting, since ApplyRoundingMode() assumes it was cleared.
RestoreRoundingMode();
bool wasImm = gpr.IsImm(rt);
u32 immVal = -1;
if (wasImm) {
immVal = gpr.GetImm(rt);
gpr.SetImm(MIPS_REG_FPCOND, (immVal >> 23) & 1);
gpr.MapReg(rt);
} else {
gpr.MapDirtyIn(MIPS_REG_FPCOND, rt);
}
// Update MIPS state. Only these bits can be written (pspautotests cpu/fpu/fcr).
ANDI2R(SCRATCH1, gpr.R(rt), 0x0181FFFF, SCRATCH2);
STR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, fcr31));
if (!wasImm) {
UBFX(gpr.R(MIPS_REG_FPCOND), gpr.R(rt), 23, 1);
// TODO: We do have the fcr31 value in a register here, could use that in UpdateRoundingMode to avoid reloading it.
UpdateRoundingMode();
} else {
UpdateRoundingMode(immVal);
}
ApplyRoundingMode();
} else {
Comp_Generic(op);
}
return;
default:
DISABLE;
break;
}
}
} // namespace MIPSComp
#endif // PPSSPP_ARCH(ARM64)
-515
View File
@@ -1,515 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Core/MemMap.h"
#include "Core/Config.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM16 (signed short)(op & 0xFFFF)
#define _IMM26 (op & 0x03FFFFFF)
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE.
//#define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; }
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
#define DISABLE { Comp_Generic(op); return; }
namespace MIPSComp {
using namespace Arm64Gen;
using namespace Arm64JitConstants;
// Destroys SCRATCH2
void Arm64Jit::SetScratch1ToEffectiveAddress(MIPSGPReg rs, s16 offset) {
if (offset) {
ADDI2R(SCRATCH1, gpr.R(rs), offset, SCRATCH2);
} else {
MOV(SCRATCH1, gpr.R(rs));
}
#ifdef MASKED_PSP_MEMORY
ANDI2R(SCRATCH1, SCRATCH1, 0x3FFFFFFF);
#endif
}
std::vector<FixupBranch> Arm64Jit::SetScratch1ForSafeAddress(MIPSGPReg rs, s16 offset, ARM64Reg tempReg) {
std::vector<FixupBranch> skips;
SetScratch1ToEffectiveAddress(rs, offset);
// We can do this a little smarter by shifting out the lower 8 bits, since blocks are 0x100 aligned.
// PSP_GetUserMemoryEnd() is dynamic, but the others encode to imms just fine.
// So we only need to safety check the one value.
// This is because ARM64 immediates for many instructions like CMP can only encode
// immediates up to 12 bits, shifted by 12 or not.
if ((PSP_GetUserMemoryEnd() & 0x000FFFFF) == 0) {
// In other words, shift right 8, and kill off the top 4 bits as we don't want them involved in the ocmpares.
UBFX(tempReg, SCRATCH1, 8, 24 - 4);
// Now check if we're higher than that.
CMPI2R(tempReg, PSP_GetUserMemoryEnd() >> 8);
} else {
// Compare first using the tempReg (need it because we have a full 28-bit value), then shift into it.
ANDI2R(SCRATCH1, SCRATCH1, 0x0FFFFFFF);
CMPI2R(SCRATCH1, PSP_GetUserMemoryEnd(), tempReg);
UBFX(tempReg, SCRATCH1, 8, 24);
}
skips.push_back(B(CC_HS));
// If its higher than memory start and we didn't skip yet, it must be good. Hurray.
CMPI2R(tempReg, PSP_GetKernelMemoryBase() >> 8);
FixupBranch inRAM = B(CC_HS);
// If we got here and it's higher, then it's between VRAM and RAM - skip.
CMPI2R(tempReg, PSP_GetVidMemEnd() >> 8);
skips.push_back(B(CC_HS));
// And if it's higher the VRAM and we're still here again, it's in VRAM.
CMPI2R(tempReg, PSP_GetVidMemBase() >> 8);
FixupBranch inVRAM = B(CC_HS);
// Last gap, this is between SRAM and VRAM. Skip it.
CMPI2R(tempReg, PSP_GetScratchpadMemoryEnd() >> 8);
skips.push_back(B(CC_HS));
// And for lower than SRAM, we just skip again.
CMPI2R(tempReg, PSP_GetScratchpadMemoryBase() >> 8);
skips.push_back(B(CC_LO));
// At this point, we're either in SRAM (above) or in RAM/VRAM.
SetJumpTarget(inRAM);
SetJumpTarget(inVRAM);
return skips;
}
void Arm64Jit::Comp_ITypeMemLR(MIPSOpcode op, bool load) {
CONDITIONAL_DISABLE(LSU);
CheckMemoryBreakpoint();
int offset = SignExtend16ToS32(op & 0xFFFF);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
int o = op >> 26;
if (!js.inDelaySlot && !jo.Disabled(JitDisable::LSU_UNALIGNED)) {
// Optimisation: Combine to single unaligned load/store
bool isLeft = (o == 34 || o == 42);
CheckMemoryBreakpoint(1);
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Find a matching shift in opposite direction with opposite offset.
if (nextOp == (isLeft ? (op.encoding + (4 << 26) - 3) : (op.encoding - (4 << 26) + 3))) {
EatInstruction(nextOp);
nextOp = MIPSOpcode(((load ? 35 : 43) << 26) | ((isLeft ? nextOp : op) & 0x03FFFFFF)); //lw, sw
Comp_ITypeMem(nextOp);
return;
}
}
u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF;
std::vector<FixupBranch> skips;
if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) {
#ifdef MASKED_PSP_MEMORY
u32 addr = iaddr & 0x3FFFFFFF;
#else
u32 addr = iaddr;
#endif
// Need to initialize since this only loads part of the register.
// But rs no longer matters (even if rs == rt) since we have the address.
gpr.MapReg(rt, load ? MAP_DIRTY : 0);
MOVI2R(SCRATCH1, addr & ~3);
u8 shift = (addr & 3) * 8;
switch (o) {
case 34: // lwl
LDR(SCRATCH1, MEMBASEREG, SCRATCH1);
ANDI2R(gpr.R(rt), gpr.R(rt), 0x00ffffff >> shift, INVALID_REG);
ORR(gpr.R(rt), gpr.R(rt), SCRATCH1, ArithOption(gpr.R(rt), ST_LSL, 24 - shift));
break;
case 38: // lwr
LDR(SCRATCH1, MEMBASEREG, SCRATCH1);
ANDI2R(gpr.R(rt), gpr.R(rt), 0xffffff00 << (24 - shift), INVALID_REG);
ORR(gpr.R(rt), gpr.R(rt), SCRATCH1, ArithOption(gpr.R(rt), ST_LSR, shift));
break;
case 42: // swl
LDR(SCRATCH2, MEMBASEREG, SCRATCH1);
ANDI2R(SCRATCH2, SCRATCH2, 0xffffff00 << shift, INVALID_REG);
ORR(SCRATCH2, SCRATCH2, gpr.R(rt), ArithOption(gpr.R(rt), ST_LSR, 24 - shift));
STR(SCRATCH2, MEMBASEREG, SCRATCH1);
break;
case 46: // swr
LDR(SCRATCH2, MEMBASEREG, SCRATCH1);
ANDI2R(SCRATCH2, SCRATCH2, 0x00ffffff >> (24 - shift), INVALID_REG);
ORR(SCRATCH2, SCRATCH2, gpr.R(rt), ArithOption(gpr.R(rt), ST_LSL, shift));
STR(SCRATCH2, MEMBASEREG, SCRATCH1);
break;
}
return;
}
_dbg_assert_msg_(!gpr.IsImm(rs), "Invalid immediate address %08x? CPU bug?", iaddr);
if (load) {
gpr.MapDirtyIn(rt, rs, false);
} else {
gpr.MapInIn(rt, rs);
}
gpr.SpillLock(rt);
gpr.SpillLock(rs);
// Need to get temps before skipping safe mem.
ARM64Reg LR_SCRATCH3 = gpr.GetAndLockTempR();
ARM64Reg LR_SCRATCH4 = o == 42 || o == 46 ? gpr.GetAndLockTempR() : INVALID_REG;
if (!g_Config.bFastMemory && rs != MIPS_REG_SP) {
skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2);
} else {
SetScratch1ToEffectiveAddress(rs, offset);
}
// Here's our shift amount.
ANDI2R(SCRATCH2, SCRATCH1, 3);
LSL(SCRATCH2, SCRATCH2, 3);
// Now align the address for the actual read.
ANDI2R(SCRATCH1, SCRATCH1, ~3U);
switch (o) {
case 34: // lwl
MOVI2R(LR_SCRATCH3, 0x00ffffff);
LDR(SCRATCH1, MEMBASEREG, ArithOption(SCRATCH1));
LSRV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2);
AND(gpr.R(rt), gpr.R(rt), LR_SCRATCH3);
NEG(SCRATCH2, SCRATCH2);
ADDI2R(SCRATCH2, SCRATCH2, 24);
LSLV(SCRATCH1, SCRATCH1, SCRATCH2);
ORR(gpr.R(rt), gpr.R(rt), SCRATCH1);
break;
case 38: // lwr
MOVI2R(LR_SCRATCH3, 0xffffff00);
LDR(SCRATCH1, MEMBASEREG, ArithOption(SCRATCH1));
LSRV(SCRATCH1, SCRATCH1, SCRATCH2);
NEG(SCRATCH2, SCRATCH2);
ADDI2R(SCRATCH2, SCRATCH2, 24);
LSLV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2);
AND(gpr.R(rt), gpr.R(rt), LR_SCRATCH3);
ORR(gpr.R(rt), gpr.R(rt), SCRATCH1);
break;
case 42: // swl
MOVI2R(LR_SCRATCH3, 0xffffff00);
LDR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1));
LSLV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2);
AND(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3);
NEG(SCRATCH2, SCRATCH2);
ADDI2R(SCRATCH2, SCRATCH2, 24);
LSRV(LR_SCRATCH3, gpr.R(rt), SCRATCH2);
ORR(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3);
STR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1));
break;
case 46: // swr
MOVI2R(LR_SCRATCH3, 0x00ffffff);
LDR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1));
NEG(SCRATCH2, SCRATCH2);
ADDI2R(SCRATCH2, SCRATCH2, 24);
LSRV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2);
AND(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3);
NEG(SCRATCH2, SCRATCH2);
ADDI2R(SCRATCH2, SCRATCH2, 24);
LSLV(LR_SCRATCH3, gpr.R(rt), SCRATCH2);
ORR(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3);
STR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1));
break;
}
for (auto skip : skips) {
SetJumpTarget(skip);
}
gpr.ReleaseSpillLocksAndDiscardTemps();
}
void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU);
CheckMemoryBreakpoint();
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
int offset = SignExtend16ToS32(op & 0xFFFF);
bool load = false;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
int o = op >> 26;
// 16-bit accesses can go through the depth layout (Memory::DepthMirrored16).
if (Memory::DepthMirrorsActive() && (o == 33 || o == 37 || o == 41)) {
DISABLE;
}
if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) {
// Don't load anything into $zr
return;
}
u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF;
std::vector<FixupBranch> skips;
ARM64Reg targetReg = INVALID_REG;
ARM64Reg addrReg = INVALID_REG;
int dataSize = 4;
switch (o) {
case 37:
case 33:
dataSize = 2;
break;
case 36:
case 32:
dataSize = 1;
break;
// Store
case 41:
dataSize = 2;
break;
case 40:
dataSize = 1;
break;
}
switch (o) {
case 32: //lb
case 33: //lh
case 35: //lw
case 36: //lbu
case 37: //lhu
load = true;
case 40: //sb
case 41: //sh
case 43: //sw
#ifndef MASKED_PSP_MEMORY
if (jo.cachePointers && g_Config.bFastMemory) {
// ARM has smaller load/store immediate displacements than MIPS, 12 bits - and some memory ops only have 8 bits.
int offsetRange = 0x3ff;
if (o == 41 || o == 33 || o == 37 || o == 32)
offsetRange = 0xff; // 8 bit offset only
if (!gpr.IsImm(rs) && rs != rt && (offset <= offsetRange) && offset >= 0 &&
(dataSize == 1 || (offset & (dataSize - 1)) == 0)) { // Check that the offset is aligned to the access size as that's required for INDEX_UNSIGNED encodings. we can get here through fallback from lwl/lwr
gpr.SpillLock(rs, rt);
gpr.MapRegAsPointer(rs);
// For a store, try to avoid mapping a reg if not needed.
targetReg = load ? INVALID_REG : gpr.TryMapTempImm(rt);
if (targetReg == INVALID_REG) {
gpr.MapReg(rt, load ? MAP_NOINIT : 0);
targetReg = gpr.R(rt);
}
switch (o) {
case 35: LDR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
case 37: LDRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
case 33: LDRSH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
case 36: LDRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
case 32: LDRSB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
// Store
case 43: STR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
case 41: STRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
case 40: STRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break;
}
gpr.ReleaseSpillLocksAndDiscardTemps();
break;
}
}
#endif
if (!load && gpr.IsImm(rt) && gpr.TryMapTempImm(rt) != INVALID_REG) {
// We're storing an immediate value, let's see if we can optimize rt.
if (!gpr.IsImm(rs) || !Memory::IsValidAddress(iaddr) || offset == 0) {
// In this case, we're always going to need rs mapped, which may flush the temp imm.
// We handle that in the cases below since targetReg is INVALID_REG.
gpr.MapIn(rs);
}
targetReg = gpr.TryMapTempImm(rt);
}
if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) {
#ifdef MASKED_PSP_MEMORY
u32 addr = iaddr & 0x3FFFFFFF;
#else
u32 addr = iaddr;
#endif
if (addr == iaddr && offset == 0) {
// It was already safe. Let's shove it into a reg and use it directly.
if (targetReg == INVALID_REG) {
load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs);
targetReg = gpr.R(rt);
}
addrReg = gpr.R(rs);
} else {
// In this case, only map rt. rs+offset will be in SCRATCH1.
if (targetReg == INVALID_REG) {
gpr.MapReg(rt, load ? MAP_NOINIT : 0);
targetReg = gpr.R(rt);
}
MOVI2R(SCRATCH1, addr);
addrReg = SCRATCH1;
}
} else {
// This gets hit in a few games, as a result of never-taken delay slots (some branch types
// conditionally execute the delay slot instructions). Ignore in those cases.
if (!js.inDelaySlot) {
_dbg_assert_msg_(!gpr.IsImm(rs), "Invalid immediate address %08x? CPU bug?", iaddr);
}
// If we already have a targetReg, we optimized an imm, and rs is already mapped.
if (targetReg == INVALID_REG) {
if (load) {
gpr.MapDirtyIn(rt, rs);
} else {
gpr.MapInIn(rt, rs);
}
targetReg = gpr.R(rt);
}
if (!g_Config.bFastMemory && rs != MIPS_REG_SP) {
skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2);
} else {
SetScratch1ToEffectiveAddress(rs, offset);
}
addrReg = SCRATCH1;
}
switch (o) {
// Load
case 35: LDR(targetReg, MEMBASEREG, addrReg); break;
case 37: LDRH(targetReg, MEMBASEREG, addrReg); break;
case 33: LDRSH(targetReg, MEMBASEREG, addrReg); break;
case 36: LDRB(targetReg, MEMBASEREG, addrReg); break;
case 32: LDRSB(targetReg, MEMBASEREG, addrReg); break;
// Store
case 43: STR(targetReg, MEMBASEREG, addrReg); break;
case 41: STRH(targetReg, MEMBASEREG, addrReg); break;
case 40: STRB(targetReg, MEMBASEREG, addrReg); break;
}
for (auto skip : skips) {
SetJumpTarget(skip);
// TODO: Could clear to zero here on load, if skipping this for good reads.
}
break;
case 34: //lwl
case 38: //lwr
load = true;
case 42: //swl
case 46: //swr
Comp_ITypeMemLR(op, load);
break;
default:
Comp_Generic(op);
return;
}
}
void Arm64Jit::Comp_StoreSync(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU);
CheckMemoryBreakpoint();
// ll loads a word and sets llBit. sc stores a word only if llBit is set, and sets rt to
// whether it did. Only the fast memory path, like the plain loads and stores mostly take.
if (js.kernelMode || !g_Config.bFastMemory) {
DISABLE;
}
int offset = SignExtend16ToS32(op & 0xFFFF);
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
switch (op >> 26) {
case 48: // ll
if (rt != MIPS_REG_ZERO) {
gpr.MapDirtyIn(rt, rs);
SetScratch1ToEffectiveAddress(rs, offset);
LDR(gpr.R(rt), MEMBASEREG, SCRATCH1);
}
MOVI2R(SCRATCH2, 1);
STR(INDEX_UNSIGNED, SCRATCH2, CTXREG, offsetof(MIPSState, llBit));
break;
case 56: // sc
{
if (rt == MIPS_REG_ZERO) {
DISABLE;
}
// rt is both the value to store and the result.
gpr.MapDirtyIn(rt, rs, false);
SetScratch1ToEffectiveAddress(rs, offset);
LDR(INDEX_UNSIGNED, SCRATCH2, CTXREG, offsetof(MIPSState, llBit));
FixupBranch failed = CBZ(SCRATCH2);
STR(gpr.R(rt), MEMBASEREG, SCRATCH1);
MOVI2R(gpr.R(rt), 1);
FixupBranch done = B();
SetJumpTarget(failed);
MOVI2R(gpr.R(rt), 0);
SetJumpTarget(done);
break;
}
default:
DISABLE;
}
}
void Arm64Jit::Comp_Cache(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU);
int func = (op >> 16) & 0x1F;
// See Int_Cache for the definitions.
switch (func) {
case 24: break;
case 25: break;
case 27: break;
case 30: break;
default:
// Fall back to the interpreter.
DISABLE;
}
}
}
#endif // PPSSPP_ARCH(ARM64)
-37
View File
@@ -1,37 +0,0 @@
// Copyright (c) 2013- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Common/CPUDetect.h"
#include "Core/MemMap.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
namespace MIPSComp {
int Arm64Jit::Replace_fabsf() {
fpr.MapDirtyIn(0, 12);
fp.FABS(fpr.R(0), fpr.R(12));
return 4; // Number of instructions in the MIPS function
}
}
#endif // PPSSPP_ARCH(ARM64)
File diff suppressed because it is too large. Load diff
-790
View File
@@ -1,790 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Common/Profiler/Profiler.h"
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/CPUDetect.h"
#include "Common/StringUtils.h"
#include "Core/Reporting.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/Interpreter.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Core/MIPS/ARM64/Arm64RegCacheFPU.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
using namespace Arm64JitConstants;
static void DisassembleArm64Print(const u8 *data, int size) {
std::vector<std::string> lines = DisassembleArm64(data, size);
for (auto s : lines) {
INFO_LOG(Log::JIT, "%s", s.c_str());
}
/*
INFO_LOG(Log::JIT, "+++");
// A format friendly to Online Disassembler which gets endianness wrong
for (size_t i = 0; i < lines.size(); i++) {
uint32_t opcode = ((const uint32_t *)data)[i];
INFO_LOG(Log::JIT, "%d/%d: %08x", (int)(i+1), (int)lines.size(), swap32(opcode));
}
INFO_LOG(Log::JIT, "===");
INFO_LOG(Log::JIT, "===");*/
}
static u32 JitBreakpoint(uint32_t addr) {
// Should we skip this breakpoint?
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc || g_breakpoints.CheckSkipFirst() == addr)
return 0;
BreakAction result = g_breakpoints.ExecBreakPoint(addr);
if ((result & BREAK_ACTION_PAUSE) == 0)
return 0;
return 1;
}
static u32 JitMemCheck(u32 pc) {
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc)
return 0;
// Note: pc may be the delay slot.
const auto op = Memory::Read_Instruction(pc);
s32 offset = SignExtend16ToS32(op & 0xFFFF);
if (MIPSGetInfo(op) & IS_VFPU)
offset &= 0xFFFC;
u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset;
g_breakpoints.ExecOpMemCheck(addr, pc);
return coreState == CORE_RUNNING_CPU || coreState == CORE_NEXTFRAME ? 0 : 1;
}
namespace MIPSComp
{
using namespace Arm64Gen;
using namespace Arm64JitConstants;
Arm64Jit::Arm64Jit(MIPSState *mipsState) : blocks(mipsState, this), gpr(mipsState, &js, &jo), fpr(mipsState, &js, &jo), mips_(mipsState), fp(this) {
// Automatically disable incompatible options.
if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) {
jo.enablePointerify = false;
}
#ifdef MASKED_PSP_MEMORY
jo.enablePointerify = false;
#endif
logBlocks = 0;
dontLogBlocks = 0;
blocks.Init();
gpr.SetEmitter(this);
fpr.SetEmitter(this, &fp);
AllocCodeSpace(1024 * 1024 * 16); // 32MB is the absolute max because that's what an ARM branch instruction can reach, backwards and forwards.
GenerateFixedCode(jo);
js.startDefaultPrefix = mips_->HasDefaultPrefix();
js.currentRoundingFunc = convertS0ToSCRATCH1[mips_->fcr31 & 3];
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
g_breakpoints.SetSkipFirst(0);
}
Arm64Jit::~Arm64Jit() {
}
void Arm64Jit::DoState(PointerWrap &p) {
auto s = p.Section("Jit", 1, 2);
if (!s)
return;
Do(p, js.startDefaultPrefix);
if (p.mode == PointerWrap::MODE_READ && !js.startDefaultPrefix) {
WARN_LOG(Log::CPU, "Jit: An uneaten prefix was previously detected. Jitting in unknown-prefix mode.");
}
if (s >= 2) {
Do(p, js.hasSetRounding);
if (p.mode == PointerWrap::MODE_READ) {
js.lastSetRounding = 0;
}
} else {
js.hasSetRounding = 1;
}
// Note: we can't update the currentRoundingFunc here because fcr31 wasn't loaded yet.
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
g_breakpoints.SetSkipFirst(0);
}
void Arm64Jit::UpdateFCR31() {
js.currentRoundingFunc = convertS0ToSCRATCH1[mips_->fcr31 & 3];
// fcr31 can reach a non-default mode without a compiled ctc1 (an interpreted one, a savestate,
// a core switch, a thread switch). Blocks compiled before that don't switch modes around
// syscalls or conversions, so they're thrown away, at the next safe point.
if ((mips_->fcr31 & 0x01000003) && !js.hasSetRounding) {
js.hasSetRounding = true;
js.lastSetRounding = true;
mips_->ClearJitCacheDeferred();
}
}
void Arm64Jit::FlushAll() {
gpr.FlushAll();
fpr.FlushAll();
FlushPrefixV();
}
void Arm64Jit::FlushPrefixV() {
if (js.startDefaultPrefix && !js.blockWrotePrefixes && js.HasNoPrefix()) {
// They started default, we never modified in memory, and they're default now.
// No reason to modify memory. This is common at end of blocks. Just clear dirty.
js.prefixSFlag = (JitState::PrefixState)(js.prefixSFlag & ~JitState::PREFIX_DIRTY);
js.prefixTFlag = (JitState::PrefixState)(js.prefixTFlag & ~JitState::PREFIX_DIRTY);
js.prefixDFlag = (JitState::PrefixState)(js.prefixDFlag & ~JitState::PREFIX_DIRTY);
return;
}
if ((js.prefixSFlag & JitState::PREFIX_DIRTY) != 0) {
MOVI2R(SCRATCH1, js.prefixS);
STR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_SPREFIX]));
js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {
MOVI2R(SCRATCH1, js.prefixT);
STR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_TPREFIX]));
js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {
MOVI2R(SCRATCH1, js.prefixD);
STR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_DPREFIX]));
js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);
}
// If we got here, we must've written prefixes to memory in this block.
js.blockWrotePrefixes = true;
}
void Arm64Jit::ClearCache() {
INFO_LOG(Log::JIT, "ARM64Jit: Clearing the cache!");
blocks.Clear();
ClearCodeSpace(jitStartOffset);
FlushIcacheSection(region + jitStartOffset, region + region_size - jitStartOffset);
}
void Arm64Jit::InvalidateCacheAt(u32 em_address, int length) {
if (blocks.RangeMayHaveBlocks(em_address, em_address + length)) {
blocks.InvalidateICache(em_address, length);
}
}
void Arm64Jit::EatInstruction(MIPSOpcode op) {
MIPSInfo info = MIPSGetInfo(op);
if (info & DELAYSLOT) {
ERROR_LOG_REPORT_ONCE(ateDelaySlot, Log::JIT, "Ate a branch op.");
}
if (js.inDelaySlot) {
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, Log::JIT, "Ate an instruction inside a delay slot.");
}
CheckJitBreakpoint(GetCompilerPC() + 4, 0);
js.numInstructions++;
js.compilerPC += 4;
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
}
void Arm64Jit::CompileDelaySlot(int flags) {
// Need to offset the downcount which was already incremented for the branch + delay slot.
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
// preserve flag around the delay slot! Maybe this is not always necessary on ARM where
// we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the
// delay slot, we're screwed.
if (flags & DELAYSLOT_SAFE)
MRS(FLAGTEMPREG, FIELD_NZCV); // Save flags register. FLAGTEMPREG is preserved through function calls and is not allocated.
js.inDelaySlot = true;
MIPSOpcode op = GetOffsetInstruction(1);
MIPSCompileOp(op, this);
js.inDelaySlot = false;
if (flags & DELAYSLOT_FLUSH)
FlushAll();
if (flags & DELAYSLOT_SAFE)
_MSR(FIELD_NZCV, FLAGTEMPREG); // Restore flags register
}
void Arm64Jit::Compile(u32 em_address) {
PROFILE_THIS_SCOPE("jitc");
if (!Memory::IsValid4AlignedAddress(em_address)) {
Core_ExecException(em_address, em_address, ExecExceptionType::JUMP);
return;
}
if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) {
INFO_LOG(Log::JIT, "Space left: %d", (int)GetSpaceLeft());
ClearCache();
}
BeginWrite(JitBlockCache::MAX_BLOCK_INSTRUCTIONS * 16);
// To debug really wacky JIT issues, it can be a good idea to bisect the block number,
// and disable parts of the jit using jo.disableFlags for certain ranges of blocks.
int block_num = blocks.AllocateBlock(em_address);
JitBlock *b = blocks.GetBlock(block_num);
b->DoIntegrityCheck(em_address, block_num, "BeforeDoJit");
DoJit(em_address, b);
b->DoIntegrityCheck(em_address, block_num, "BeforeFinalize");
blocks.FinalizeBlock(block_num, jo.enableBlocklink);
b->DoIntegrityCheck(em_address, block_num, "AfterFinalize");
EndWrite();
_dbg_assert_(js.nextExit <= 2);
// Don't forget to zap the newly written instructions in the instruction cache!
FlushIcache();
bool cleanSlate = false;
if (js.hasSetRounding && !js.lastSetRounding) {
WARN_LOG(Log::JIT, "Detected rounding mode usage, rebuilding jit with checks");
// Won't loop, since hasSetRounding is only ever set to 1.
js.lastSetRounding = js.hasSetRounding;
cleanSlate = true;
}
// Drat. The VFPU hit an uneaten prefix at the end of a block.
if (js.startDefaultPrefix && js.MayHavePrefix()) {
WARN_LOG_REPORT(Log::JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4);
js.LogPrefix();
// Let's try that one more time. We won't get back here because we toggled the value.
js.startDefaultPrefix = false;
// TODO ARM64: This crashes.
// TODO: Really? Need to fix that in such a case. Wonder what games are affected.
//cleanSlate = true;
}
if (cleanSlate) {
// Our assumptions are all wrong so it's clean-slate time.
ClearCache();
Compile(em_address);
}
}
void Arm64Jit::RunLoopUntil(u64 globalticks) {
PROFILE_THIS_SCOPE("jit");
((void (*)())enterDispatcher)();
}
u32 Arm64Jit::GetCompilerPC() {
return js.compilerPC;
}
MIPSOpcode Arm64Jit::GetOffsetInstruction(int offset) {
const u32 addr = GetCompilerPC() + 4 * offset;
return Memory::IsValid4AlignedAddress(addr) ? ReadExecutedOp(addr) : Memory::Read_Instruction(addr);
}
void Arm64Jit::DoJit(u32 em_address, JitBlock *b) {
js.Begin(b);
// We add a downcount flag check before the block, used when entering from a linked block.
// The last block decremented downcounter, and the flag should still be available.
// Got three variants here of where we position the code, needs detailed benchmarking.
FixupBranch bail;
if (jo.useBackJump) {
// Moves the MOVI2R and B *before* checkedEntry, and just branch backwards there.
// Speedup seems to be zero unfortunately but I guess it may vary from device to device.
// Not intrusive so keeping it around here to experiment with, may help on ARMv6 due to
// large/slow construction of 32-bit immediates?
const u8 *backJump = GetCodePtr();
MOVI2R(SCRATCH1, js.blockStart);
B((const void *)outerLoopPCInSCRATCH1);
b->checkedEntry = GetCodePtr();
B(CC_LT, backJump);
} else if (jo.useForwardJump) {
b->checkedEntry = GetCodePtr();
bail = B(CC_LT);
} else if (jo.enableBlocklink) {
b->checkedEntry = GetCodePtr();
MOVI2R(SCRATCH1, js.blockStart);
FixupBranch skip = B(CC_GE);
B((const void *)outerLoopPCInSCRATCH1);
SetJumpTarget(skip);
} else {
// No block linking, no need to add headers to blocks.
}
b->normalEntry = GetCodePtr();
gpr.Start();
fpr.Start();
js.numInstructions = 0;
while (js.compiling) {
gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages
// Jit breakpoints are quite fast, so let's do them in release too.
CheckJitBreakpoint(GetCompilerPC(), 0);
MIPSOpcode inst = ReadExecutedOp(GetCompilerPC());
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
MIPSCompileOp(inst, this);
js.compilerPC += 4;
js.numInstructions++;
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
gpr.FlushAll();
}
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
fpr.FlushAll();
FlushPrefixV();
}
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
if (GetSpaceLeft() < 0x800 || js.numInstructions >= JitBlockCache::MAX_BLOCK_INSTRUCTIONS) {
FlushAll();
WriteExit(GetCompilerPC(), js.nextExit++);
js.compiling = false;
}
}
if (jo.useForwardJump) {
SetJumpTarget(bail);
MOVI2R(SCRATCH1, js.blockStart);
B((const void *)outerLoopPCInSCRATCH1);
}
char temp[256];
if (logBlocks > 0 && dontLogBlocks == 0) {
INFO_LOG(Log::JIT, "=============== mips %d ===============", blocks.GetNumBlocks());
for (u32 cpc = b->originalAddress; cpc != GetCompilerPC() + 4; cpc += 4) {
MIPSDisAsm(Memory::ReadUnchecked_Instruction(cpc), cpc, temp, sizeof(temp), true);
INFO_LOG(Log::JIT, "M: %08x %s", cpc, temp);
}
}
b->codeSize = GetCodePtr() - b->normalEntry;
if (logBlocks > 0 && dontLogBlocks == 0) {
INFO_LOG(Log::JIT, "=============== ARM (%d instructions -> %d bytes) ===============", js.numInstructions, b->codeSize);
DisassembleArm64Print(b->normalEntry, GetCodePtr() - b->normalEntry);
}
if (logBlocks > 0)
logBlocks--;
if (dontLogBlocks > 0)
dontLogBlocks--;
b->originalSize = js.numInstructions;
}
bool Arm64Jit::DescribeCodePtr(const u8 *ptr, std::string &name) {
// Used in disassembly viewer.
if (ptr == applyRoundingMode)
name = "applyRoundingMode";
else if (ptr == updateRoundingMode)
name = "updateRoundingMode";
else if (ptr == dispatcher)
name = "dispatcher";
else if (ptr == dispatcherPCInSCRATCH1)
name = "dispatcher (PC in SCRATCH1)";
else if (ptr == dispatcherNoCheck)
name = "dispatcherNoCheck";
else if (ptr == enterDispatcher)
name = "enterDispatcher";
else if (ptr == restoreRoundingMode)
name = "restoreRoundingMode";
else if (ptr == saveStaticRegisters)
name = "saveStaticRegisters";
else if (ptr == loadStaticRegisters)
name = "loadStaticRegisters";
else {
u32 addr = blocks.GetAddressFromBlockPtr(ptr);
// Returns 0 when it's valid, but unknown.
if (addr == 0) {
name = "(unknown or deleted block)";
return true;
} else if (addr != (u32)-1) {
name = "(outside space)";
return true;
}
int number = blocks.GetBlockNumberFromAddress(addr);
if (number != -1) {
const JitBlock *block = blocks.GetBlock(number);
if (block) {
name = StringFromFormat("(block %d at %08x)", number, block->originalAddress);
return true;
}
}
return false;
}
return true;
}
void Arm64Jit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE);
}
ARM64XEmitter emit(GetCodePtrFromWritablePtr(exitPoint), exitPoint);
emit.B(checkedEntry);
// TODO: Write stuff after, convering up the now-unused instructions.
emit.FlushIcache();
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
void Arm64Jit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
// Send anyone who tries to run this block back to the dispatcher.
// Not entirely ideal, but .. works.
// Spurious entrances from previously linked blocks can only come through checkedEntry
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE);
}
ARM64XEmitter emit(GetCodePtrFromWritablePtr(checkedEntry), checkedEntry);
emit.MOVI2R(SCRATCH1, originalAddress);
emit.STR(INDEX_UNSIGNED, SCRATCH1, CTXREG, offsetof(MIPSState, pc));
emit.B(MIPSComp::jit->GetDispatcher());
emit.FlushIcache();
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
void Arm64Jit::Comp_ReplacementFunc(MIPSOpcode op)
{
// We get here if we execute the first instruction of a replaced function. This means
// that we do need to return to RA.
// Inlined function calls (caught in jal) are handled differently.
// Memory never holds these: if it does, the game is executing data. Let the interpreter raise it.
if (Memory::Read_Instruction(GetCompilerPC()).encoding == op.encoding) {
Comp_Generic(op);
return;
}
int index = op.encoding & MIPS_REPLACEMENT_INDEX_MASK;
const ReplacementTableEntry *entry = GetReplacementFunc(index);
if (!entry) {
ERROR_LOG_REPORT_ONCE(replFunc, Log::HLE, "Invalid replacement op %08x at %08x", op.encoding, js.compilerPC);
// TODO: What should we do here? We're way off in the weeds probably.
return;
}
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
// We don't need to disable hooks, the code will still run.
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
// Any breakpoint at the func entry was already tripped, so we can still run the replacement.
// That's a common case - just to see how often the replacement hits.
disabled = g_breakpoints.RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
}
}
if (disabled) {
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
} else if (entry->jitReplaceFunc) {
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
// Compile the original instruction at this address. We ignore cycles for hooks.
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
} else {
FlushAll();
// Flushed, so R1 is safe.
LDR(INDEX_UNSIGNED, SCRATCH1, CTXREG, MIPS_REG_RA * 4);
js.downcountAmount += cycles;
WriteExitDestInR(SCRATCH1);
js.compiling = false;
}
} else if (entry->replaceFunc) {
FlushAll();
SaveStaticRegisters();
RestoreRoundingMode();
MOVI2R(SCRATCH1, GetCompilerPC());
MovToPC(SCRATCH1);
// Standard function call, nothing fancy.
// The function returns the number of cycles it took in EAX.
QuickCallFunction(SCRATCH1_64, (const void *)(entry->replaceFunc));
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
// Compile the original instruction at this address. We ignore cycles for hooks.
ApplyRoundingMode();
LoadStaticRegisters();
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
} else {
ApplyRoundingMode();
LoadStaticRegisters();
CMPI2R(W0, 0);
FixupBranch positive = B(CC_GE);
NEG(W0, W0);
MovFromPC(W1);
FixupBranch done = B();
SetJumpTarget(positive);
LDR(INDEX_UNSIGNED, W1, CTXREG, MIPS_REG_RA * 4);
SetJumpTarget(done);
WriteDownCountR(W0);
WriteExitDestInR(W1);
js.compiling = false;
}
} else {
ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
}
}
void Arm64Jit::Comp_Generic(MIPSOpcode op) {
FlushAll();
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
const MIPSInfo info = MIPSGetInfo(op);
if (func) {
SaveStaticRegisters();
// Interpreted ops run in the guest's rounding mode, like on the interpreter. A syscall is host code
// (like a compiled one), and only ctc1 to fcr31 changes the mode, so only those need it switched.
const bool isSyscall = (info & IS_SYSCALL) != 0;
const bool setsMode = MIPS_IS_CTC1_FCR31(op.encoding);
if (isSyscall)
RestoreRoundingMode();
if (setsMode)
UpdateRoundingMode();
MOVI2R(SCRATCH1, GetCompilerPC());
MovToPC(SCRATCH1);
MOVI2R(W0, op.encoding);
QuickCallFunction(SCRATCH2_64, (void *)&MIPSInterpretTrampoline);
if (setsMode)
RestoreRoundingMode(true);
if (isSyscall || setsMode)
ApplyRoundingMode(setsMode);
LoadStaticRegisters();
}
if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0) {
// If it does eat them, it'll happen in MIPSCompileOp().
if ((info & OUT_EAT_PREFIX) == 0)
js.PrefixUnknown();
// Even if DISABLE'd, we want to set this flag so we overwrite.
if ((info & OUT_VFPU_PREFIX) != 0)
js.blockWrotePrefixes = true;
}
}
void Arm64Jit::MovFromPC(ARM64Reg r) {
LDR(INDEX_UNSIGNED, r, CTXREG, offsetof(MIPSState, pc));
}
void Arm64Jit::MovToPC(ARM64Reg r) {
STR(INDEX_UNSIGNED, r, CTXREG, offsetof(MIPSState, pc));
}
// Should not really be necessary except when entering Advance
void Arm64Jit::SaveStaticRegisters() {
if (jo.useStaticAlloc) {
QuickCallFunction(SCRATCH2_64, saveStaticRegisters);
} else {
// Inline the single operation
STR(INDEX_UNSIGNED, DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
}
}
void Arm64Jit::LoadStaticRegisters() {
if (jo.useStaticAlloc) {
QuickCallFunction(SCRATCH2_64, loadStaticRegisters);
} else {
LDR(INDEX_UNSIGNED, DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
}
}
void Arm64Jit::WriteDownCount(int offset, bool updateFlags) {
int theDowncount = js.downcountAmount + offset;
if (updateFlags) {
SUBSI2R(DOWNCOUNTREG, DOWNCOUNTREG, theDowncount, SCRATCH1);
} else {
SUBI2R(DOWNCOUNTREG, DOWNCOUNTREG, theDowncount, SCRATCH1);
}
}
void Arm64Jit::WriteDownCountR(ARM64Reg reg, bool updateFlags) {
if (updateFlags) {
SUBS(DOWNCOUNTREG, DOWNCOUNTREG, reg);
} else {
SUB(DOWNCOUNTREG, DOWNCOUNTREG, reg);
}
}
// Destroys SCRATCH2
void Arm64Jit::RestoreRoundingMode(bool force) {
// If the game has never set an interesting rounding mode, we can safely skip this.
if (force || js.hasSetRounding) {
QuickCallFunction(SCRATCH2_64, restoreRoundingMode);
}
}
// Destroys SCRATCH1 and SCRATCH2
void Arm64Jit::ApplyRoundingMode(bool force) {
// If the game has never set an interesting rounding mode, we can safely skip this.
if (force || js.hasSetRounding) {
QuickCallFunction(SCRATCH2_64, applyRoundingMode);
}
}
// Destroys SCRATCH1 and SCRATCH2
void Arm64Jit::UpdateRoundingMode(u32 fcr31) {
// We must set js.hasSetRounding at compile time, or this block will use the wrong rounding mode.
// The fcr31 parameter is -1 when not known at compile time, so we just assume it was changed.
if (fcr31 & 0x01000003) {
js.hasSetRounding = true;
}
QuickCallFunction(SCRATCH2_64, updateRoundingMode);
}
// IDEA - could have a WriteDualExit that takes two destinations and two condition flags,
// and just have conditional that set PC "twice". This only works when we fall back to dispatcher
// though, as we need to have the SUBS flag set in the end. So with block linking in the mix,
// I don't think this gives us that much benefit.
void Arm64Jit::WriteExit(u32 destination, int exit_num) {
// NOTE: Can't blindly check for bad destination addresses here, sometimes exits with bad destinations are written intentionally (like breaks).
_assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
// NOTE: Can't blindly check for bad destination addresses here, sometimes exits with bad destinations are written intentionally (like breaks).
WriteDownCount();
//If nobody has taken care of this yet (this can be removed when all branches are done)
JitBlock *b = js.curBlock;
b->exitAddress[exit_num] = destination;
b->exitPtrs[exit_num] = GetWritableCodePtr();
// Link opportunity!
int block = blocks.GetBlockNumberFromStartAddress(destination);
if (block >= 0 && jo.enableBlocklink) {
// The target block exists! Directly link to its checked entrypoint.
B(blocks.GetBlock(block)->checkedEntry);
b->linkStatus[exit_num] = true;
} else {
MOVI2R(SCRATCH1, destination);
B((const void *)dispatcherPCInSCRATCH1);
}
}
void Arm64Jit::WriteExitDestInR(ARM64Reg Reg) {
// TODO: If not fast memory, check for invalid address in reg and trigger exception.
MovToPC(Reg);
WriteDownCount();
// TODO: shouldn't need an indirect branch here...
B((const void *)dispatcher);
}
void Arm64Jit::WriteSyscallExit() {
WriteDownCount();
B((const void *)dispatcherCheckCoreState);
}
bool Arm64Jit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
if (g_breakpoints.NeedsBreakCheckAt(addr)) {
MRS(FLAGTEMPREG, FIELD_NZCV);
FlushAll();
MOVI2R(SCRATCH1, GetCompilerPC());
MovToPC(SCRATCH1);
SaveStaticRegisters();
RestoreRoundingMode();
MOVI2R(W0, addr);
QuickCallFunction(SCRATCH1_64, &JitBreakpoint);
// If 0, the conditional breakpoint wasn't taken.
CMPI2R(W0, 0);
FixupBranch skip = B(CC_EQ);
WriteDownCount(downcountOffset);
ApplyRoundingMode();
LoadStaticRegisters();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
LoadStaticRegisters();
_MSR(FIELD_NZCV, FLAGTEMPREG);
return true;
}
return false;
}
bool Arm64Jit::CheckMemoryBreakpoint(int instructionOffset) {
if (g_breakpoints.HasMemChecks()) {
int off = instructionOffset + (js.inDelaySlot ? 1 : 0);
MRS(FLAGTEMPREG, FIELD_NZCV);
FlushAll();
RestoreRoundingMode();
MOVI2R(W0, GetCompilerPC());
MovToPC(W0);
if (off != 0)
ADDI2R(W0, W0, off * 4);
QuickCallFunction(SCRATCH2_64, &JitMemCheck);
// If 0, the breakpoint wasn't tripped.
CMPI2R(W0, 0);
FixupBranch skip = B(CC_EQ);
WriteDownCount(-1 - off);
ApplyRoundingMode();
B((const void *)dispatcherCheckCoreState);
SetJumpTarget(skip);
ApplyRoundingMode();
_MSR(FIELD_NZCV, FLAGTEMPREG);
return true;
}
return false;
}
void Arm64Jit::Comp_DoNothing(MIPSOpcode op) { }
} // namespace
#endif // PPSSPP_ARCH(ARM64)
-302
View File
@@ -1,302 +0,0 @@
// Copyright (c) 2012- 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/CPUDetect.h"
#include "Common/ArmCommon.h"
#include "Common/Arm64Emitter.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Core/MIPS/ARM64/Arm64RegCacheFPU.h"
#include "Core/MIPS/MIPSVFPUUtils.h"
#ifndef offsetof
#include "stddef.h"
#endif
namespace MIPSComp {
class Arm64Jit : public Arm64Gen::ARM64CodeBlock, public JitInterface, public MIPSFrontendInterface {
public:
Arm64Jit(MIPSState *mipsState);
virtual ~Arm64Jit();
void DoState(PointerWrap &p) override;
const JitOptions &GetJitOptions() { return jo; }
// Compiled ops should ignore delay slots
// the compiler will take care of them by itself
// OR NOT
void Comp_Generic(MIPSOpcode op) override;
void RunLoopUntil(u64 globalticks) override;
void Compile(u32 em_address) override; // Compiles a block at current MIPS PC
void DoJit(u32 em_address, JitBlock *b);
const u8 *GetCrashHandler() const override { return crashHandler; }
bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); }
bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
void Comp_ReplacementFunc(MIPSOpcode op) override;
// Ops
void Comp_ITypeMem(MIPSOpcode op) override;
void Comp_StoreSync(MIPSOpcode op) override;
void Comp_Cache(MIPSOpcode op) override;
void Comp_RelBranch(MIPSOpcode op) override;
void Comp_RelBranchRI(MIPSOpcode op) override;
void Comp_FPUBranch(MIPSOpcode op) override;
void Comp_FPULS(MIPSOpcode op) override;
void Comp_FPUComp(MIPSOpcode op) override;
void Comp_Jump(MIPSOpcode op) override;
void Comp_JumpReg(MIPSOpcode op) override;
void Comp_Syscall(MIPSOpcode op) override;
void Comp_Break(MIPSOpcode op) override;
void Comp_IType(MIPSOpcode op) override;
void Comp_RType2(MIPSOpcode op) override;
void Comp_RType3(MIPSOpcode op) override;
void Comp_ShiftType(MIPSOpcode op) override;
void Comp_Allegrex(MIPSOpcode op) override;
void Comp_Allegrex2(MIPSOpcode op) override;
void Comp_VBranch(MIPSOpcode op) override;
void Comp_MulDivType(MIPSOpcode op) override;
void Comp_Special3(MIPSOpcode op) override;
void Comp_FPU3op(MIPSOpcode op) override;
void Comp_FPU2op(MIPSOpcode op) override;
void Comp_mxc1(MIPSOpcode op) override;
void Comp_DoNothing(MIPSOpcode op) override;
void Comp_SV(MIPSOpcode op) override;
void Comp_SVQ(MIPSOpcode op) override;
void Comp_VPFX(MIPSOpcode op) override;
void Comp_VVectorInit(MIPSOpcode op) override;
void Comp_VMatrixInit(MIPSOpcode op) override;
void Comp_VDot(MIPSOpcode op) override;
void Comp_VecDo3(MIPSOpcode op) override;
void Comp_VV2Op(MIPSOpcode op) override;
void Comp_Mftv(MIPSOpcode op) override;
void Comp_Vmfvc(MIPSOpcode op) override;
void Comp_Vmtvc(MIPSOpcode op) override;
void Comp_Vmmov(MIPSOpcode op) override;
void Comp_VScl(MIPSOpcode op) override;
void Comp_Vmmul(MIPSOpcode op) override;
void Comp_Vmscl(MIPSOpcode op) override;
void Comp_Vtfm(MIPSOpcode op) override;
void Comp_VHdp(MIPSOpcode op) override;
void Comp_VCrs(MIPSOpcode op) override;
void Comp_VDet(MIPSOpcode op) override;
void Comp_Vi2x(MIPSOpcode op) override;
void Comp_Vx2i(MIPSOpcode op) override;
void Comp_Vf2i(MIPSOpcode op) override;
void Comp_Vi2f(MIPSOpcode op) override;
void Comp_Vh2f(MIPSOpcode op) override;
void Comp_Vcst(MIPSOpcode op) override;
void Comp_Vhoriz(MIPSOpcode op) override;
void Comp_VRot(MIPSOpcode op) override;
void Comp_VIdt(MIPSOpcode op) override;
void Comp_Vcmp(MIPSOpcode op) override;
void Comp_Vcmov(MIPSOpcode op) override;
void Comp_Viim(MIPSOpcode op) override;
void Comp_Vfim(MIPSOpcode op) override;
void Comp_VCrossQuat(MIPSOpcode op) override;
void Comp_Vsgn(MIPSOpcode op) override;
void Comp_Vocp(MIPSOpcode op) override;
void Comp_ColorConv(MIPSOpcode op) override;
void Comp_Vbfy(MIPSOpcode op) override;
// Non-NEON: VPFX
// NEON implementations of the VFPU ops.
void CompNEON_SV(MIPSOpcode op);
void CompNEON_SVQ(MIPSOpcode op);
void CompNEON_VVectorInit(MIPSOpcode op);
void CompNEON_VMatrixInit(MIPSOpcode op);
void CompNEON_VDot(MIPSOpcode op);
void CompNEON_VecDo3(MIPSOpcode op);
void CompNEON_VV2Op(MIPSOpcode op);
void CompNEON_Mftv(MIPSOpcode op);
void CompNEON_Vmfvc(MIPSOpcode op);
void CompNEON_Vmtvc(MIPSOpcode op);
void CompNEON_Vmmov(MIPSOpcode op);
void CompNEON_VScl(MIPSOpcode op);
void CompNEON_Vmmul(MIPSOpcode op);
void CompNEON_Vmscl(MIPSOpcode op);
void CompNEON_Vtfm(MIPSOpcode op);
void CompNEON_VHdp(MIPSOpcode op);
void CompNEON_VCrs(MIPSOpcode op);
void CompNEON_VDet(MIPSOpcode op);
void CompNEON_Vi2x(MIPSOpcode op);
void CompNEON_Vx2i(MIPSOpcode op);
void CompNEON_Vf2i(MIPSOpcode op);
void CompNEON_Vi2f(MIPSOpcode op);
void CompNEON_Vh2f(MIPSOpcode op);
void CompNEON_Vcst(MIPSOpcode op);
void CompNEON_Vhoriz(MIPSOpcode op);
void CompNEON_VRot(MIPSOpcode op);
void CompNEON_VIdt(MIPSOpcode op);
void CompNEON_Vcmp(MIPSOpcode op);
void CompNEON_Vcmov(MIPSOpcode op);
void CompNEON_Viim(MIPSOpcode op);
void CompNEON_Vfim(MIPSOpcode op);
void CompNEON_VCrossQuat(MIPSOpcode op);
void CompNEON_Vsgn(MIPSOpcode op);
void CompNEON_Vocp(MIPSOpcode op);
void CompNEON_ColorConv(MIPSOpcode op);
void CompNEON_Vbfy(MIPSOpcode op);
int Replace_fabsf() override;
JitBlockCache *GetBlockCache() override { return &blocks; }
JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; }
void ClearCache() override;
void InvalidateCacheAt(u32 em_address, int length = 4) override;
void UpdateFCR31() override;
void EatPrefix() override { js.EatPrefix(); }
const u8 *GetDispatcher() const override {
return dispatcher;
}
u32 GetBlockShadowHookValue() const override {
return (u32)(blockShadowHook - GetBasePtr());
}
bool IsAtDispatchFetch(const u8 *ptr) const override {
return ptr == dispatcherFetch;
}
void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override;
void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override;
const u8 *GetCodeBase() const override { return GetCodePtr(); }
private:
void GenerateFixedCode(const JitOptions &jo);
void FlushAll();
void FlushPrefixV();
u32 GetCompilerPC();
void CompileDelaySlot(int flags);
void EatInstruction(MIPSOpcode op);
MIPSOpcode GetOffsetInstruction(int offset);
void WriteDownCount(int offset = 0, bool updateFlags = true);
void WriteDownCountR(Arm64Gen::ARM64Reg reg, bool updateFlags = true);
void RestoreRoundingMode(bool force = false);
void ApplyRoundingMode(bool force = false);
void UpdateRoundingMode(u32 fcr31 = -1);
void MovFromPC(Arm64Gen::ARM64Reg r);
void MovToPC(Arm64Gen::ARM64Reg r);
// Clobbers SCRATCH2.
void SaveStaticRegisters();
// Clobbers SCRATCH2.
void LoadStaticRegisters();
void WriteExit(u32 destination, int exit_num);
void WriteExitDestInR(Arm64Gen::ARM64Reg Reg);
void WriteSyscallExit();
bool CheckJitBreakpoint(u32 addr, int downcountOffset);
bool CheckMemoryBreakpoint(int instructionOffset = 0);
// Utility compilation functions
void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely);
void BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely);
void BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool likely);
void BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely);
// Utilities to reduce duplicated code
void CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, Arm64Gen::ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, u64 val), u32 (*eval)(u32 a, u32 b));
void CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arithOp2)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, Arm64Gen::ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, u64 val), u32 (*eval)(u32 a, u32 b), bool symmetric = false);
void CompShiftImm(MIPSOpcode op, Arm64Gen::ShiftType shiftType, int sa);
void CompShiftVar(MIPSOpcode op, Arm64Gen::ShiftType shiftType);
void CompVrotShuffle(u8 *dregs, int imm, VectorSize sz, bool negSin);
void CompVV2OpCall(MIPSOpcode op);
void LoadVAfterCallFlush(Arm64Gen::ARM64Reg dest, u8 vreg);
void ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz);
void ApplyPrefixD(const u8 *vregs, VectorSize sz);
void GetVectorRegsPrefixS(u8 *regs, VectorSize sz, int vectorReg) {
_assert_(js.prefixSFlag & JitState::PREFIX_KNOWN);
GetVectorRegs(regs, sz, vectorReg);
ApplyPrefixST(regs, js.prefixS, sz);
}
void GetVectorRegsPrefixT(u8 *regs, VectorSize sz, int vectorReg) {
_assert_(js.prefixTFlag & JitState::PREFIX_KNOWN);
GetVectorRegs(regs, sz, vectorReg);
ApplyPrefixST(regs, js.prefixT, sz);
}
void GetVectorRegsPrefixD(u8 *regs, VectorSize sz, int vectorReg);
// Utils
void SetScratch1ToEffectiveAddress(MIPSGPReg rs, s16 offset);
std::vector<Arm64Gen::FixupBranch> SetScratch1ForSafeAddress(MIPSGPReg rs, s16 offset, Arm64Gen::ARM64Reg tempReg);
void Comp_ITypeMemLR(MIPSOpcode op, bool load);
JitBlockCache blocks;
JitOptions jo;
JitState js;
Arm64RegCache gpr;
Arm64RegCacheFPU fpr;
Arm64Gen::ARM64FloatEmitter fp;
MIPSState *mips_;
int dontLogBlocks;
int logBlocks;
public:
// Code pointers
const u8 *enterDispatcher;
const u8 *outerLoop;
const u8 *outerLoopPCInSCRATCH1;
const u8 *dispatcherCheckCoreState;
const u8 *dispatcherPCInSCRATCH1;
const u8 *dispatcher;
const u8 *dispatcherNoCheck;
const u8 *dispatcherFetch;
const u8 *saveStaticRegisters;
const u8 *loadStaticRegisters;
const u8 *restoreRoundingMode;
const u8 *applyRoundingMode;
const u8 *updateRoundingMode;
const u8 *crashHandler;
const u8 *blockShadowHook;
int jitStartOffset;
// Indexed by FPCR FZ:RN bits for convenience. Uses SCRATCH2.
const u8 *convertS0ToSCRATCH1[8];
};
} // namespace MIPSComp
-927
View File
@@ -1,927 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include "Common/Log.h"
#include "Core/MemMap.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/Reporting.h"
#include "Common/Arm64Emitter.h"
#ifndef offsetof
#include "stddef.h"
#endif
using namespace Arm64Gen;
using namespace Arm64JitConstants;
Arm64RegCache::Arm64RegCache(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo) : mips_(mipsState), js_(js), jo_(jo) {
}
void Arm64RegCache::Init(ARM64XEmitter *emitter) {
emit_ = emitter;
}
void Arm64RegCache::Start() {
for (int i = 0; i < NUM_ARMREG; i++) {
ar[i].mipsReg = MIPS_REG_INVALID;
ar[i].isDirty = false;
ar[i].pointerified = false;
ar[i].tempLocked = false;
}
for (int i = 0; i < NUM_MIPSREG; i++) {
mr[i].loc = ML_MEM;
mr[i].reg = INVALID_REG;
mr[i].imm = -1;
mr[i].spillLock = false;
mr[i].isStatic = false;
}
int numStatics;
const StaticAllocation *statics = GetStaticAllocations(numStatics);
for (int i = 0; i < numStatics; i++) {
ar[statics[i].ar].mipsReg = statics[i].mr;
ar[statics[i].ar].pointerified = statics[i].pointerified && jo_->enablePointerify;
mr[statics[i].mr].loc = ML_ARMREG;
mr[statics[i].mr].reg = statics[i].ar;
mr[statics[i].mr].isStatic = true;
mr[statics[i].mr].spillLock = true;
}
}
const ARM64Reg *Arm64RegCache::GetMIPSAllocationOrder(int &count) {
// See register alloc remarks in Arm64Asm.cpp
// W19-W23 are most suitable for static allocation. Those that are chosen for static allocation
// should be omitted here and added in GetStaticAllocations.
static const ARM64Reg allocationOrder[] = {
W19, W20, W21, W22, W23, W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15,
};
static const ARM64Reg allocationOrderStaticAlloc[] = {
W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15,
};
if (jo_->useStaticAlloc) {
count = ARRAY_SIZE(allocationOrderStaticAlloc);
return allocationOrderStaticAlloc;
} else {
count = ARRAY_SIZE(allocationOrder);
return allocationOrder;
}
}
const Arm64RegCache::StaticAllocation *Arm64RegCache::GetStaticAllocations(int &count) {
static const StaticAllocation allocs[] = {
{MIPS_REG_SP, W19, true},
{MIPS_REG_V0, W20},
{MIPS_REG_V1, W22},
{MIPS_REG_A0, W21},
{MIPS_REG_RA, W23},
};
if (jo_->useStaticAlloc) {
count = ARRAY_SIZE(allocs);
return allocs;
} else {
count = 0;
return nullptr;
}
}
void Arm64RegCache::EmitLoadStaticRegisters() {
int count;
const StaticAllocation *allocs = GetStaticAllocations(count);
// TODO: Use LDP when possible.
for (int i = 0; i < count; i++) {
int offset = GetMipsRegOffset(allocs[i].mr);
emit_->LDR(INDEX_UNSIGNED, allocs[i].ar, CTXREG, offset);
if (allocs[i].pointerified && jo_->enablePointerify) {
emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32);
}
}
}
void Arm64RegCache::EmitSaveStaticRegisters() {
int count;
const StaticAllocation *allocs = GetStaticAllocations(count);
// TODO: Use STP when possible.
// 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_->STR(INDEX_UNSIGNED, allocs[i].ar, CTXREG, offset);
}
}
void Arm64RegCache::FlushBeforeCall() {
// These registers are not preserved by function calls.
for (int i = 0; i < 19; ++i) {
FlushArmReg(ARM64Reg(W0 + i));
}
FlushArmReg(W30);
}
bool Arm64RegCache::IsInRAM(MIPSGPReg reg) {
return mr[reg].loc == ML_MEM;
}
bool Arm64RegCache::IsMapped(MIPSGPReg mipsReg) {
return mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM;
}
bool Arm64RegCache::IsMappedAsPointer(MIPSGPReg mipsReg) {
if (mr[mipsReg].loc == ML_ARMREG) {
return ar[mr[mipsReg].reg].pointerified;
} else if (mr[mipsReg].loc == ML_ARMREG_IMM) {
if (ar[mr[mipsReg].reg].pointerified) {
ERROR_LOG(Log::JIT, "Really shouldn't be pointerified here");
}
} else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
return true;
}
return false;
}
void Arm64RegCache::MarkDirty(ARM64Reg reg) {
ar[reg].isDirty = true;
}
void Arm64RegCache::SetRegImm(ARM64Reg reg, u64 imm) {
if (reg == INVALID_REG || reg == W0 || reg == X0) {
ERROR_LOG(Log::JIT, "SetRegImm to invalid register: at %08x", js_->compilerPC);
return;
}
_dbg_assert_(!ar[reg].tempLocked);
// On ARM64, at least Cortex A57, good old MOVT/MOVW (MOVK in 64-bit) is really fast.
emit_->MOVI2R(reg, imm);
// ar[reg].pointerified = false;
}
void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) {
if (mr[mipsReg].isStatic) {
ERROR_LOG(Log::JIT, "Cannot MapRegTo static register %d", mipsReg);
return;
}
ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false;
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
if (mipsReg == MIPS_REG_ZERO) {
// If we get a request to map the zero register, at least we won't spend
// time on a memory access...
emit_->MOVI2R(reg, 0);
// This way, if we SetImm() it, we'll keep it.
mr[mipsReg].loc = ML_ARMREG_IMM;
mr[mipsReg].imm = 0;
} else {
switch (mr[mipsReg].loc) {
case ML_MEM:
{
int offset = GetMipsRegOffset(mipsReg);
ARM64Reg loadReg = reg;
// INFO_LOG(Log::JIT, "MapRegTo %d mips: %d offset %d", (int)reg, mipsReg, offset);
if (mipsReg == MIPS_REG_LO) {
loadReg = EncodeRegTo64(loadReg);
}
// TODO: Scan ahead / hint when loading multiple regs?
// We could potentially LDP if mipsReg + 1 or mipsReg - 1 is needed.
emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, offset);
mr[mipsReg].loc = ML_ARMREG;
break;
}
case ML_IMM:
emit_->MOVI2R(reg, mr[mipsReg].imm);
ar[reg].isDirty = true; // IMM is always dirty.
// If we are mapping dirty, it means we're gonna overwrite.
// So the imm value is no longer valid.
if (mapFlags & MAP_DIRTY)
mr[mipsReg].loc = ML_ARMREG;
else
mr[mipsReg].loc = ML_ARMREG_IMM;
break;
default:
_assert_msg_(mr[mipsReg].loc != ML_ARMREG_AS_PTR, "MapRegTo with a pointer?");
mr[mipsReg].loc = ML_ARMREG;
break;
}
}
} else {
mr[mipsReg].loc = ML_ARMREG;
}
ar[reg].mipsReg = mipsReg;
ar[reg].pointerified = false;
mr[mipsReg].reg = reg;
}
ARM64Reg Arm64RegCache::AllocateReg() {
int allocCount;
const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount);
allocate:
for (int i = 0; i < allocCount; i++) {
ARM64Reg reg = allocOrder[i];
if (ar[reg].mipsReg == MIPS_REG_INVALID && !ar[reg].tempLocked) {
return reg;
}
}
// Still nothing. Let's spill a reg and goto 10.
// TODO: Use age or something to choose which register to spill?
// TODO: Spill dirty regs first? or opposite?
bool clobbered;
ARM64Reg bestToSpill = FindBestToSpill(true, &clobbered);
if (bestToSpill == INVALID_REG) {
bestToSpill = FindBestToSpill(false, &clobbered);
}
if (bestToSpill != INVALID_REG) {
if (clobbered) {
DiscardR(ar[bestToSpill].mipsReg);
} else {
FlushArmReg(bestToSpill);
}
// Now one must be free.
goto allocate;
}
// Uh oh, we have all of them spilllocked....
ERROR_LOG_REPORT(Log::JIT, "Out of spillable registers at PC %08x!!!", mips_->pc);
return INVALID_REG;
}
ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
int allocCount;
const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount);
static const int UNUSED_LOOKAHEAD_OPS = 30;
*clobbered = false;
for (int i = 0; i < allocCount; i++) {
ARM64Reg reg = allocOrder[i];
if (ar[reg].mipsReg != MIPS_REG_INVALID && mr[ar[reg].mipsReg].spillLock)
continue;
if (ar[reg].tempLocked)
continue;
// As it's in alloc-order, we know it's not static so we don't need to check for that.
// Awesome, a clobbered reg. Let's use it.
if (MIPSAnalyst::IsRegisterClobbered(ar[reg].mipsReg, compilerPC_, UNUSED_LOOKAHEAD_OPS)) {
bool canClobber = true;
// HI is stored inside the LO reg. They both have to clobber at the same time.
if (ar[reg].mipsReg == MIPS_REG_LO) {
canClobber = MIPSAnalyst::IsRegisterClobbered(MIPS_REG_HI, compilerPC_, UNUSED_LOOKAHEAD_OPS);
}
if (canClobber) {
*clobbered = true;
return reg;
}
}
// Not awesome. A used reg. Let's try to avoid spilling.
if (unusedOnly && MIPSAnalyst::IsRegisterUsed(ar[reg].mipsReg, compilerPC_, UNUSED_LOOKAHEAD_OPS)) {
continue;
}
return reg;
}
return INVALID_REG;
}
ARM64Reg Arm64RegCache::TryMapTempImm(MIPSGPReg r) {
// If already mapped, no need for a temporary.
if (IsMapped(r)) {
return R(r);
}
if (mr[r].loc == ML_IMM) {
if (mr[r].imm == 0) {
return WZR;
}
// Try our luck - check for an exact match in another armreg.
for (int i = 0; i < NUM_MIPSREG; ++i) {
if (mr[i].loc == ML_ARMREG_IMM && mr[i].imm == mr[r].imm) {
// Awesome, let's just use this reg.
return mr[i].reg;
}
}
}
return INVALID_REG;
}
ARM64Reg Arm64RegCache::GetAndLockTempR() {
ARM64Reg reg = AllocateReg();
if (reg != INVALID_REG) {
ar[reg].tempLocked = true;
}
return reg;
}
// TODO: Somewhat smarter spilling - currently simply spills the first available, should do
// round robin or FIFO or something.
ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) {
if (mipsReg == MIPS_REG_HI) {
ERROR_LOG_REPORT(Log::JIT, "Cannot map HI in Arm64RegCache");
return INVALID_REG;
}
if (mipsReg == MIPS_REG_INVALID) {
ERROR_LOG(Log::JIT, "Cannot map invalid register");
return INVALID_REG;
}
ARM64Reg armReg = mr[mipsReg].reg;
if (mr[mipsReg].isStatic) {
if (armReg == INVALID_REG) {
ERROR_LOG(Log::JIT, "MapReg on statically mapped reg %d failed - armReg got lost", mipsReg);
}
if (mr[mipsReg].loc == ML_IMM) {
// Back into the register, with or without the imm value.
// If noinit, the MAP_DIRTY check below will take care of the rest.
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
SetRegImm(armReg, mr[mipsReg].imm);
mr[mipsReg].loc = ML_ARMREG_IMM;
ar[armReg].pointerified = false;
}
} else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
// Was mapped as pointer, now we want it mapped as a value, presumably to
// add or subtract stuff to it.
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG);
}
mr[mipsReg].loc = ML_ARMREG;
}
// Erasing the imm on dirty (necessary since otherwise we will still think it's ML_ARMREG_IMM and return
// true for IsImm and calculate crazily wrong things). /unknown
if (mapFlags & MAP_DIRTY) {
mr[mipsReg].loc = ML_ARMREG; // As we are dirty, can't keep ARMREG_IMM, we will quickly drift out of sync
ar[armReg].pointerified = false;
ar[armReg].isDirty = true; // Not that it matters
}
return mr[mipsReg].reg;
}
// Let's see if it's already mapped. If so we just need to update the dirty flag.
// We don't need to check for ML_NOINIT because we assume that anyone who maps
// with that flag immediately writes a "known" value to the register.
if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) {
if (ar[armReg].mipsReg != mipsReg) {
ERROR_LOG_REPORT(Log::JIT, "Register mapping out of sync! %i", mipsReg);
}
if (mapFlags & MAP_DIRTY) {
// Mapping dirty means the old imm value is invalid.
mr[mipsReg].loc = ML_ARMREG;
ar[armReg].isDirty = true;
// If reg is written to, pointerification is lost.
ar[armReg].pointerified = false;
}
return mr[mipsReg].reg;
} else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
// Was mapped as pointer, now we want it mapped as a value, presumably to
// add or subtract stuff to it.
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG);
}
mr[mipsReg].loc = ML_ARMREG;
if (mapFlags & MAP_DIRTY) {
ar[armReg].isDirty = true;
}
return (ARM64Reg)mr[mipsReg].reg;
}
// Okay, not mapped, so we need to allocate an ARM register.
ARM64Reg reg = AllocateReg();
if (reg != INVALID_REG) {
// Grab it, and load the value into it (if requested).
MapRegTo(reg, mipsReg, mapFlags);
}
return reg;
}
Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) {
// Already mapped.
if (mr[reg].loc == ML_ARMREG_AS_PTR) {
return mr[reg].reg;
}
ARM64Reg retval = INVALID_REG;
if (mr[reg].loc != ML_ARMREG && mr[reg].loc != ML_ARMREG_IMM) {
retval = MapReg(reg);
} else {
retval = mr[reg].reg;
}
if (mr[reg].loc == ML_ARMREG || mr[reg].loc == ML_ARMREG_IMM) {
// If there was an imm attached, discard it.
mr[reg].loc = ML_ARMREG;
ARM64Reg a = DecodeReg(mr[reg].reg);
if (!jo_->enablePointerify) {
// Convert to a pointer by adding the base and clearing off the top bits.
// If SP, we can probably avoid the top bit clear, let's play with that later.
#ifdef MASKED_PSP_MEMORY
emit_->ANDI2R(EncodeRegTo64(a), EncodeRegTo64(a), 0x3FFFFFFF);
#endif
emit_->ADD(EncodeRegTo64(a), EncodeRegTo64(a), MEMBASEREG);
mr[reg].loc = ML_ARMREG_AS_PTR;
} else if (!ar[a].pointerified) {
emit_->MOVK(EncodeRegTo64(a), ((uint64_t)Memory::base) >> 32, SHIFT_32);
ar[a].pointerified = true;
}
} else {
ERROR_LOG(Log::JIT, "MapRegAsPointer : MapReg failed to allocate a register?");
}
return retval;
}
void Arm64RegCache::MapIn(MIPSGPReg rs) {
MapReg(rs);
}
void Arm64RegCache::MapInIn(MIPSGPReg rd, MIPSGPReg rs) {
SpillLock(rd, rs);
MapReg(rd);
MapReg(rs);
ReleaseSpillLock(rd, rs);
}
void Arm64RegCache::MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad) {
SpillLock(rd, rs);
bool load = !avoidLoad || rd == rs;
MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
MapReg(rs);
ReleaseSpillLock(rd, rs);
}
void Arm64RegCache::MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) {
SpillLock(rd, rs, rt);
bool load = !avoidLoad || (rd == rs || rd == rt);
MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
MapReg(rt);
MapReg(rs);
ReleaseSpillLock(rd, rs, rt);
}
void Arm64RegCache::MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, bool avoidLoad) {
SpillLock(rd1, rd2, rs);
bool load1 = !avoidLoad || rd1 == rs;
bool load2 = !avoidLoad || rd2 == rs;
MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT);
MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT);
MapReg(rs);
ReleaseSpillLock(rd1, rd2, rs);
}
void Arm64RegCache::MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) {
SpillLock(rd1, rd2, rs, rt);
bool load1 = !avoidLoad || (rd1 == rs || rd1 == rt);
bool load2 = !avoidLoad || (rd2 == rs || rd2 == rt);
MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT);
MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT);
MapReg(rt);
MapReg(rs);
ReleaseSpillLock(rd1, rd2, rs, rt);
}
void Arm64RegCache::FlushArmReg(ARM64Reg r) {
if (r == INVALID_REG) {
ERROR_LOG(Log::JIT, "FlushArmReg called on invalid register %d", r);
return;
}
if (ar[r].mipsReg == MIPS_REG_INVALID) {
// Nothing to do, reg not mapped.
if (ar[r].isDirty) {
ERROR_LOG_REPORT(Log::JIT, "Dirty but no mipsreg?");
}
return;
}
if (mr[ar[r].mipsReg].isStatic) {
ERROR_LOG(Log::JIT, "Cannot FlushArmReg a statically mapped register");
return;
}
auto &mreg = mr[ar[r].mipsReg];
if (mreg.loc == ML_ARMREG_IMM || ar[r].mipsReg == MIPS_REG_ZERO) {
// We know its immediate value, no need to STR now.
mreg.loc = ML_IMM;
mreg.reg = INVALID_REG;
} else {
if (mreg.loc == ML_IMM || ar[r].isDirty) {
if (mreg.loc == ML_ARMREG_AS_PTR) {
// Unpointerify, in case dirty.
emit_->SUB(EncodeRegTo64(r), EncodeRegTo64(r), MEMBASEREG);
mreg.loc = ML_ARMREG;
}
// Note: may be a 64-bit reg.
ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg);
if (storeReg != INVALID_REG)
emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[r].mipsReg));
}
mreg.loc = ML_MEM;
mreg.reg = INVALID_REG;
mreg.imm = 0;
}
ar[r].isDirty = false;
ar[r].mipsReg = MIPS_REG_INVALID;
ar[r].pointerified = false;
}
void Arm64RegCache::DiscardR(MIPSGPReg mipsReg) {
if (mr[mipsReg].isStatic) {
// Simply do nothing unless it's an IMM/ARMREG_IMM/ARMREG_AS_PTR, in case we just switch it over to ARMREG, losing the value.
ARM64Reg armReg = mr[mipsReg].reg;
if (mr[mipsReg].loc == ML_ARMREG_IMM || mr[mipsReg].loc == ML_IMM || mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
// Ignore the imm value, restore sanity
mr[mipsReg].loc = ML_ARMREG;
ar[armReg].pointerified = false;
ar[armReg].isDirty = false;
}
return;
}
const RegMIPSLoc prevLoc = mr[mipsReg].loc;
if (prevLoc == ML_ARMREG || prevLoc == ML_ARMREG_IMM || prevLoc == ML_ARMREG_AS_PTR) {
ARM64Reg armReg = mr[mipsReg].reg;
ar[armReg].isDirty = false;
ar[armReg].mipsReg = MIPS_REG_INVALID;
ar[armReg].pointerified = false;
mr[mipsReg].reg = INVALID_REG;
if (mipsReg == MIPS_REG_ZERO) {
mr[mipsReg].loc = ML_IMM;
} else {
mr[mipsReg].loc = ML_MEM;
}
mr[mipsReg].imm = 0;
}
if (prevLoc == ML_IMM && mipsReg != MIPS_REG_ZERO) {
mr[mipsReg].loc = ML_MEM;
mr[mipsReg].imm = 0;
}
}
ARM64Reg Arm64RegCache::ARM64RegForFlush(MIPSGPReg r) {
if (mr[r].isStatic)
return INVALID_REG; // No flushing needed
switch (mr[r].loc) {
case ML_IMM:
if (r == MIPS_REG_ZERO) {
return INVALID_REG;
}
// Zero is super easy.
if (mr[r].imm == 0) {
return WZR;
}
// Could we get lucky? Check for an exact match in another armreg.
for (int i = 0; i < NUM_MIPSREG; ++i) {
if (mr[i].loc == ML_ARMREG_IMM && mr[i].imm == mr[r].imm) {
// Awesome, let's just store this reg.
return mr[i].reg;
}
}
return INVALID_REG;
case ML_ARMREG:
case ML_ARMREG_IMM:
if (mr[r].reg == INVALID_REG) {
ERROR_LOG_REPORT(Log::JIT, "ARM64RegForFlush: MipsReg %d had bad ArmReg", r);
return INVALID_REG;
}
// No need to flush if it's zero or not dirty.
if (r == MIPS_REG_ZERO || !ar[mr[r].reg].isDirty) {
return INVALID_REG;
}
if (r == MIPS_REG_LO) {
return EncodeRegTo64(mr[r].reg);
}
return mr[r].reg;
case ML_ARMREG_AS_PTR:
return INVALID_REG;
case ML_MEM:
return INVALID_REG;
default:
ERROR_LOG_REPORT(Log::JIT, "ARM64RegForFlush: MipsReg %d with invalid location %d", r, mr[r].loc);
return INVALID_REG;
}
}
void Arm64RegCache::FlushR(MIPSGPReg r) {
if (mr[r].isStatic) {
ERROR_LOG(Log::JIT, "Cannot flush static reg %d", r);
return;
}
switch (mr[r].loc) {
case ML_IMM:
// IMM is always "dirty".
if (r == MIPS_REG_LO) {
emit_->MOVI2R(SCRATCH1_64, mr[r].imm);
emit_->STR(INDEX_UNSIGNED, SCRATCH1_64, CTXREG, GetMipsRegOffset(r));
} else if (r != MIPS_REG_ZERO) {
// Try to optimize using a different reg.
ARM64Reg storeReg = ARM64RegForFlush(r);
if (storeReg == INVALID_REG) {
emit_->MOVI2R(SCRATCH1, mr[r].imm);
storeReg = SCRATCH1;
}
emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(r));
}
break;
case ML_ARMREG:
case ML_ARMREG_IMM:
if (ar[mr[r].reg].isDirty) {
// Note: might be a 64-bit reg.
ARM64Reg storeReg = ARM64RegForFlush(r);
if (storeReg != INVALID_REG) {
emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(r));
}
ar[mr[r].reg].isDirty = false;
}
ar[mr[r].reg].mipsReg = MIPS_REG_INVALID;
ar[mr[r].reg].pointerified = false;
break;
case ML_ARMREG_AS_PTR:
if (ar[mr[r].reg].isDirty) {
emit_->SUB(EncodeRegTo64(mr[r].reg), EncodeRegTo64(mr[r].reg), MEMBASEREG);
// We set this so ARM64RegForFlush knows it's no longer a pointer.
mr[r].loc = ML_ARMREG;
ARM64Reg storeReg = ARM64RegForFlush(r);
if (storeReg != INVALID_REG) {
emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(r));
}
ar[mr[r].reg].isDirty = false;
}
ar[mr[r].reg].mipsReg = MIPS_REG_INVALID;
break;
case ML_MEM:
// Already there, nothing to do.
break;
default:
ERROR_LOG_REPORT(Log::JIT, "FlushR: MipsReg %d with invalid location %d", r, mr[r].loc);
break;
}
if (r == MIPS_REG_ZERO) {
mr[r].loc = ML_IMM;
} else {
mr[r].loc = ML_MEM;
}
mr[r].reg = INVALID_REG;
mr[r].imm = 0;
}
void Arm64RegCache::FlushAll() {
// Note: make sure not to change the registers when flushing:
// Branching code expects the armreg to retain its value.
// LO can't be included in a 32-bit pair, since it's 64 bit.
// Flush it first so we don't get it confused.
FlushR(MIPS_REG_LO);
// Try to flush in pairs when possible.
// 1 because MIPS_REG_ZERO isn't flushable anyway.
// 31 because 30 and 31 are the last possible pair - MIPS_REG_FPCOND, etc. are too far away.
for (int i = 1; i < 31; i++) {
MIPSGPReg mreg1 = MIPSGPReg(i);
MIPSGPReg mreg2 = MIPSGPReg(i + 1);
ARM64Reg areg1 = ARM64RegForFlush(mreg1);
ARM64Reg areg2 = ARM64RegForFlush(mreg2);
// If either one doesn't have a reg yet, try flushing imms to scratch regs.
if (areg1 == INVALID_REG && IsPureImm(mreg1) && !mr[i].isStatic) {
areg1 = SCRATCH1;
}
if (areg2 == INVALID_REG && IsPureImm(mreg2) && !mr[i + 1].isStatic) {
areg2 = SCRATCH2;
}
if (areg1 != INVALID_REG && areg2 != INVALID_REG) {
// Actually put the imms in place now that we know we can do the STP.
// We didn't do it before in case the other wouldn't work.
if (areg1 == SCRATCH1) {
SetRegImm(areg1, GetImm(mreg1));
}
if (areg2 == SCRATCH2) {
SetRegImm(areg2, GetImm(mreg2));
}
// We can use a paired store, awesome.
emit_->STP(INDEX_SIGNED, areg1, areg2, CTXREG, GetMipsRegOffset(mreg1));
// Now we mark them as stored by discarding.
DiscardR(mreg1);
DiscardR(mreg2);
}
}
// Final pass to grab any that were left behind.
for (int i = 0; i < NUM_MIPSREG; i++) {
MIPSGPReg mipsReg = MIPSGPReg(i);
if (mr[i].isStatic) {
Arm64Gen::ARM64Reg armReg = mr[i].reg;
// Cannot leave any IMMs in registers, not even ML_ARMREG_IMM, can confuse the regalloc later if this flush is mid-block
// due to an interpreter fallback that changes the register.
if (mr[i].loc == ML_IMM) {
emit_->MOVI2R(mr[i].reg, mr[i].imm);
mr[i].loc = ML_ARMREG;
ar[armReg].pointerified = false;
} else if (mr[i].loc == ML_ARMREG_IMM) {
// The register already contains the immediate.
if (ar[armReg].pointerified) {
ERROR_LOG(Log::JIT, "ML_ARMREG_IMM but pointerified. Wrong.");
ar[armReg].pointerified = false;
}
mr[i].loc = ML_ARMREG;
} else if (mr[i].loc == ML_ARMREG_AS_PTR) {
emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG);
mr[i].loc = ML_ARMREG;
}
if (i != MIPS_REG_ZERO && mr[i].reg == INVALID_REG) {
ERROR_LOG(Log::JIT, "ARM reg of static %i is invalid", i);
continue;
}
} else {
FlushR(mipsReg);
}
}
int count = 0;
const StaticAllocation *allocs = GetStaticAllocations(count);
for (int i = 0; i < count; i++) {
if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified && jo_->enablePointerify) {
// Re-pointerify
emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32);
ar[allocs[i].ar].pointerified = true;
} else if (!allocs[i].pointerified) {
// If this register got pointerified on the way, mark it as not, so that after save/reload (like in an interpreter fallback), it won't be regarded as such, as it simply won't be.
ar[allocs[i].ar].pointerified = false;
}
}
// Sanity check
for (int i = 0; i < NUM_ARMREG; i++) {
if (ar[i].mipsReg != MIPS_REG_INVALID && mr[ar[i].mipsReg].isStatic == false) {
ERROR_LOG_REPORT(Log::JIT, "Flush fail: ar[%i].mipsReg=%i", i, ar[i].mipsReg);
}
}
}
void Arm64RegCache::SetImm(MIPSGPReg r, u64 immVal) {
if (r == MIPS_REG_HI) {
ERROR_LOG_REPORT(Log::JIT, "Cannot set HI imm in Arm64RegCache");
return;
}
if (r == MIPS_REG_ZERO && immVal != 0) {
ERROR_LOG_REPORT(Log::JIT, "Trying to set immediate %08x to r0 at %08x", (u32)immVal, compilerPC_);
return;
}
if (mr[r].loc == ML_ARMREG_IMM && mr[r].imm == immVal) {
// Already have that value, let's keep it in the reg.
return;
}
if (r != MIPS_REG_LO) {
// All regs on the PSP are 32 bit, but LO we treat as HI:LO so is 64 full bits.
immVal = immVal & 0xFFFFFFFF;
}
if (mr[r].isStatic) {
mr[r].loc = ML_IMM;
mr[r].imm = immVal;
ar[mr[r].reg].pointerified = false;
// We do not change reg to INVALID_REG for obvious reasons..
} else {
// Zap existing value if cached in a reg
if (mr[r].reg != INVALID_REG) {
ar[mr[r].reg].mipsReg = MIPS_REG_INVALID;
ar[mr[r].reg].isDirty = false;
ar[mr[r].reg].pointerified = false;
}
mr[r].loc = ML_IMM;
mr[r].imm = immVal;
mr[r].reg = INVALID_REG;
}
}
bool Arm64RegCache::IsImm(MIPSGPReg r) const {
if (r == MIPS_REG_ZERO)
return true;
else
return mr[r].loc == ML_IMM || mr[r].loc == ML_ARMREG_IMM;
}
bool Arm64RegCache::IsPureImm(MIPSGPReg r) const {
if (r == MIPS_REG_ZERO)
return true;
else
return mr[r].loc == ML_IMM;
}
u64 Arm64RegCache::GetImm(MIPSGPReg r) const {
if (r == MIPS_REG_ZERO)
return 0;
if (mr[r].loc != ML_IMM && mr[r].loc != ML_ARMREG_IMM) {
ERROR_LOG_REPORT(Log::JIT, "Trying to get imm from non-imm register %i", r);
}
return mr[r].imm;
}
int Arm64RegCache::GetMipsRegOffset(MIPSGPReg r) {
if (r < 32)
return r * 4;
switch (r) {
case MIPS_REG_HI:
return offsetof(MIPSState, hi);
case MIPS_REG_LO:
return offsetof(MIPSState, lo);
case MIPS_REG_FPCOND:
return offsetof(MIPSState, fpcond);
case MIPS_REG_VFPUCC:
return offsetof(MIPSState, vfpuCtrl[VFPU_CTRL_CC]);
default:
ERROR_LOG_REPORT(Log::JIT, "bad mips register %i", r);
return 0; // or what?
}
}
void Arm64RegCache::SpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPReg r4) {
mr[r1].spillLock = true;
if (r2 != MIPS_REG_INVALID) mr[r2].spillLock = true;
if (r3 != MIPS_REG_INVALID) mr[r3].spillLock = true;
if (r4 != MIPS_REG_INVALID) mr[r4].spillLock = true;
}
void Arm64RegCache::ReleaseSpillLocksAndDiscardTemps() {
for (int i = 0; i < NUM_MIPSREG; i++) {
if (!mr[i].isStatic)
mr[i].spillLock = false;
}
for (int i = 0; i < NUM_ARMREG; i++) {
ar[i].tempLocked = false;
}
}
void Arm64RegCache::ReleaseSpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPReg r4) {
if (!mr[r1].isStatic)
mr[r1].spillLock = false;
if (r2 != MIPS_REG_INVALID && !mr[r2].isStatic)
mr[r2].spillLock = false;
if (r3 != MIPS_REG_INVALID && !mr[r3].isStatic)
mr[r3].spillLock = false;
if (r4 != MIPS_REG_INVALID && !mr[r4].isStatic)
mr[r4].spillLock = false;
}
ARM64Reg Arm64RegCache::R(MIPSGPReg mipsReg) {
if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) {
return mr[mipsReg].reg;
} else {
ERROR_LOG_REPORT(Log::JIT, "Reg %i not in arm reg. compilerPC = %08x", mipsReg, compilerPC_);
return INVALID_REG; // BAAAD
}
}
ARM64Reg Arm64RegCache::RPtr(MIPSGPReg mipsReg) {
if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) {
return (ARM64Reg)mr[mipsReg].reg;
} else if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) {
int a = mr[mipsReg].reg;
if (ar[a].pointerified) {
return (ARM64Reg)mr[mipsReg].reg;
} 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 arm reg. compilerPC = %08x", mipsReg, compilerPC_);
return INVALID_REG; // BAAAD
}
}
#endif // PPSSPP_ARCH(ARM64)
-168
View File
@@ -1,168 +0,0 @@
// Copyright (c) 2012- 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 "Core/MIPS/MIPS.h"
#include "Common/Arm64Emitter.h"
namespace Arm64JitConstants {
const Arm64Gen::ARM64Reg DOWNCOUNTREG = Arm64Gen::W24;
const Arm64Gen::ARM64Reg FLAGTEMPREG = Arm64Gen::X25;
const Arm64Gen::ARM64Reg JITBASEREG = Arm64Gen::X26;
const Arm64Gen::ARM64Reg CTXREG = Arm64Gen::X27;
const Arm64Gen::ARM64Reg MEMBASEREG = Arm64Gen::X28;
const Arm64Gen::ARM64Reg SCRATCH1_64 = Arm64Gen::X16;
const Arm64Gen::ARM64Reg SCRATCH2_64 = Arm64Gen::X17;
const Arm64Gen::ARM64Reg SCRATCH1 = Arm64Gen::W16;
const Arm64Gen::ARM64Reg SCRATCH2 = Arm64Gen::W17;
enum {
TOTAL_MAPPABLE_MIPSREGS = 36,
};
enum RegMIPSLoc {
ML_IMM,
ML_ARMREG,
// In an arm reg, but an adjusted pointer (not pointerified - unaligned.)
ML_ARMREG_AS_PTR,
// In an arm reg, but also has a known immediate value.
ML_ARMREG_IMM,
ML_MEM,
};
// Initing is the default so the flag is reversed.
enum {
MAP_DIRTY = 1,
MAP_NOINIT = 2 | MAP_DIRTY,
};
} // namespace
typedef int MIPSReg;
struct RegARM64 {
MIPSGPReg mipsReg; // if -1, no mipsreg attached.
bool isDirty; // Should the register be written back?
bool pointerified; // Has used movk to move the memory base into the top part of the reg. Note - still usable as 32-bit reg!
bool tempLocked; // Reserved for a temp register.
};
struct RegMIPS {
// Where is this MIPS register?
Arm64JitConstants::RegMIPSLoc loc;
// Data (both or only one may be used, depending on loc.)
u64 imm;
Arm64Gen::ARM64Reg reg; // reg index
bool spillLock; // if true, this register cannot be spilled.
bool isStatic; // if true, this register will not be written back to ram by the regcache
// If loc == ML_MEM, it's back in its location in the CPU context struct.
};
namespace MIPSComp {
struct JitOptions;
struct JitState;
}
class Arm64RegCache {
public:
Arm64RegCache(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo);
~Arm64RegCache() {}
void Init(Arm64Gen::ARM64XEmitter *emitter);
void Start();
// Protect the arm register containing a MIPS register from spilling, to ensure that
// it's being kept allocated.
void SpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID);
void ReleaseSpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID);
void ReleaseSpillLocksAndDiscardTemps();
void SetImm(MIPSGPReg reg, u64 immVal);
bool IsImm(MIPSGPReg reg) const;
bool IsPureImm(MIPSGPReg reg) const;
u64 GetImm(MIPSGPReg reg) const;
void SetRegImm(Arm64Gen::ARM64Reg reg, u64 imm);
// May fail and return INVALID_REG if it needs flushing.
Arm64Gen::ARM64Reg TryMapTempImm(MIPSGPReg);
// Returns an ARM register containing the requested MIPS register.
Arm64Gen::ARM64Reg MapReg(MIPSGPReg reg, int mapFlags = 0);
Arm64Gen::ARM64Reg MapRegAsPointer(MIPSGPReg reg);
bool IsMapped(MIPSGPReg reg);
bool IsMappedAsPointer(MIPSGPReg reg);
bool IsInRAM(MIPSGPReg reg);
void MarkDirty(Arm64Gen::ARM64Reg reg);
void MapIn(MIPSGPReg rs);
void MapInIn(MIPSGPReg rd, MIPSGPReg rs);
void MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad = true);
void MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad = true);
void MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, bool avoidLoad = true);
void MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad = true);
void FlushArmReg(Arm64Gen::ARM64Reg r);
void FlushBeforeCall();
void FlushAll();
void FlushR(MIPSGPReg r);
void DiscardR(MIPSGPReg r);
Arm64Gen::ARM64Reg GetAndLockTempR();
Arm64Gen::ARM64Reg R(MIPSGPReg preg); // Returns a cached register, while checking that it's NOT mapped as a pointer
Arm64Gen::ARM64Reg RPtr(MIPSGPReg preg); // Returns a cached register, if it has been mapped as a pointer
void SetEmitter(Arm64Gen::ARM64XEmitter *emitter) { emit_ = emitter; }
// For better log output only.
void SetCompilerPC(u32 compilerPC) { compilerPC_ = compilerPC; }
int GetMipsRegOffset(MIPSGPReg r);
// These are called once on startup to generate functions, that you should then call.
void EmitLoadStaticRegisters();
void EmitSaveStaticRegisters();
private:
struct StaticAllocation {
MIPSGPReg mr;
Arm64Gen::ARM64Reg ar;
bool pointerified;
};
const StaticAllocation *GetStaticAllocations(int &count);
const Arm64Gen::ARM64Reg *GetMIPSAllocationOrder(int &count);
void MapRegTo(Arm64Gen::ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags);
Arm64Gen::ARM64Reg AllocateReg();
Arm64Gen::ARM64Reg FindBestToSpill(bool unusedOnly, bool *clobbered);
Arm64Gen::ARM64Reg ARM64RegForFlush(MIPSGPReg r);
MIPSState *mips_;
Arm64Gen::ARM64XEmitter *emit_;
MIPSComp::JitState *js_;
MIPSComp::JitOptions *jo_;
u32 compilerPC_;
enum {
NUM_ARMREG = 32, // 31 actual registers, plus the zero/sp register which is not mappable.
NUM_MIPSREG = Arm64JitConstants::TOTAL_MAPPABLE_MIPSREGS,
};
RegARM64 ar[NUM_ARMREG];
RegMIPS mr[NUM_MIPSREG];
};
-515
View File
@@ -1,515 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include <cstring>
#include "Common/CPUDetect.h"
#include "Common/Log.h"
#include "Core/Reporting.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/ARM64/Arm64RegCacheFPU.h"
#include "Core/MIPS/ARM64/Arm64Jit.h"
#include "Core/MIPS/MIPSTables.h"
using namespace Arm64Gen;
using namespace Arm64JitConstants;
Arm64RegCacheFPU::Arm64RegCacheFPU(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo) : mips_(mipsState), vr(mr + 32), js_(js), jo_(jo) {
numARMFpuReg_ = 32;
}
void Arm64RegCacheFPU::Init(Arm64Gen::ARM64XEmitter *emit, Arm64Gen::ARM64FloatEmitter *fp) {
emit_ = emit;
fp_ = fp;
}
void Arm64RegCacheFPU::Start() {
if (!initialReady) {
SetupInitialRegs();
initialReady = true;
}
memcpy(ar, arInitial, sizeof(ar));
memcpy(mr, mrInitial, sizeof(mr));
pendingFlush = false;
}
void Arm64RegCacheFPU::SetupInitialRegs() {
for (int i = 0; i < numARMFpuReg_; i++) {
arInitial[i].mipsReg = -1;
arInitial[i].isDirty = false;
}
for (int i = 0; i < NUM_MIPSFPUREG; i++) {
mrInitial[i].loc = ML_MEM;
mrInitial[i].reg = INVALID_REG;
mrInitial[i].spillLock = false;
mrInitial[i].tempLock = false;
}
}
const ARM64Reg *Arm64RegCacheFPU::GetMIPSAllocationOrder(int &count) {
// On ARM64, all 32 registers are fully 128-bit and fully interchangable so we don't
// have to care about upper or lower registers. However, only S8-S15 are callee-save, and
// only the bottom 64 bits of those. So we should allocate into these when we call
// C functions, although we don't currently do so...
static const ARM64Reg allocationOrder[] = {
// Reserve four full 128-bit temp registers, should be plenty.
S4, S5, S6, S7,
S8, S9, S10, S11, // Partially callee-save (bottom 64 bits)
S12, S13, S14, S15, // Partially callee-save (bottom 64 bits)
S16, S17, S18, S19,
S20, S21, S22, S23,
S24, S25, S26, S27,
S28, S29, S30, S31,
};
count = sizeof(allocationOrder) / sizeof(const ARM64Reg);
return allocationOrder;
}
bool Arm64RegCacheFPU::IsMapped(MIPSReg r) {
return mr[r].loc == ML_ARMREG;
}
bool Arm64RegCacheFPU::IsInRAM(MIPSReg r) {
return mr[r].loc == ML_MEM;
}
ARM64Reg Arm64RegCacheFPU::MapReg(MIPSReg mipsReg, int mapFlags) {
pendingFlush = true;
// Let's see if it's already mapped. If so we just need to update the dirty flag.
// We don't need to check for ML_NOINIT because we assume that anyone who maps
// with that flag immediately writes a "known" value to the register.
if (mr[mipsReg].loc == ML_ARMREG) {
if (ar[mr[mipsReg].reg].mipsReg != mipsReg) {
ERROR_LOG(Log::JIT, "Reg mapping out of sync! MR %i", mipsReg);
}
if (mapFlags & MAP_DIRTY) {
ar[mr[mipsReg].reg].isDirty = true;
}
//INFO_LOG(Log::JIT, "Already mapped %i to %i", mipsReg, mr[mipsReg].reg);
return (ARM64Reg)(mr[mipsReg].reg + S0);
}
// Okay, not mapped, so we need to allocate an ARM register.
int allocCount;
const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount);
allocate:
for (int i = 0; i < allocCount; i++) {
int reg = DecodeReg(allocOrder[i]);
if (ar[reg].mipsReg == -1) {
// That means it's free. Grab it, and load the value into it (if requested).
ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false;
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
if (mr[mipsReg].loc == ML_MEM && mipsReg < TEMP0) {
fp_->LDR(32, INDEX_UNSIGNED, (ARM64Reg)(reg + S0), CTXREG, GetMipsRegOffset(mipsReg));
}
}
ar[reg].mipsReg = mipsReg;
mr[mipsReg].loc = ML_ARMREG;
mr[mipsReg].reg = reg;
//INFO_LOG(Log::JIT, "Mapped %i to %i", mipsReg, mr[mipsReg].reg);
return (ARM64Reg)(reg + S0);
}
}
// Still nothing. Let's spill a reg and goto 10.
// TODO: Use age or something to choose which register to spill?
// TODO: Spill dirty regs first? or opposite?
int bestToSpill = -1;
for (int i = 0; i < allocCount; i++) {
int reg = allocOrder[i] - S0;
if (ar[reg].mipsReg != -1 && (mr[ar[reg].mipsReg].spillLock || mr[ar[reg].mipsReg].tempLock))
continue;
bestToSpill = reg;
break;
}
if (bestToSpill != -1) {
FlushArmReg((ARM64Reg)(S0 + bestToSpill));
goto allocate;
}
// Uh oh, we have all them spilllocked....
ERROR_LOG(Log::JIT, "Out of spillable registers at PC %08x!!!", js_->compilerPC);
return INVALID_REG;
}
void Arm64RegCacheFPU::MapInIn(MIPSReg rd, MIPSReg rs) {
SpillLock(rd, rs);
MapReg(rd);
MapReg(rs);
ReleaseSpillLock(rd);
ReleaseSpillLock(rs);
}
void Arm64RegCacheFPU::MapDirtyIn(MIPSReg rd, MIPSReg rs, bool avoidLoad) {
SpillLock(rd, rs);
bool load = !avoidLoad || rd == rs;
MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
MapReg(rs);
ReleaseSpillLock(rd);
ReleaseSpillLock(rs);
}
void Arm64RegCacheFPU::MapDirtyInIn(MIPSReg rd, MIPSReg rs, MIPSReg rt, bool avoidLoad) {
SpillLock(rd, rs, rt);
bool load = !avoidLoad || (rd == rs || rd == rt);
MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT);
MapReg(rt);
MapReg(rs);
ReleaseSpillLock(rd);
ReleaseSpillLock(rs);
ReleaseSpillLock(rt);
}
void Arm64RegCacheFPU::SpillLockV(const u8 *v, VectorSize sz) {
for (int i = 0; i < GetNumVectorElements(sz); i++) {
vr[v[i]].spillLock = true;
}
}
void Arm64RegCacheFPU::SpillLockV(int vec, VectorSize sz) {
u8 v[4];
GetVectorRegs(v, sz, vec);
SpillLockV(v, sz);
}
void Arm64RegCacheFPU::MapRegV(int vreg, int flags) {
MapReg(vreg + 32, flags);
}
void Arm64RegCacheFPU::LoadToRegV(ARM64Reg armReg, int vreg) {
if (vr[vreg].loc == ML_ARMREG) {
fp_->FMOV(armReg, (ARM64Reg)(S0 + vr[vreg].reg));
} else {
MapRegV(vreg);
fp_->FMOV(armReg, V(vreg));
}
}
void Arm64RegCacheFPU::MapRegsAndSpillLockV(int vec, VectorSize sz, int flags) {
u8 v[4];
GetVectorRegs(v, sz, vec);
SpillLockV(v, sz);
for (int i = 0; i < GetNumVectorElements(sz); i++) {
MapRegV(v[i], flags);
}
}
void Arm64RegCacheFPU::MapRegsAndSpillLockV(const u8 *v, VectorSize sz, int flags) {
SpillLockV(v, sz);
for (int i = 0; i < GetNumVectorElements(sz); i++) {
MapRegV(v[i], flags);
}
}
void Arm64RegCacheFPU::MapInInV(int vs, int vt) {
SpillLockV(vs);
SpillLockV(vt);
MapRegV(vs);
MapRegV(vt);
ReleaseSpillLockV(vs);
ReleaseSpillLockV(vt);
}
void Arm64RegCacheFPU::MapDirtyInV(int vd, int vs, bool avoidLoad) {
bool load = !avoidLoad || (vd == vs);
SpillLockV(vd);
SpillLockV(vs);
MapRegV(vd, load ? MAP_DIRTY : MAP_NOINIT);
MapRegV(vs);
ReleaseSpillLockV(vd);
ReleaseSpillLockV(vs);
}
void Arm64RegCacheFPU::MapDirtyInInV(int vd, int vs, int vt, bool avoidLoad) {
bool load = !avoidLoad || (vd == vs || vd == vt);
SpillLockV(vd);
SpillLockV(vs);
SpillLockV(vt);
MapRegV(vd, load ? MAP_DIRTY : MAP_NOINIT);
MapRegV(vs);
MapRegV(vt);
ReleaseSpillLockV(vd);
ReleaseSpillLockV(vs);
ReleaseSpillLockV(vt);
}
void Arm64RegCacheFPU::FlushBeforeCall() {
// Only the bottom 64 bits of D8-D15 are callee-saved, which is all a single needs.
for (int i = 0; i < 32; i++) {
if (i < 8 || i > 15) {
FlushArmReg((ARM64Reg)(S0 + i));
}
}
}
void Arm64RegCacheFPU::FlushArmReg(ARM64Reg r) {
if (r >= S0 && r <= S31) {
int reg = r - S0;
if (ar[reg].mipsReg == -1) {
// Nothing to do, reg not mapped.
return;
}
if (ar[reg].mipsReg != -1) {
if (ar[reg].isDirty && mr[ar[reg].mipsReg].loc == ML_ARMREG){
//INFO_LOG(Log::JIT, "Flushing ARM reg %i", reg);
fp_->STR(32, INDEX_UNSIGNED, r, CTXREG, GetMipsRegOffset(ar[reg].mipsReg));
}
// IMMs won't be in an ARM reg.
mr[ar[reg].mipsReg].loc = ML_MEM;
mr[ar[reg].mipsReg].reg = INVALID_REG;
} else {
ERROR_LOG(Log::JIT, "Dirty but no mipsreg?");
}
ar[reg].mipsReg = -1;
ar[reg].isDirty = false;
}
}
void Arm64RegCacheFPU::FlushV(MIPSReg r) {
FlushR(r + 32);
}
void Arm64RegCacheFPU::FlushR(MIPSReg r) {
switch (mr[r].loc) {
case ML_IMM:
// IMM is always "dirty".
// IMM is not allowed for FP (yet).
ERROR_LOG(Log::JIT, "Imm in FP register?");
break;
case ML_ARMREG:
if (mr[r].reg == INVALID_REG) {
ERROR_LOG(Log::JIT, "FlushR: MipsReg had bad ArmReg");
}
FlushArmReg((ARM64Reg)(S0 + mr[r].reg));
break;
case ML_MEM:
// Already there, nothing to do.
break;
default:
//BAD
break;
}
}
Arm64Gen::ARM64Reg Arm64RegCacheFPU::ARM64RegForFlush(int r) {
switch (mr[r].loc) {
case ML_IMM:
// IMM is always "dirty".
// IMM is not allowed for FP (yet).
ERROR_LOG(Log::JIT, "Imm in FP register?");
return INVALID_REG;
case ML_ARMREG:
if (mr[r].reg == INVALID_REG) {
ERROR_LOG_REPORT(Log::JIT, "ARM64RegForFlush: MipsReg %d had bad ArmReg", r);
return INVALID_REG;
}
// No need to flush if it's not dirty.
if (!ar[mr[r].reg].isDirty) {
return INVALID_REG;
}
return (ARM64Reg)(S0 + mr[r].reg);
case ML_MEM:
return INVALID_REG;
default:
ERROR_LOG_REPORT(Log::JIT, "ARM64RegForFlush: MipsReg %d with invalid location %d", r, mr[r].loc);
return INVALID_REG;
}
}
void Arm64RegCacheFPU::FlushAll() {
if (!pendingFlush) {
// Nothing allocated. FPU regs are not nearly as common as GPR.
return;
}
// Discard temps!
for (int i = TEMP0; i < TEMP0 + NUM_TEMPS; i++) {
DiscardR(i);
}
int numArmRegs = 0;
const ARM64Reg *order = GetMIPSAllocationOrder(numArmRegs);
// Flush pairs first when possible. Note that STP's offset can't reach more than 256 bytes so
// most VFPU registers cannot be flushed this way, unless we are willing to generate another offset pointer
// (which we could actually do right here, point right in the middle of the VFPU stuff and would reach it all)... TODO
for (int i = 0; i < 31; i++) {
int mr1 = i;
int mr2 = i + 1;
ARM64Reg ar1 = ARM64RegForFlush(mr1);
ARM64Reg ar2 = ARM64RegForFlush(mr2);
if (ar1 != INVALID_REG && ar2 != INVALID_REG) {
fp_->STP(32, INDEX_SIGNED, ar1, ar2, CTXREG, GetMipsRegOffset(mr1));
DiscardR(mr1);
DiscardR(mr2);
}
}
// Then flush one by one.
for (int i = 0; i < numArmRegs; i++) {
int a = DecodeReg(order[i]);
int m = ar[a].mipsReg;
if (ar[a].isDirty) {
if (m == -1) {
INFO_LOG(Log::JIT, "ARM reg %d is dirty but has no mipsreg", a);
continue;
}
fp_->STR(32, INDEX_UNSIGNED, (ARM64Reg)(a + S0), CTXREG, GetMipsRegOffset(m));
mr[m].loc = ML_MEM;
mr[m].reg = (int)INVALID_REG;
ar[a].mipsReg = -1;
ar[a].isDirty = false;
} else {
if (m != -1) {
mr[m].loc = ML_MEM;
mr[m].reg = (int)INVALID_REG;
}
ar[a].mipsReg = -1;
// already not dirty
}
}
// Sanity check
for (int i = 0; i < numARMFpuReg_; i++) {
if (ar[i].mipsReg != -1) {
ERROR_LOG(Log::JIT, "Flush fail: ar[%d].mipsReg=%d", i, ar[i].mipsReg);
}
}
pendingFlush = false;
}
void Arm64RegCacheFPU::DiscardR(MIPSReg r) {
switch (mr[r].loc) {
case ML_IMM:
// IMM is always "dirty".
// IMM is not allowed for FP (yet).
ERROR_LOG(Log::JIT, "Imm in FP register?");
break;
case ML_ARMREG:
if (mr[r].reg == INVALID_REG) {
ERROR_LOG(Log::JIT, "DiscardR: MipsReg had bad ArmReg");
} else {
// Note that we DO NOT write it back here. That's the whole point of Discard.
ar[mr[r].reg].isDirty = false;
ar[mr[r].reg].mipsReg = -1;
}
break;
case ML_MEM:
// Already there, nothing to do.
break;
default:
//BAD
break;
}
mr[r].loc = ML_MEM;
mr[r].reg = (int)INVALID_REG;
mr[r].tempLock = false;
mr[r].spillLock = false;
}
bool Arm64RegCacheFPU::IsTempX(ARM64Reg r) const {
return ar[r - S0].mipsReg >= TEMP0;
}
int Arm64RegCacheFPU::GetTempR() {
pendingFlush = true;
for (int r = TEMP0; r < TEMP0 + NUM_TEMPS; ++r) {
if (mr[r].loc == ML_MEM && !mr[r].tempLock) {
mr[r].tempLock = true;
return r;
}
}
ERROR_LOG(Log::CPU, "Out of temp regs! Might need to DiscardR() some");
_assert_msg_(false, "Regcache ran out of temp regs, might need to DiscardR() some.");
return -1;
}
int Arm64RegCacheFPU::GetMipsRegOffset(MIPSReg r) {
// These are offsets within the MIPSState structure. First there are the GPRS, then FPRS, then the "VFPURs", then the VFPU ctrls.
if (r < 0 || r > 32 + 128 + NUM_TEMPS) {
ERROR_LOG(Log::JIT, "bad mips register %i, out of range", r);
return 0; // or what?
}
if (r < 32 || r >= 32 + 128) {
return (32 + r) << 2;
} else {
// r is between 32 and 128 + 32
return (32 + 32 + voffset[r - 32]) << 2;
}
}
void Arm64RegCacheFPU::SpillLock(MIPSReg r1, MIPSReg r2, MIPSReg r3, MIPSReg r4) {
mr[r1].spillLock = true;
if (r2 != -1) mr[r2].spillLock = true;
if (r3 != -1) mr[r3].spillLock = true;
if (r4 != -1) mr[r4].spillLock = true;
}
// This is actually pretty slow with all the 160 regs...
void Arm64RegCacheFPU::ReleaseSpillLocksAndDiscardTemps() {
for (int i = 0; i < NUM_MIPSFPUREG; i++) {
mr[i].spillLock = false;
}
for (int i = TEMP0; i < TEMP0 + NUM_TEMPS; ++i) {
DiscardR(i);
}
}
ARM64Reg Arm64RegCacheFPU::R(int mipsReg) {
if (mr[mipsReg].loc == ML_ARMREG) {
return (ARM64Reg)(mr[mipsReg].reg + S0);
} else {
if (mipsReg < 32) {
ERROR_LOG(Log::JIT, "FReg %i not in ARM reg. compilerPC = %08x : %s", mipsReg, js_->compilerPC, MIPSDisasmAt(js_->compilerPC).c_str());
} else if (mipsReg < 32 + 128) {
ERROR_LOG(Log::JIT, "VReg %i not in ARM reg. compilerPC = %08x : %s", mipsReg - 32, js_->compilerPC, MIPSDisasmAt(js_->compilerPC).c_str());
} else {
ERROR_LOG(Log::JIT, "Tempreg %i not in ARM reg. compilerPC = %08x : %s", mipsReg - 128 - 32, js_->compilerPC, MIPSDisasmAt(js_->compilerPC).c_str());
}
return INVALID_REG; // BAAAD
}
}
#endif // PPSSPP_ARCH(ARM64)
-179
View File
@@ -1,179 +0,0 @@
// Copyright (c) 2012- 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 "Core/MIPS/MIPS.h"
#include "Core/MIPS/ARM64/Arm64RegCache.h"
#include "Core/MIPS/MIPSVFPUUtils.h"
#include "Common/Arm64Emitter.h"
namespace Arm64JitConstants {
enum {
NUM_TEMPS = 16,
TEMP0 = 32 + 128,
TOTAL_MAPPABLE_MIPSFPUREGS = 32 + 128 + NUM_TEMPS,
};
enum {
MAP_READ = 0,
MAP_MTX_TRANSPOSED = 16,
MAP_PREFER_LOW = 16,
MAP_PREFER_HIGH = 32,
// Force is not yet correctly implemented, if the reg is already mapped it will not move
MAP_FORCE_LOW = 64, // Only map Q0-Q7 (and probably not Q0-Q3 as they are S registers so that leaves Q8-Q15)
MAP_FORCE_HIGH = 128, // Only map Q8-Q15
};
}
struct FPURegARM64 {
int mipsReg; // if -1, no mipsreg attached.
bool isDirty; // Should the register be written back?
};
struct FPURegQuad64 {
int mipsVec;
VectorSize sz;
u8 vregs[4];
bool isDirty;
bool spillLock;
bool isTemp;
};
struct FPURegMIPS {
// Where is this MIPS register?
Arm64JitConstants::RegMIPSLoc loc;
// Data (only one of these is used, depending on loc. Could make a union).
u32 reg;
int lane;
bool spillLock; // if true, this register cannot be spilled.
bool tempLock;
// If loc == ML_MEM, it's back in its location in the CPU context struct.
};
namespace MIPSComp {
struct JitOptions;
struct JitState;
}
class Arm64RegCacheFPU {
public:
Arm64RegCacheFPU(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo);
~Arm64RegCacheFPU() {}
void Init(Arm64Gen::ARM64XEmitter *emitter, Arm64Gen::ARM64FloatEmitter *fp);
void Start();
// Protect the arm register containing a MIPS register from spilling, to ensure that
// it's being kept allocated.
void SpillLock(MIPSReg reg, MIPSReg reg2 = -1, MIPSReg reg3 = -1, MIPSReg reg4 = -1);
void SpillLockV(MIPSReg r) { SpillLock(r + 32); }
void ReleaseSpillLocksAndDiscardTemps();
void ReleaseSpillLock(int mipsreg) {
mr[mipsreg].spillLock = false;
}
void ReleaseSpillLockV(int mipsreg) {
ReleaseSpillLock(mipsreg + 32);
}
void SetImm(MIPSReg reg, u32 immVal);
bool IsImm(MIPSReg reg) const;
u32 GetImm(MIPSReg reg) const;
// Returns an ARM register containing the requested MIPS register.
Arm64Gen::ARM64Reg MapReg(MIPSReg reg, int mapFlags = 0);
void MapInIn(MIPSReg rd, MIPSReg rs);
void MapDirty(MIPSReg rd);
void MapDirtyIn(MIPSReg rd, MIPSReg rs, bool avoidLoad = true);
void MapDirtyInIn(MIPSReg rd, MIPSReg rs, MIPSReg rt, bool avoidLoad = true);
bool IsMapped(MIPSReg r);
bool IsMappedV(MIPSReg r) { return IsMapped((MIPSReg)(r + 32)); }
bool IsInRAM(MIPSReg r);
bool IsInRAMV(MIPSReg r) { return IsInRAM((MIPSReg)(r + 32)); }
void FlushArmReg(Arm64Gen::ARM64Reg r);
void FlushR(MIPSReg r);
void DiscardR(MIPSReg r);
Arm64Gen::ARM64Reg R(int preg); // Returns a cached register
void MapRegV(int vreg, int flags = 0);
void LoadToRegV(Arm64Gen::ARM64Reg armReg, int vreg);
void MapInInV(int rt, int rs);
void MapDirtyInV(int rd, int rs, bool avoidLoad = true);
void MapDirtyInInV(int rd, int rs, int rt, bool avoidLoad = true);
bool IsTempX(Arm64Gen::ARM64Reg r) const;
MIPSReg GetTempV() { return GetTempR() - 32; }
// VFPU registers as single VFP registers.
Arm64Gen::ARM64Reg V(int vreg) { return R(vreg + 32); }
void FlushAll();
// Flushes only the registers a call doesn't preserve. S8-S15 stay mapped.
void FlushBeforeCall();
// This one is allowed at any point.
void FlushV(MIPSReg r);
// NOTE: These require you to release spill locks manually!
void MapRegsAndSpillLockV(int vec, VectorSize vsz, int flags);
void MapRegsAndSpillLockV(const u8 *v, VectorSize vsz, int flags);
void SpillLockV(const u8 *v, VectorSize vsz);
void SpillLockV(int vec, VectorSize vsz);
void SetEmitter(Arm64Gen::ARM64XEmitter *emitter, Arm64Gen::ARM64FloatEmitter *fp) { emit_ = emitter; fp_ = fp; }
int GetMipsRegOffset(MIPSReg r);
int GetMipsRegOffsetV(MIPSReg r) {
return GetMipsRegOffset(r + 32);
}
private:
Arm64Gen::ARM64Reg ARM64RegForFlush(int r);
MIPSReg GetTempR();
const Arm64Gen::ARM64Reg *GetMIPSAllocationOrder(int &count);
void SetupInitialRegs();
MIPSState *mips_;
Arm64Gen::ARM64XEmitter *emit_;
Arm64Gen::ARM64FloatEmitter *fp_;
MIPSComp::JitState *js_;
MIPSComp::JitOptions *jo_;
int numARMFpuReg_;
enum {
// On ARM64, each of the 32 registers are full 128-bit. No sharing of components!
MAX_ARMFPUREG = 32,
NUM_MIPSFPUREG = Arm64JitConstants::TOTAL_MAPPABLE_MIPSFPUREGS,
};
FPURegARM64 ar[MAX_ARMFPUREG];
FPURegMIPS mr[NUM_MIPSFPUREG];
FPURegMIPS *vr;
bool pendingFlush;
bool initialReady = false;
FPURegARM64 arInitial[MAX_ARMFPUREG];
FPURegMIPS mrInitial[NUM_MIPSFPUREG];
};
+3 -11
View File
@@ -41,10 +41,8 @@
#if PPSSPP_ARCH(ARM)
#include "../ARM/ArmJit.h"
#elif PPSSPP_ARCH(ARM64)
#include "../ARM64/Arm64Jit.h"
#include "../ARM64/Arm64IRJit.h"
#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "../x86/Jit.h"
#include "../x86/X64IRJit.h"
#elif PPSSPP_ARCH(RISCV64)
#include "../RiscV/RiscVJit.h"
@@ -105,19 +103,13 @@ namespace MIPSComp {
return notTakenTarget;
}
JitInterface *CreateNativeJit(MIPSState *mipsState, bool useIR) {
JitInterface *CreateNativeJit(MIPSState *mipsState) {
#if PPSSPP_ARCH(ARM)
return new MIPSComp::ArmJit(mipsState);
#elif PPSSPP_ARCH(ARM64)
if (useIR)
return new MIPSComp::Arm64IRJit(mipsState);
else
return new MIPSComp::Arm64Jit(mipsState);
return new MIPSComp::Arm64IRJit(mipsState);
#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
if (useIR)
return new MIPSComp::X64IRJit(mipsState);
else
return new MIPSComp::Jit(mipsState);
return new MIPSComp::X64IRJit(mipsState);
#elif PPSSPP_ARCH(RISCV64)
return new MIPSComp::RiscVJit(mipsState);
#elif PPSSPP_ARCH(LOONGARCH64)
+1 -1
View File
@@ -169,5 +169,5 @@ namespace MIPSComp {
void DoDummyJitState(PointerWrap &p);
JitInterface *CreateNativeJit(MIPSState *mipsState, bool useIR);
JitInterface *CreateNativeJit(MIPSState *mipsState);
}
+2 -2
View File
@@ -360,7 +360,7 @@ void MIPSState::Init() {
memset(vcmpResult, 0, sizeof(vcmpResult));
if (PSP_CoreParameter().cpuCore == CPUCore::JIT || PSP_CoreParameter().cpuCore == CPUCore::JIT_IR) {
MIPSComp::jit = MIPSComp::CreateNativeJit(this, PSP_CoreParameter().cpuCore == CPUCore::JIT_IR);
MIPSComp::jit = MIPSComp::CreateNativeJit(this);
} else if (PSP_CoreParameter().cpuCore == CPUCore::IR_INTERPRETER) {
MIPSComp::jit = new MIPSComp::IRJit(this, false);
} else {
@@ -393,7 +393,7 @@ void MIPSState::UpdateCore(CPUCore desired) {
case CPUCore::JIT:
case CPUCore::JIT_IR:
INFO_LOG(Log::CPU, "Switching to JIT%s", PSP_CoreParameter().cpuCore == CPUCore::JIT_IR ? " IR" : "");
newjit = MIPSComp::CreateNativeJit(this, PSP_CoreParameter().cpuCore == CPUCore::JIT_IR);
newjit = MIPSComp::CreateNativeJit(this);
break;
case CPUCore::IR_INTERPRETER:
-16
View File
@@ -148,22 +148,6 @@ extern u8 fromvoffset[128];
enum class CPUCore;
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
// Note that CTXREG is offset to point at the first floating point register, intentionally. This is so that a byte offset
// can reach both GPR and FPR regs.
#define MIPSSTATE_VAR(x) MDisp(X64JitConstants::CTXREG, \
(int)(offsetof(MIPSState, x) - offsetof(MIPSState, f[0])))
// Workaround for compilers that don't like dynamic indexing in offsetof
#define MIPSSTATE_VAR_ELEM32(x, i) MDisp(X64JitConstants::CTXREG, \
(int)(offsetof(MIPSState, x) - offsetof(MIPSState, f[0]) + (i) * 4))
// To get RIP/relative addressing (requires tight memory control so generated code isn't too far from the binary, and a reachable variable called mips):
// #define MIPSSTATE_VAR(x) M(&mips_->x)
#endif
// Only icache invalidations currently, so no type field.
struct PendingCacheOperation {
u32 addr;
-237
View File
@@ -1,237 +0,0 @@
// Copyright (C) 2003 Dolphin 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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Common/Math/math_util.h"
#include "ABI.h"
#include "x64Emitter.h"
#include "Core/Core.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/MIPS/MIPS.h"
#include "Core/CoreTiming.h"
#include "Common/MemoryUtil.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/x86/Jit.h"
using namespace Gen;
using namespace X64JitConstants;
extern volatile CoreState coreState;
namespace MIPSComp
{
//TODO - make an option
//#if _DEBUG
static bool enableDebug = false;
//#else
// bool enableDebug = false;
//#endif
//static bool enableStatistics = false; //unused?
//GLOBAL STATIC ALLOCATIONS x86
//EAX - ubiquitous scratch register - EVERYBODY scratches this
//EBP - Pointer to fpr/gpr regs
//GLOBAL STATIC ALLOCATIONS x64
//EAX - ubiquitous scratch register - EVERYBODY scratches this
//RBX - Base pointer of memory
//R14 - Pointer to fpr/gpr regs
//R15 - Pointer to array of block pointers
void ImHere() {
DEBUG_LOG(Log::CPU, "JIT Here: %08x", currentMIPS->pc);
}
void Jit::GenerateFixedCode(JitOptions &jo) {
BeginWrite(GetMemoryProtectPageSize());
AlignCodePage();
restoreRoundingMode = AlignCode16(); {
STMXCSR(MIPSSTATE_VAR(temp));
// Clear the rounding mode, flush-to-zero and denormals-are-zero bits back to 0.
AND(32, MIPSSTATE_VAR(temp), Imm32(~((7 << 13) | (1 << 6))));
LDMXCSR(MIPSSTATE_VAR(temp));
RET();
}
applyRoundingMode = AlignCode16(); {
MOV(32, R(EAX), MIPSSTATE_VAR(fcr31));
AND(32, R(EAX), Imm32(0x01000003));
// If it's 0 (nearest + no flush0), we don't actually bother setting - we cleared the rounding
// mode out in restoreRoundingMode anyway. This is the most common.
FixupBranch skip = J_CC(CC_Z);
STMXCSR(MIPSSTATE_VAR(temp));
// The MIPS bits don't correspond exactly, so we have to adjust.
// 0 -> 0 (skip2), 1 -> 3, 2 -> 2 (skip2), 3 -> 1
TEST(8, R(AL), Imm8(1));
FixupBranch skip2 = J_CC(CC_Z);
XOR(32, R(EAX), Imm8(2));
SetJumpTarget(skip2);
// Adjustment complete, now reconstruct MXCSR
SHL(32, R(EAX), Imm8(13));
// Before setting new bits, we must clear the old ones.
// Clearing bits 13-14 (rounding mode), 15 (flush to zero) and 6 (denormals are zero).
AND(32, MIPSSTATE_VAR(temp), Imm32(~((7 << 13) | (1 << 6))));
OR(32, MIPSSTATE_VAR(temp), R(EAX));
TEST(32, MIPSSTATE_VAR(fcr31), Imm32(1 << 24));
FixupBranch skip3 = J_CC(CC_Z);
// FTZ and DAZ together, which flushes like ARM64's FZ: inputs as well as results.
OR(32, MIPSSTATE_VAR(temp), Imm32((1 << 15) | (1 << 6)));
SetJumpTarget(skip3);
LDMXCSR(MIPSSTATE_VAR(temp));
SetJumpTarget(skip);
RET();
}
enterDispatcher = AlignCode16();
ABI_PushAllCalleeSavedRegsAndAdjustStack();
#if PPSSPP_ARCH(AMD64)
// Two statically allocated registers.
MOV(64, R(MEMBASEREG), ImmPtr(Memory::base));
uintptr_t jitbase = (uintptr_t)GetBasePtr();
if (jitbase > 0x7FFFFFFFULL) {
MOV(64, R(JITBASEREG), ImmPtr(GetBasePtr()));
jo.reserveR15ForAsm = true;
}
#endif
// From the start of the FP reg, a single byte offset can reach all GPR + all FPR (but no VFPUR)
MOV(PTRBITS, R(CTXREG), ImmPtr(&mips_->f[0]));
outerLoop = GetCodePtr();
RestoreRoundingMode(true);
LEA(PTRBITS, ECX, MDisp(CTXREG, -(s32)offsetof(MIPSState, f[0]))); // Adjust to get the real pointer.
ABI_CallFunctionR(reinterpret_cast<void *>(&CoreTiming::Advance), ECX);
ApplyRoundingMode(true);
FixupBranch skipToCoreStateCheck = J(); //skip the downcount check
dispatcherCheckCoreState = GetCodePtr();
// The result of slice decrementation should be in flags if somebody jumped here
// IMPORTANT - We jump on negative, not carry!!!
FixupBranch bailCoreState = J_CC(CC_S, true);
SetJumpTarget(skipToCoreStateCheck);
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(0)); // rip accessible
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm32(0));
}
FixupBranch badCoreState = J_CC(CC_NZ, true);
FixupBranch skipToRealDispatch2 = J(); //skip the sync and compare first time
dispatcher = GetCodePtr();
// The result of slice decrementation should be in flags if somebody jumped here
// IMPORTANT - We jump on negative, not carry!!!
FixupBranch bail = J_CC(CC_S, true);
SetJumpTarget(skipToRealDispatch2);
dispatcherNoCheck = GetCodePtr();
MOV(32, R(EAX), MIPSSTATE_VAR(pc));
dispatcherInEAXNoCheck = GetCodePtr();
// Look the block up in the shadow. The address stays in EAX for MemFault if this faults.
AND(32, R(EAX), Imm32(Memory::BLOCK_SHADOW_ENTRY_MASK));
#if PPSSPP_ARCH(X86)
_assert_msg_(Memory::blockShadow != 0, "Block shadow bogus");
dispatcherFetch = GetCodePtr();
MOV(32, R(EAX), MDisp(EAX, (u32)Memory::blockShadow));
#elif PPSSPP_ARCH(AMD64)
MOV(64, R(RDX), ImmPtr(Memory::blockShadow));
dispatcherFetch = GetCodePtr();
MOV(32, R(EAX), MComplex(RDX, RAX, SCALE_1, 0));
#endif
TEST(32, R(EAX), R(EAX));
FixupBranch notfound = J_CC(CC_Z);
if (enableDebug) {
ADD(32, MIPSSTATE_VAR(debugCount), Imm8(1));
}
// The entry is the block's offset into the code space.
#if PPSSPP_ARCH(X86)
ADD(32, R(EAX), ImmPtr(GetBasePtr()));
#elif PPSSPP_ARCH(AMD64)
if (jo.reserveR15ForAsm) {
ADD(64, R(RAX), R(JITBASEREG));
} else {
// See above, reserveR15ForAsm is used when above 0x7FFFFFFF.
ADD(64, R(EAX), Imm32((u32)jitbase));
}
#endif
JMPptr(R(EAX));
SetJumpTarget(notfound);
//Ok, no block, let's jit
// A replacement hook's block shadow entry jumps here too (GetBlockShadowHookValue()). JitAt
// replaces it with the new block's entry, and destroying that block puts the hook value back.
blockShadowHook = GetCodePtr();
RestoreRoundingMode(true);
LEA(PTRBITS, ECX, MDisp(CTXREG, -(s32)offsetof(MIPSState, f[0]))); // Adjust to get the real pointer.
ABI_CallFunctionR(reinterpret_cast<void *>(&MIPSComp::JitAt), ECX);
ApplyRoundingMode(true);
JMP(dispatcherNoCheck, true); // Let's just dispatch again, we'll enter the block since we know it's there.
SetJumpTarget(bail);
SetJumpTarget(bailCoreState);
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(0)); // rip accessible
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm32(0));
}
J_CC(CC_Z, outerLoop, true);
const uint8_t *quitLoop = GetCodePtr();
SetJumpTarget(badCoreState);
RestoreRoundingMode(true);
ABI_PopAllCalleeSavedRegsAndAdjustStack();
RET();
crashHandler = GetCodePtr();
if (RipAccessible((const void *)&coreState)) {
MOV(32, M(&coreState), Imm32(CORE_RUNTIME_ERROR));
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
MOV(32, MatR(RAX), Imm32(CORE_RUNTIME_ERROR));
}
JMP(quitLoop, true);
// Let's spare the pre-generated code from unprotect-reprotect.
endOfPregeneratedCode = AlignCodePage();
EndWrite();
}
} // namespace
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
File diff suppressed because it is too large. Load diff
-711
View File
@@ -1,711 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Profiler/Profiler.h"
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/Reporting.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/HLETables.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/x86/Jit.h"
#include "Core/MIPS/x86/RegCache.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM26 (op & 0x03FFFFFF)
#define TARGET16 ((int)(SignExtend16ToU32(op) << 2))
#define TARGET26 (_IMM26 << 2)
#define LOOPOPTIMIZATION 0
using namespace MIPSAnalyst;
// NOTE: Can't use CONDITIONAL_DISABLE in this file, branches are so special
// that they cannot be interpreted in the context of the Jit.
// But we can at least log and compare.
// #define DO_CONDITIONAL_LOG 1
#define DO_CONDITIONAL_LOG 0
// We can also disable nice delay slots.
// #define CONDITIONAL_NICE_DELAYSLOT branchInfo.delaySlotIsNice = false;
#define CONDITIONAL_NICE_DELAYSLOT ;
#if DO_CONDITIONAL_LOG
#define CONDITIONAL_LOG BranchLog(op);
#define CONDITIONAL_LOG_EXIT(addr) BranchLogExit(op, addr, false);
#define CONDITIONAL_LOG_EXIT_EAX() BranchLogExit(op, 0, true);
#else
#define CONDITIONAL_LOG ;
#define CONDITIONAL_LOG_EXIT(addr) ;
#define CONDITIONAL_LOG_EXIT_EAX() ;
#endif
namespace MIPSComp
{
using namespace Gen;
static void JitBranchLog(MIPSOpcode op, u32 pc) {
currentMIPS->pc = pc;
currentMIPS->inDelaySlot = false;
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
MIPSInfo info = MIPSGetInfo(op);
func(currentMIPS, op);
// Branch taken, use nextPC.
if (currentMIPS->inDelaySlot)
currentMIPS->intBranchExit = currentMIPS->nextPC;
else
{
// Branch not taken, likely delay slot skipped.
if (info & LIKELY)
currentMIPS->intBranchExit = currentMIPS->pc;
// Branch not taken, so increment over delay slot.
else
currentMIPS->intBranchExit = currentMIPS->pc + 4;
}
currentMIPS->pc = pc;
currentMIPS->inDelaySlot = false;
}
static void JitBranchLogMismatch(MIPSOpcode op, u32 pc)
{
char temp[256];
MIPSDisAsm(op, pc, temp, sizeof(temp), true);
ERROR_LOG(Log::JIT, "Bad jump: %s - int:%08x jit:%08x", temp, currentMIPS->intBranchExit, currentMIPS->jitBranchExit);
Core_Break(BreakReason::JitBranchDebug, pc);
}
void Jit::BranchLog(MIPSOpcode op)
{
FlushAll();
ABI_CallFunctionCC(thunks.ProtectFunction(&JitBranchLog), op.encoding, GetCompilerPC());
}
void Jit::BranchLogExit(MIPSOpcode op, u32 dest, bool useEAX)
{
OpArg destArg = useEAX ? R(EAX) : Imm32(dest);
CMP(32, MIPSSTATE_VAR(intBranchExit), destArg);
FixupBranch skip = J_CC(CC_E);
MOV(32, MIPSSTATE_VAR(jitBranchExit), destArg);
ABI_CallFunctionCC(thunks.ProtectFunction(&JitBranchLogMismatch), op.encoding, GetCompilerPC());
// Restore EAX, we probably ruined it.
if (useEAX)
MOV(32, R(EAX), MIPSSTATE_VAR(jitBranchExit));
SetJumpTarget(skip);
}
CCFlags Jit::FlipCCFlag(CCFlags flag)
{
switch (flag)
{
case CC_O: return CC_NO;
case CC_NO: return CC_O;
case CC_B: return CC_NB;
case CC_NB: return CC_B;
case CC_Z: return CC_NZ;
case CC_NZ: return CC_Z;
case CC_BE: return CC_NBE;
case CC_NBE: return CC_BE;
case CC_S: return CC_NS;
case CC_NS: return CC_S;
case CC_P: return CC_NP;
case CC_NP: return CC_P;
case CC_L: return CC_NL;
case CC_NL: return CC_L;
case CC_LE: return CC_NLE;
case CC_NLE: return CC_LE;
}
ERROR_LOG_REPORT(Log::JIT, "FlipCCFlag: Unexpected CC flag: %d", flag);
return CC_O;
}
CCFlags Jit::SwapCCFlag(CCFlags flag)
{
// This swaps the comparison for an lhs/rhs swap, but doesn't flip/invert the logic.
switch (flag)
{
case CC_O: return CC_O;
case CC_NO: return CC_NO;
case CC_B: return CC_A;
case CC_NB: return CC_NA;
case CC_Z: return CC_Z;
case CC_NZ: return CC_NZ;
case CC_BE: return CC_AE;
case CC_NBE: return CC_NAE;
case CC_S: return CC_S;
case CC_NS: return CC_NS;
case CC_P: return CC_P;
case CC_NP: return CC_NP;
case CC_L: return CC_G;
case CC_NL: return CC_NG;
case CC_LE: return CC_GE;
case CC_NLE: return CC_NGE;
}
ERROR_LOG_REPORT(Log::JIT, "SwapCCFlag: Unexpected CC flag: %d", flag);
return CC_O;
}
bool Jit::PredictTakeBranch(u32 targetAddr, bool likely) {
// If it's likely, it's... probably likely, right?
if (likely)
return true;
// TODO: Normal branch prediction would be to take branches going upward to lower addresses.
// However, this results in worse performance as of this comment's writing.
// The reverse check generally gives better or same performance.
return targetAddr > GetCompilerPC();
}
void Jit::CompBranchExits(CCFlags cc, u32 targetAddr, u32 notTakenAddr, const BranchInfo &branchInfo) {
if (branchInfo.andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
// We may want to try to continue along this branch a little while, to reduce reg flushing.
bool predictTakeBranch = PredictTakeBranch(targetAddr, branchInfo.likely);
Gen::FixupBranch ptr;
if (!branchInfo.likely) {
if (!branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
else
FlushAll();
ptr = J_CC(cc, true);
} else {
FlushAll();
ptr = J_CC(cc, true);
if (!branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_FLUSH);
}
// Handle the linkage of a delay slot, even when we're taking the branch.
if (branchInfo.delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
FlushAll();
}
// Take the branch
CONDITIONAL_LOG_EXIT(targetAddr);
WriteExit(targetAddr, js.nextExit++);
// Not taken
SetJumpTarget(ptr);
CONDITIONAL_LOG_EXIT(notTakenAddr);
WriteExit(notTakenAddr, js.nextExit++);
js.compiling = false;
}
void Jit::CompBranchExit(bool taken, u32 targetAddr, u32 notTakenAddr, const BranchInfo &branchInfo) {
// Continuing is handled in the imm branch case... TODO: move it here?
if (branchInfo.andLink)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
if (branchInfo.delaySlotIsBranch) {
if (taken) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
gpr.SetImm(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
} else if (taken || !branchInfo.likely) {
CompileDelaySlot(DELAYSLOT_FLUSH);
} else {
FlushAll();
}
const u32 destAddr = taken ? targetAddr : notTakenAddr;
CONDITIONAL_LOG_EXIT(destAddr);
WriteExit(destAddr, js.nextExit++);
js.compiling = false;
}
void Jit::BranchRSRTComp(MIPSOpcode op, Gen::CCFlags cc, bool likely)
{
CONDITIONAL_LOG;
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rt, rs);
CONDITIONAL_NICE_DELAYSLOT;
bool immBranch = false;
bool immBranchTaken = false;
if (gpr.IsImm(rs) && gpr.IsImm(rt) && !branchInfo.delaySlotIsBranch) {
// The cc flags are opposites: when NOT to take the branch.
bool immBranchNotTaken;
s32 rsImm = (s32)gpr.GetImm(rs);
s32 rtImm = (s32)gpr.GetImm(rt);
switch (cc)
{
case CC_E: immBranchNotTaken = rsImm == rtImm; break;
case CC_NE: immBranchNotTaken = rsImm != rtImm; break;
default: immBranchNotTaken = false; _dbg_assert_msg_(false, "Bad cc flag in BranchRSRTComp().");
}
immBranch = true;
immBranchTaken = !immBranchNotTaken;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
if (immBranch)
CompBranchExit(immBranchTaken, targetAddr, notTakenTarget, branchInfo);
else
{
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0)
{
gpr.KillImmediate(rs, true, false);
CMP(32, gpr.R(rs), Imm32(0));
}
else
{
gpr.MapReg(rs, true, false);
CMP(32, gpr.R(rs), gpr.R(rt));
}
CompBranchExits(cc, targetAddr, notTakenTarget, branchInfo);
}
}
void Jit::BranchRSZeroComp(MIPSOpcode op, Gen::CCFlags cc, bool andLink, bool likely)
{
CONDITIONAL_LOG;
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), andLink, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rs);
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
bool immBranch = false;
bool immBranchTaken = false;
if (gpr.IsImm(rs) && !branchInfo.delaySlotIsBranch) {
// The cc flags are opposites: when NOT to take the branch.
bool immBranchNotTaken;
s32 imm = (s32)gpr.GetImm(rs);
switch (cc)
{
case CC_G: immBranchNotTaken = imm > 0; break;
case CC_GE: immBranchNotTaken = imm >= 0; break;
case CC_L: immBranchNotTaken = imm < 0; break;
case CC_LE: immBranchNotTaken = imm <= 0; break;
default: immBranchNotTaken = false; _dbg_assert_msg_(false, "Bad cc flag in BranchRSZeroComp().");
}
immBranch = true;
immBranchTaken = !immBranchNotTaken;
}
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
if (immBranch)
CompBranchExit(immBranchTaken, targetAddr, notTakenTarget, branchInfo);
else
{
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
gpr.MapReg(rs, true, false);
CMP(32, gpr.R(rs), Imm32(0));
CompBranchExits(cc, targetAddr, notTakenTarget, branchInfo);
}
}
void Jit::Comp_RelBranch(MIPSOpcode op)
{
switch (op>>26)
{
case 4: BranchRSRTComp(op, CC_NZ, false); break;//beq
case 5: BranchRSRTComp(op, CC_Z, false); break;//bne
case 6: BranchRSZeroComp(op, CC_G, false, false); break;//blez
case 7: BranchRSZeroComp(op, CC_LE, false, false); break;//bgtz
case 20: BranchRSRTComp(op, CC_NZ, true); break;//beql
case 21: BranchRSRTComp(op, CC_Z, true); break;//bnel
case 22: BranchRSZeroComp(op, CC_G, false, true); break;//blezl
case 23: BranchRSZeroComp(op, CC_LE, false, true); break;//bgtzl
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
}
void Jit::Comp_RelBranchRI(MIPSOpcode op)
{
switch ((op >> 16) & 0x1F)
{
case 0: BranchRSZeroComp(op, CC_GE, false, false); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz
case 1: BranchRSZeroComp(op, CC_L, false, false); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez
case 2: BranchRSZeroComp(op, CC_GE, false, true); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl
case 3: BranchRSZeroComp(op, CC_L, false, true); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl
case 16: BranchRSZeroComp(op, CC_GE, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltzal
case 17: BranchRSZeroComp(op, CC_L, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgezal
case 18: BranchRSZeroComp(op, CC_GE, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else SkipLikely(); break;//bltzall
case 19: BranchRSZeroComp(op, CC_L, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else SkipLikely(); break;//bgezall
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
}
// If likely is set, discard the branch slot if NOT taken.
void Jit::BranchFPFlag(MIPSOpcode op, Gen::CCFlags cc, bool likely)
{
CONDITIONAL_LOG;
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceFPU(op, branchInfo.delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
gpr.KillImmediate(MIPS_REG_FPCOND, true, false);
TEST(32, gpr.R(MIPS_REG_FPCOND), Imm32(1));
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
CompBranchExits(cc, targetAddr, notTakenTarget, branchInfo);
}
void Jit::Comp_FPUBranch(MIPSOpcode op)
{
switch((op >> 16) & 0x1f)
{
case 0: BranchFPFlag(op, CC_NZ, false); break; //bc1f
case 1: BranchFPFlag(op, CC_Z, false); break; //bc1t
case 2: BranchFPFlag(op, CC_NZ, true); break; //bc1fl
case 3: BranchFPFlag(op, CC_Z, true); break; //bc1tl
default:
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
break;
}
}
// If likely is set, discard the branch slot if NOT taken.
void Jit::BranchVFPUFlag(MIPSOpcode op, Gen::CCFlags cc, bool likely)
{
CONDITIONAL_LOG;
if (js.inDelaySlot) {
// I think we can safely just warn-log this without reporting, it's pretty clear that this type
// of branch is ignored.
WARN_LOG(Log::JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
// Sometimes there's a VFPU branch in a delay slot (Disgaea 2: Dark Hero Days, Zettai Hero Project, La Pucelle)
// The behavior is undefined - the CPU may take the second branch even if the first one passes.
// However, it does consistently try each branch, which these games seem to expect.
branchInfo.delaySlotIsNice = IsDelaySlotNiceVFPU(op, branchInfo.delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
if (!likely && branchInfo.delaySlotIsNice && !branchInfo.delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_NICE);
// THE CONDITION
int imm3 = (op >> 18) & 7;
gpr.KillImmediate(MIPS_REG_VFPUCC, true, false);
TEST(32, gpr.R(MIPS_REG_VFPUCC), Imm32(1 << imm3));
u32 notTakenTarget = ResolveNotTakenTarget(branchInfo);
CompBranchExits(cc, targetAddr, notTakenTarget, branchInfo);
}
void Jit::Comp_VBranch(MIPSOpcode op)
{
switch ((op >> 16) & 3)
{
case 0: BranchVFPUFlag(op, CC_NZ, false); break; //bvf
case 1: BranchVFPUFlag(op, CC_Z, false); break; //bvt
case 2: BranchVFPUFlag(op, CC_NZ, true); break; //bvfl
case 3: BranchVFPUFlag(op, CC_Z, true); break; //bvtl
default:
_dbg_assert_msg_(false,"Comp_VBranch: Invalid instruction");
break;
}
}
static void HitInvalidJump(uint32_t dest) {
Core_ExecException(dest, currentMIPS->pc - 8, ExecExceptionType::JUMP);
}
void Jit::Comp_Jump(MIPSOpcode op) {
CONDITIONAL_LOG;
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
u32 off = TARGET26;
u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off;
// Might be a stubbed address or something?
if (!Memory::IsValidAddress(targetAddr) || (targetAddr & 3) != 0) {
if (js.nextExit == 0) {
ERROR_LOG(Log::JIT, "Jump to invalid address: %08x PC %08x RA %08x", targetAddr, GetCompilerPC(), currentMIPS->r[MIPS_REG_RA]);
} else {
js.compiling = false;
}
// TODO: Mark this block dirty or something? May be indication it will be changed by imports.
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC() + 8));
ABI_CallFunctionC(&HitInvalidJump, targetAddr);
WriteSyscallExit();
return;
}
switch (op >> 26) {
case 2: //j
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
CONDITIONAL_LOG_EXIT(targetAddr);
WriteExit(targetAddr, js.nextExit++);
break;
case 3: //jal
// Save return address - might be overwritten by delay slot.
gpr.SetImm(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
FlushAll();
CONDITIONAL_LOG_EXIT(targetAddr);
WriteExit(targetAddr, js.nextExit++);
break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
js.compiling = false;
}
void Jit::Comp_JumpReg(MIPSOpcode op) {
CONDITIONAL_LOG;
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in JumpReg delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
bool andLink = (op & 0x3f) == 9 && rd != MIPS_REG_ZERO;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
bool delaySlotIsNice = IsDelaySlotNiceReg(op, delaySlotOp, rs);
if (andLink && rs == rd)
delaySlotIsNice = false;
CONDITIONAL_NICE_DELAYSLOT;
X64Reg destReg = EAX;
if (IsSyscall(delaySlotOp))
{
// If this is a syscall, write the pc (for thread switching and other good reasons.)
gpr.MapReg(rs, true, false);
MOV(32, MIPSSTATE_VAR(pc), gpr.R(rs));
if (andLink)
gpr.SetImm(rd, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_FLUSH);
// Syscalls write the exit code for us.
_dbg_assert_msg_(!js.compiling, "Expected syscall to write an exit code.");
return;
}
else if (delaySlotIsNice)
{
if (andLink)
gpr.SetImm(rd, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
if (!andLink && rs == MIPS_REG_RA && g_Config.bDiscardRegsOnJRRA) {
// According to the MIPS ABI, there are some regs we don't need to preserve.
// Let's discard them so we don't need to write them back.
// NOTE: Not all games follow the MIPS ABI! Tekken 6, for example, will crash
// with this enabled.
gpr.DiscardRegContentsIfCached(MIPS_REG_COMPILER_SCRATCH);
for (int i = MIPS_REG_A0; i <= MIPS_REG_T7; i++)
gpr.DiscardRegContentsIfCached((MIPSGPReg)i);
gpr.DiscardRegContentsIfCached(MIPS_REG_T8);
gpr.DiscardRegContentsIfCached(MIPS_REG_T9);
}
if (gpr.R(rs).IsSimpleReg()) {
destReg = gpr.R(rs).GetSimpleReg();
} else {
MOV(32, R(EAX), gpr.R(rs));
}
FlushAll();
} else {
// Latch destination now - save it in memory.
gpr.MapReg(rs, true, false);
MOV(32, MIPSSTATE_VAR(savedPC), gpr.R(rs));
if (andLink)
gpr.SetImm(rd, GetCompilerPC() + 8);
CompileDelaySlot(DELAYSLOT_NICE);
MOV(32, R(EAX), MIPSSTATE_VAR(savedPC));
FlushAll();
}
switch (op & 0x3f) {
case 8: //jr
break;
case 9: //jalr
break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
CONDITIONAL_LOG_EXIT_EAX();
WriteExitDestInReg(destReg);
js.compiling = false;
}
void Jit::Comp_Syscall(MIPSOpcode op)
{
if (op.encoding == 0x03FFFFcc) {
WARN_LOG(Log::JIT, "Encountered bad syscall instruction at %08x (%08x)", js.compilerPC, op.encoding);
}
if (!g_Config.bSkipDeadbeefFilling)
{
// All of these will be overwritten with DEADBEEF anyway.
gpr.DiscardR(MIPS_REG_COMPILER_SCRATCH);
// We need to keep A0 - T3, which are used for args.
gpr.DiscardR(MIPS_REG_T4);
gpr.DiscardR(MIPS_REG_T5);
gpr.DiscardR(MIPS_REG_T6);
gpr.DiscardR(MIPS_REG_T7);
gpr.DiscardR(MIPS_REG_T8);
gpr.DiscardR(MIPS_REG_T9);
gpr.DiscardR(MIPS_REG_HI);
gpr.DiscardR(MIPS_REG_LO);
}
FlushAll();
// If we're in a delay slot, this is off by one.
const int offset = js.inDelaySlot ? -1 : 0;
WriteDowncount(offset);
RestoreRoundingMode();
js.downcountAmount = -offset;
if (!js.inDelaySlot) {
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC() + 4));
}
#ifdef USE_PROFILER
// When profiling, we can't skip CallSyscall, since it times syscalls.
ABI_CallFunctionC(&CallSyscall, op.encoding);
#else
// Skip the CallSyscall where possible.
const HLEFunction *func = GetSyscallFunctionData(op, js.compilerPC);
if (func) {
void *quickFunc = GetQuickSyscallFunc(func, op);
if (quickFunc) {
ABI_CallFunctionP(quickFunc, (void *)func);
} else {
ABI_CallFunctionC(&CallSyscall, op.encoding);
}
} else {
ABI_CallFunctionC(&CallSyscallUnresolvedAtPC, js.compilerPC);
}
#endif
ApplyRoundingMode();
WriteSyscallExit();
js.compiling = false;
}
void Jit::Comp_Break(MIPSOpcode op)
{
Comp_Generic(op);
WriteSyscallExit();
js.compiling = false;
}
} // namespace Mipscomp
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
-501
View File
@@ -1,501 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Core/Config.h"
#include "Core/MemMap.h"
#include "Common/CommonTypes.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/x86/Jit.h"
#include "Core/MIPS/x86/RegCache.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM16 (signed short)(op & 0xFFFF)
#define _IMM26 (op & 0x03FFFFFF)
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE.
// #define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; }
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
#define DISABLE { Comp_Generic(op); return; }
namespace MIPSComp {
using namespace Gen;
using namespace X64JitConstants;
alignas(16) const u32 reverseQNAN[4] = { 0x803FFFFF, 0x803FFFFF, 0x803FFFFF, 0x803FFFFF };
void Jit::CopyFPReg(X64Reg dst, OpArg src) {
if (src.IsSimpleReg()) {
MOVAPS(dst, src);
} else {
MOVSS(dst, src);
}
}
void Jit::CompFPTriArith(MIPSOpcode op, void (XEmitter::*arith)(X64Reg reg, OpArg), bool orderMatters) {
int ft = _FT;
int fs = _FS;
int fd = _FD;
fpr.SpillLock(fd, fs, ft);
if (fs == fd) {
fpr.MapReg(fd, true, true);
(this->*arith)(fpr.RX(fd), fpr.R(ft));
} else if (ft == fd && !orderMatters) {
fpr.MapReg(fd, true, true);
(this->*arith)(fpr.RX(fd), fpr.R(fs));
} else if (ft != fd) {
// fs can't be fd (handled above.)
fpr.MapReg(fd, false, true);
CopyFPReg(fpr.RX(fd), fpr.R(fs));
(this->*arith)(fpr.RX(fd), fpr.R(ft));
} else {
// fd must be ft, and order must matter.
fpr.MapReg(fd, true, true);
CopyFPReg(XMM0, fpr.R(fs));
(this->*arith)(XMM0, fpr.R(ft));
MOVAPS(fpr.RX(fd), R(XMM0));
}
fpr.ReleaseSpillLocks();
}
void Jit::Comp_FPU3op(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU);
switch (op & 0x3f) {
case 0: CompFPTriArith(op, &XEmitter::ADDSS, false); break; //F(fd) = F(fs) + F(ft); //add
case 1: CompFPTriArith(op, &XEmitter::SUBSS, true); break; //F(fd) = F(fs) - F(ft); //sub
case 2: //F(fd) = F(fs) * F(ft); //mul
// XMM1 = !my_isnan(fs) && !my_isnan(ft)
MOVSS(XMM1, fpr.R(_FS));
CMPORDSS(XMM1, fpr.R(_FT));
CompFPTriArith(op, &XEmitter::MULSS, false);
// fd must still be in a reg, save it in XMM0 for now.
MOVAPS(XMM0, fpr.R(_FD));
// fd = my_isnan(fd) && !my_isnan(fs) && !my_isnan(ft)
CMPUNORDSS(fpr.RX(_FD), fpr.R(_FD));
ANDPS(fpr.RX(_FD), R(XMM1));
// At this point fd = FFFFFFFF if non-NAN inputs produced a NAN output.
// We'll AND it with the inverse QNAN bits to clear (00000000 means no change.)
if (RipAccessible(&reverseQNAN)) {
ANDPS(fpr.RX(_FD), M(&reverseQNAN)); // rip accessible
} else {
MOV(PTRBITS, R(TEMPREG), ImmPtr(&reverseQNAN));
ANDPS(fpr.RX(_FD), MatR(TEMPREG));
}
// ANDN is backwards, which is why we saved XMM0 to start. Now put it back.
ANDNPS(fpr.RX(_FD), R(XMM0));
break;
case 3: CompFPTriArith(op, &XEmitter::DIVSS, true); break; //F(fd) = F(fs) / F(ft); //div
default:
_dbg_assert_msg_(false,"Trying to compile FPU3Op instruction that can't be interpreted");
break;
}
}
void Jit::Comp_FPULS(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU_FPU);
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
s32 offset = _IMM16;
int ft = _FT;
MIPSGPReg rs = _RS;
CheckMemoryBreakpoint(0, rs, offset);
switch (op >> 26) {
case 49: // lwc1
{
gpr.Lock(rs);
fpr.SpillLock(ft);
fpr.MapReg(ft, false, true);
JitSafeMem safe(this, rs, offset);
OpArg src;
if (safe.PrepareRead(src, 4))
MOVSS(fpr.RX(ft), src);
if (safe.PrepareSlowRead(safeMemFuncs.readU32))
MOVD_xmm(fpr.RX(ft), R(EAX));
safe.Finish();
gpr.UnlockAll();
fpr.ReleaseSpillLocks();
}
break;
case 57: // swc1
{
gpr.Lock(rs);
fpr.SpillLock(ft);
fpr.MapReg(ft, true, false);
JitSafeMem safe(this, rs, offset);
OpArg dest;
if (safe.PrepareWrite(dest, 4))
MOVSS(dest, fpr.RX(ft));
if (safe.PrepareSlowWrite())
{
MOVSS(MIPSSTATE_VAR(temp), fpr.RX(ft));
safe.DoSlowWrite(safeMemFuncs.writeU32, MIPSSTATE_VAR(temp));
}
safe.Finish();
gpr.UnlockAll();
fpr.ReleaseSpillLocks();
}
break;
default:
_dbg_assert_msg_(false,"Trying to interpret FPULS instruction that can't be interpreted");
break;
}
}
alignas(16) static const u64 ssSignBits2[2] = {0x8000000080000000ULL, 0x8000000080000000ULL};
alignas(16) static const u64 ssNoSignMask[2] = {0x7FFFFFFF7FFFFFFFULL, 0x7FFFFFFF7FFFFFFFULL};
void Jit::CompFPComp(int lhs, int rhs, u8 compare, bool allowNaN) {
gpr.MapReg(MIPS_REG_FPCOND, false, true);
// This means that NaN also means true, e.g. !<> or !>, etc.
if (allowNaN) {
CopyFPReg(XMM0, fpr.R(lhs));
CopyFPReg(XMM1, fpr.R(lhs));
CMPSS(XMM0, fpr.R(rhs), compare);
CMPUNORDSS(XMM1, fpr.R(rhs));
POR(XMM0, R(XMM1));
} else {
CopyFPReg(XMM0, fpr.R(lhs));
CMPSS(XMM0, fpr.R(rhs), compare);
}
MOVD_xmm(gpr.R(MIPS_REG_FPCOND), XMM0);
// 1 rather than the compare's all-ones mask, as on the other cores.
AND(32, gpr.R(MIPS_REG_FPCOND), Imm32(1));
}
void Jit::Comp_FPUComp(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU_COMP);
int fs = _FS;
int ft = _FT;
switch (op & 0xf) {
case 0: //f
case 8: //sf
gpr.SetImm(MIPS_REG_FPCOND, 0);
break;
case 1: //un
case 9: //ngle
CompFPComp(fs, ft, CMP_UNORD);
break;
case 2: //eq
case 10: //seq
CompFPComp(fs, ft, CMP_EQ);
break;
case 3: //ueq
case 11: //ngl
CompFPComp(fs, ft, CMP_EQ, true);
break;
case 4: //olt
case 12: //lt
CompFPComp(fs, ft, CMP_LT);
break;
case 5: //ult
case 13: //nge
CompFPComp(ft, fs, CMP_NLE);
break;
case 6: //ole
case 14: //le
CompFPComp(fs, ft, CMP_LE);
break;
case 7: //ule
case 15: //ngt
CompFPComp(ft, fs, CMP_NLT);
break;
default:
DISABLE;
}
}
void Jit::Comp_FPU2op(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU);
int fs = _FS;
int fd = _FD;
auto execRounding = [&](void (XEmitter::*conv)(X64Reg, OpArg), int setMXCSR) {
fpr.SpillLock(fd, fs);
fpr.MapReg(fd, fs == fd, true);
// Small optimization: 0 is our default mode anyway.
if (setMXCSR == 0 && !js.hasSetRounding) {
setMXCSR = -1;
}
if (setMXCSR != -1) {
STMXCSR(MIPSSTATE_VAR(mxcsrTemp));
MOV(32, R(TEMPREG), MIPSSTATE_VAR(mxcsrTemp));
AND(32, R(TEMPREG), Imm32(~(3 << 13)));
OR(32, R(TEMPREG), Imm32(setMXCSR << 13));
MOV(32, MIPSSTATE_VAR(temp), R(TEMPREG));
LDMXCSR(MIPSSTATE_VAR(temp));
}
(this->*conv)(TEMPREG, fpr.R(fs));
// Did we get an indefinite integer value?
CMP(32, R(TEMPREG), Imm32(0x80000000));
FixupBranch skip = J_CC(CC_NE);
if (fd != fs) {
CopyFPReg(fpr.RX(fd), fpr.R(fs));
}
XORPS(XMM1, R(XMM1));
CMPSS(fpr.RX(fd), R(XMM1), CMP_LT);
// At this point, -inf = 0xffffffff, inf/nan = 0x00000000.
// We want -inf to be 0x80000000 inf/nan to be 0x7fffffff, so we flip those bits.
MOVD_xmm(R(TEMPREG), fpr.RX(fd));
XOR(32, R(TEMPREG), Imm32(0x7fffffff));
SetJumpTarget(skip);
MOVD_xmm(fpr.RX(fd), R(TEMPREG));
if (setMXCSR != -1) {
LDMXCSR(MIPSSTATE_VAR(mxcsrTemp));
}
};
switch (op & 0x3f) {
case 5: //F(fd) = fabsf(F(fs)); break; //abs
fpr.SpillLock(fd, fs);
fpr.MapReg(fd, fd == fs, true);
MOV(PTRBITS, R(TEMPREG), ImmPtr(&ssNoSignMask[0]));
if (fd != fs && fpr.IsMapped(fs)) {
MOVAPS(fpr.RX(fd), MatR(TEMPREG));
ANDPS(fpr.RX(fd), fpr.R(fs));
} else {
if (fd != fs) {
MOVSS(fpr.RX(fd), fpr.R(fs));
}
ANDPS(fpr.RX(fd), MatR(TEMPREG));
}
break;
case 6: //F(fd) = F(fs); break; //mov
if (fd != fs) {
fpr.SpillLock(fd, fs);
fpr.MapReg(fd, fd == fs, true);
CopyFPReg(fpr.RX(fd), fpr.R(fs));
}
break;
case 7: //F(fd) = -F(fs); break; //neg
fpr.SpillLock(fd, fs);
fpr.MapReg(fd, fd == fs, true);
MOV(PTRBITS, R(TEMPREG), ImmPtr(&ssSignBits2[0]));
if (fd != fs && fpr.IsMapped(fs)) {
MOVAPS(fpr.RX(fd), MatR(TEMPREG));
XORPS(fpr.RX(fd), fpr.R(fs));
} else {
if (fd != fs) {
MOVSS(fpr.RX(fd), fpr.R(fs));
}
XORPS(fpr.RX(fd), MatR(TEMPREG));
}
break;
case 4: //F(fd) = sqrtf(F(fs)); break; //sqrt
fpr.SpillLock(fd, fs);
fpr.MapReg(fd, fd == fs, true);
// x86 gives a negative NaN for a negative input, the PSP a positive one: clear the sign
// where the input was negative. -0 and NaN inputs come through as they are.
XORPS(XMM1, R(XMM1));
CopyFPReg(XMM0, fpr.R(fs));
CMPSS(XMM0, R(XMM1), CMP_LT);
if (RipAccessible(&ssSignBits2[0])) {
ANDPS(XMM0, M(&ssSignBits2[0])); // rip accessible
} else {
MOV(PTRBITS, R(TEMPREG), ImmPtr(&ssSignBits2[0]));
ANDPS(XMM0, MatR(TEMPREG));
}
SQRTSS(fpr.RX(fd), fpr.R(fs));
XORPS(fpr.RX(fd), R(XMM0));
break;
case 13: //FsI(fd) = F(fs)>=0 ? (int)floorf(F(fs)) : (int)ceilf(F(fs)); break; //trunc.w.s
execRounding(&XEmitter::CVTTSS2SI, -1);
break;
case 32: //F(fd) = (float)FsI(fs); break; //cvt.s.w
fpr.SpillLock(fd, fs);
fpr.MapReg(fd, fs == fd, true);
if (fpr.IsMapped(fs)) {
CVTDQ2PS(fpr.RX(fd), fpr.R(fs));
} else {
// If fs was fd, we'd be in the case above since we mapped fd.
MOVSS(fpr.RX(fd), fpr.R(fs));
CVTDQ2PS(fpr.RX(fd), fpr.R(fd));
}
break;
case 36: //FsI(fd) = (int) F(fs); break; //cvt.w.s
// Uses the current rounding mode.
execRounding(&XEmitter::CVTSS2SI, -1);
break;
case 12: //FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s
execRounding(&XEmitter::CVTSS2SI, 0);
break;
case 14: //FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s
execRounding(&XEmitter::CVTSS2SI, 2);
break;
case 15: //FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s
execRounding(&XEmitter::CVTSS2SI, 1);
break;
default:
DISABLE;
return;
}
fpr.ReleaseSpillLocks();
}
void Jit::Comp_mxc1(MIPSOpcode op) {
CONDITIONAL_DISABLE(FPU_XFER);
int fs = _FS;
MIPSGPReg rt = _RT;
switch ((op >> 21) & 0x1f) {
case 0: // R(rt) = FI(fs); break; //mfc1
if (rt == MIPS_REG_ZERO)
return;
gpr.MapReg(rt, false, true);
// If fs is not mapped, most likely it's being abandoned.
// Just load from memory in that case.
if (fpr.R(fs).IsSimpleReg()) {
MOVD_xmm(gpr.R(rt), fpr.RX(fs));
} else {
MOV(32, gpr.R(rt), fpr.R(fs));
}
break;
case 2: // R(rt) = currentMIPS->ReadFCR(fs); break; //cfc1
if (rt == MIPS_REG_ZERO)
return;
if (fs == 31) {
bool wasImm = gpr.IsImm(MIPS_REG_FPCOND);
if (!wasImm) {
gpr.Lock(rt, MIPS_REG_FPCOND);
gpr.MapReg(MIPS_REG_FPCOND, true, false);
}
gpr.MapReg(rt, false, true);
MOV(32, gpr.R(rt), MIPSSTATE_VAR(fcr31));
if (wasImm) {
if (gpr.GetImm(MIPS_REG_FPCOND) & 1) {
OR(32, gpr.R(rt), Imm32(1 << 23));
} else {
AND(32, gpr.R(rt), Imm32(~(1 << 23)));
}
} else {
AND(32, gpr.R(rt), Imm32(~(1 << 23)));
MOV(32, R(TEMPREG), gpr.R(MIPS_REG_FPCOND));
AND(32, R(TEMPREG), Imm32(1));
SHL(32, R(TEMPREG), Imm8(23));
OR(32, gpr.R(rt), R(TEMPREG));
}
gpr.UnlockAll();
} else if (fs == 0) {
gpr.SetImm(rt, MIPSState::FCR0_VALUE);
} else {
Comp_Generic(op);
}
return;
case 4: //FI(fs) = R(rt); break; //mtc1
fpr.MapReg(fs, false, true);
if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0) {
XORPS(fpr.RX(fs), fpr.R(fs));
} else {
gpr.KillImmediate(rt, true, false);
MOVD_xmm(fpr.RX(fs), gpr.R(rt));
}
return;
case 6: //currentMIPS->WriteFCR(fs, R(rt)); break; //ctc1
if (fs == 31) {
// Must clear before setting, since ApplyRoundingMode() assumes it was cleared.
RestoreRoundingMode();
if (gpr.IsImm(rt)) {
gpr.SetImm(MIPS_REG_FPCOND, (gpr.GetImm(rt) >> 23) & 1);
MOV(32, MIPSSTATE_VAR(fcr31), Imm32(gpr.GetImm(rt) & 0x0181FFFF));
if ((gpr.GetImm(rt) & 0x1000003) == 0) {
// Default nearest / no-flush mode, just leave it cleared.
} else {
UpdateRoundingMode(gpr.GetImm(rt));
ApplyRoundingMode();
}
} else {
gpr.Lock(rt, MIPS_REG_FPCOND);
gpr.MapReg(rt, true, false);
gpr.MapReg(MIPS_REG_FPCOND, false, true);
MOV(32, gpr.R(MIPS_REG_FPCOND), gpr.R(rt));
SHR(32, gpr.R(MIPS_REG_FPCOND), Imm8(23));
AND(32, gpr.R(MIPS_REG_FPCOND), Imm32(1));
MOV(32, MIPSSTATE_VAR(fcr31), gpr.R(rt));
AND(32, MIPSSTATE_VAR(fcr31), Imm32(0x0181FFFF));
gpr.UnlockAll();
UpdateRoundingMode();
ApplyRoundingMode();
}
} else {
Comp_Generic(op);
}
return;
}
}
} // namespace MIPSComp
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
-496
View File
@@ -1,496 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Core/MemMap.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/Config.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/x86/Jit.h"
#include "Core/MIPS/x86/RegCache.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM16 (signed short)(op & 0xFFFF)
#define _IMM26 (op & 0x03FFFFFF)
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE.
// #define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; }
#define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; }
#define DISABLE { Comp_Generic(op); return; }
#define INVALIDOP { Comp_Generic(op); return; }
namespace MIPSComp {
using namespace Gen;
void Jit::CompITypeMemRead(MIPSOpcode op, u32 bits, void (XEmitter::*mov)(int, int, X64Reg, OpArg), const void *safeFunc)
{
CONDITIONAL_DISABLE(LSU);
int offset = _IMM16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
gpr.Lock(rt, rs);
gpr.MapReg(rt, rt == rs, true);
JitSafeMem safe(this, rs, offset);
OpArg src;
if (safe.PrepareRead(src, bits / 8))
(this->*mov)(32, bits, gpr.RX(rt), src);
if (safe.PrepareSlowRead(safeFunc))
(this->*mov)(32, bits, gpr.RX(rt), R(EAX));
safe.Finish();
gpr.UnlockAll();
}
static OpArg DowncastImm(OpArg in, int bits) {
if (!in.IsImm())
return in;
if (in.GetImmBits() > bits) {
in.SetImmBits(bits);
return in;
}
return in;
}
void Jit::CompITypeMemWrite(MIPSOpcode op, u32 bits, const void *safeFunc, bool makeRTWritable)
{
CONDITIONAL_DISABLE(LSU);
int offset = _IMM16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
gpr.Lock(rt, rs);
if (rt == MIPS_REG_ZERO || gpr.R(rt).IsImm()) {
if (makeRTWritable) {
gpr.MapReg(rt, true, true);
}
// NOTICE_LOG(Log::JIT, "%d-bit Imm at %08x : %08x", bits, js.blockStart, (u32)gpr.R(rt).GetImmValue());
} else {
gpr.MapReg(rt, true, false);
}
#if PPSSPP_ARCH(X86)
// We use EDX so we can have DL for 8-bit ops.
const bool needSwap = bits == 8 && !gpr.R(rt).IsSimpleReg(EDX) && !gpr.R(rt).IsSimpleReg(ECX);
if (needSwap)
gpr.FlushLockX(EDX);
#else
const bool needSwap = false;
#endif
JitSafeMem safe(this, rs, offset);
OpArg dest;
if (safe.PrepareWrite(dest, bits / 8))
{
if (needSwap)
{
MOV(32, R(EDX), gpr.R(rt));
MOV(bits, dest, R(EDX));
}
else {
if (rt == MIPS_REG_ZERO) {
switch (bits) {
case 8: MOV(8, dest, Imm8(0)); break;
case 16: MOV(16, dest, Imm16(0)); break;
case 32: MOV(32, dest, Imm32(0)); break;
}
} else {
// The downcast is needed so we don't try to generate a 8-bit write with a 32-bit imm
// (that might have been generated from an li instruction) which is illegal.
MOV(bits, dest, DowncastImm(gpr.R(rt), bits));
}
}
}
if (safe.PrepareSlowWrite())
safe.DoSlowWrite(safeFunc, gpr.R(rt));
safe.Finish();
if (needSwap)
gpr.UnlockAllX();
gpr.UnlockAll();
}
void Jit::CompITypeMemUnpairedLR(MIPSOpcode op, bool isStore)
{
CONDITIONAL_DISABLE(LSU);
int offset = _IMM16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
X64Reg shiftReg = ECX;
gpr.FlushLockX(ECX, EDX);
#if PPSSPP_ARCH(AMD64)
// On x64, we need ECX for CL, but it's also the first arg and gets lost. Annoying.
gpr.FlushLockX(R9);
shiftReg = R9;
#endif
gpr.Lock(rt, rs);
gpr.MapReg(rt, true, !isStore);
// Grab the offset from alignment for shifting (<< 3 for bytes -> bits.)
MOV(32, R(shiftReg), gpr.R(rs));
ADD(32, R(shiftReg), Imm32(offset));
AND(32, R(shiftReg), Imm32(3));
SHL(32, R(shiftReg), Imm8(3));
{
JitSafeMem safe(this, rs, offset, ~3);
OpArg src;
if (safe.PrepareRead(src, 4))
{
if (!src.IsSimpleReg(EAX))
MOV(32, R(EAX), src);
CompITypeMemUnpairedLRInner(op, shiftReg);
}
if (safe.PrepareSlowRead(safeMemFuncs.readU32))
CompITypeMemUnpairedLRInner(op, shiftReg);
safe.Finish();
}
// For store ops, write EDX back to memory.
if (isStore)
{
JitSafeMem safe(this, rs, offset, ~3);
OpArg dest;
if (safe.PrepareWrite(dest, 4))
MOV(32, dest, R(EDX));
if (safe.PrepareSlowWrite())
safe.DoSlowWrite(safeMemFuncs.writeU32, R(EDX));
safe.Finish();
}
gpr.UnlockAll();
gpr.UnlockAllX();
}
void Jit::CompITypeMemUnpairedLRInner(MIPSOpcode op, X64Reg shiftReg)
{
CONDITIONAL_DISABLE(LSU);
int o = op>>26;
MIPSGPReg rt = _RT;
// Make sure we have the shift for the target in ECX.
if (shiftReg != ECX)
MOV(32, R(ECX), R(shiftReg));
// Now use that shift (left on target, right on source.)
switch (o)
{
case 34: //lwl
MOV(32, R(EDX), Imm32(0x00ffffff));
SHR(32, R(EDX), R(CL));
AND(32, gpr.R(rt), R(EDX));
break;
case 38: //lwr
SHR(32, R(EAX), R(CL));
break;
case 42: //swl
MOV(32, R(EDX), Imm32(0xffffff00));
SHL(32, R(EDX), R(CL));
AND(32, R(EAX), R(EDX));
break;
case 46: //swr
MOV(32, R(EDX), gpr.R(rt));
SHL(32, R(EDX), R(CL));
// EDX is already the target value to write, but may be overwritten below. Save it.
PUSH(EDX);
break;
default:
_dbg_assert_msg_(false, "Unsupported left/right load/store instruction.");
}
// Flip ECX around from 3 bytes / 24 bits.
if (shiftReg == ECX)
{
MOV(32, R(EDX), Imm32(24));
SUB(32, R(EDX), R(ECX));
MOV(32, R(ECX), R(EDX));
}
else
{
MOV(32, R(ECX), Imm32(24));
SUB(32, R(ECX), R(shiftReg));
}
// Use the flipped shift (left on source, right on target) and write target.
switch (o)
{
case 34: //lwl
SHL(32, R(EAX), R(CL));
OR(32, gpr.R(rt), R(EAX));
break;
case 38: //lwr
MOV(32, R(EDX), Imm32(0xffffff00));
SHL(32, R(EDX), R(CL));
AND(32, gpr.R(rt), R(EDX));
OR(32, gpr.R(rt), R(EAX));
break;
case 42: //swl
MOV(32, R(EDX), gpr.R(rt));
SHR(32, R(EDX), R(CL));
OR(32, R(EDX), R(EAX));
break;
case 46: //swr
MOV(32, R(EDX), Imm32(0x00ffffff));
SHR(32, R(EDX), R(CL));
AND(32, R(EAX), R(EDX));
// This is the target value we saved earlier.
POP(EDX);
OR(32, R(EDX), R(EAX));
break;
default:
_dbg_assert_msg_(false, "Unsupported left/right load/store instruction.");
}
}
void Jit::Comp_ITypeMem(MIPSOpcode op)
{
CONDITIONAL_DISABLE(LSU);
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
// NOTE: This should be done before the $zr check in case there are reads with side effects.
DISABLE;
return;
}
int offset = _IMM16;
MIPSGPReg rs = _RS;
MIPSGPReg rt = _RT;
int o = op>>26;
// 16-bit accesses can go through the depth layout (Memory::DepthMirrored16).
if (Memory::DepthMirrorsActive() && (o == 33 || o == 37 || o == 41)) {
DISABLE;
}
if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) {
// Don't load anything into $zr
return;
}
CheckMemoryBreakpoint(0, rs, offset);
switch (o)
{
case 37: //R(rt) = ReadMem16(addr); break; //lhu
CompITypeMemRead(op, 16, &XEmitter::MOVZX, safeMemFuncs.readU16);
break;
case 36: //R(rt) = ReadMem8 (addr); break; //lbu
CompITypeMemRead(op, 8, &XEmitter::MOVZX, safeMemFuncs.readU8);
break;
case 35: //R(rt) = ReadMem32(addr); break; //lw
CompITypeMemRead(op, 32, &XEmitter::MOVZX, safeMemFuncs.readU32);
break;
case 32: //R(rt) = SignExtend8ToU32 (ReadMem8 (addr)); break; //lb
CompITypeMemRead(op, 8, &XEmitter::MOVSX, safeMemFuncs.readU8);
break;
case 33: //R(rt) = SignExtend16ToU32(ReadMem16(addr)); break; //lh
CompITypeMemRead(op, 16, &XEmitter::MOVSX, safeMemFuncs.readU16);
break;
case 40: //WriteMem8 (addr, R(rt)); break; //sb
CompITypeMemWrite(op, 8, safeMemFuncs.writeU8);
break;
case 41: //WriteMem16(addr, R(rt)); break; //sh
CompITypeMemWrite(op, 16, safeMemFuncs.writeU16);
break;
case 43: //WriteMem32(addr, R(rt)); break; //sw
CompITypeMemWrite(op, 32, safeMemFuncs.writeU32);
break;
case 34: //lwl
{
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Looking for lwr rd, offset-3(rs) which makes a pair.
u32 desiredOp = ((op & 0xFFFF0000) + (4 << 26)) + (offset - 3);
if (!js.inDelaySlot && nextOp == desiredOp && !jo.Disabled(JitDisable::LSU_UNALIGNED))
{
CheckMemoryBreakpoint(1, rs, offset - 3);
EatInstruction(nextOp);
// nextOp has the correct address.
CompITypeMemRead(nextOp, 32, &XEmitter::MOVZX, safeMemFuncs.readU32);
}
else
CompITypeMemUnpairedLR(op, false);
}
break;
case 38: //lwr
{
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Looking for lwl rd, offset+3(rs) which makes a pair.
u32 desiredOp = ((op & 0xFFFF0000) - (4 << 26)) + (offset + 3);
if (!js.inDelaySlot && nextOp == desiredOp && !jo.Disabled(JitDisable::LSU_UNALIGNED))
{
CheckMemoryBreakpoint(1, rs, offset + 3);
EatInstruction(nextOp);
// op has the correct address.
CompITypeMemRead(op, 32, &XEmitter::MOVZX, safeMemFuncs.readU32);
}
else
CompITypeMemUnpairedLR(op, false);
}
break;
case 42: //swl
{
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Looking for swr rd, offset-3(rs) which makes a pair.
u32 desiredOp = ((op & 0xFFFF0000) + (4 << 26)) + (offset - 3);
if (!js.inDelaySlot && nextOp == desiredOp && !jo.Disabled(JitDisable::LSU_UNALIGNED))
{
CheckMemoryBreakpoint(1, rs, offset - 3);
EatInstruction(nextOp);
// nextOp has the correct address.
CompITypeMemWrite(nextOp, 32, safeMemFuncs.writeU32);
}
else
CompITypeMemUnpairedLR(op, true);
}
break;
case 46: //swr
{
MIPSOpcode nextOp = GetOffsetInstruction(1);
// Looking for swl rd, offset+3(rs) which makes a pair.
u32 desiredOp = ((op & 0xFFFF0000) - (4 << 26)) + (offset + 3);
if (!js.inDelaySlot && nextOp == desiredOp && !jo.Disabled(JitDisable::LSU_UNALIGNED))
{
CheckMemoryBreakpoint(1, rs, offset + 3);
EatInstruction(nextOp);
// op has the correct address.
CompITypeMemWrite(op, 32, safeMemFuncs.writeU32);
}
else
CompITypeMemUnpairedLR(op, true);
}
break;
default:
Comp_Generic(op);
return;
}
}
void Jit::Comp_StoreSync(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU);
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
int offset = _IMM16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
// Note: still does something even if loading to zero.
CheckMemoryBreakpoint(0, rs, offset);
FixupBranch skipStore;
FixupBranch finish;
switch (op >> 26) {
case 48: // ll
CompITypeMemRead(op, 32, &XEmitter::MOVZX, safeMemFuncs.readU32);
MOV(8, MDisp(X64JitConstants::CTXREG, -128 + offsetof(MIPSState, llBit)), Imm8(1));
break;
case 56: // sc
// Map before the jump in case any regs spill. Unlock happens inside CompITypeMemWrite().
// This is not a very common op, but it's in jit so memory breakpoints can trip.
gpr.Lock(rt, rs);
gpr.MapReg(rt, true, true);
gpr.MapReg(rs, true, false);
CMP(8, MDisp(X64JitConstants::CTXREG, -128 + offsetof(MIPSState, llBit)), Imm8(1));
skipStore = J_CC(CC_NE);
CompITypeMemWrite(op, 32, safeMemFuncs.writeU32, true);
// $zero is an immediate, and has no result to write.
if (rt != MIPS_REG_ZERO) {
MOV(32, gpr.R(rt), Imm32(1));
finish = J();
SetJumpTarget(skipStore);
MOV(32, gpr.R(rt), Imm32(0));
SetJumpTarget(finish);
} else {
SetJumpTarget(skipStore);
}
break;
default:
INVALIDOP;
}
}
void Jit::Comp_Cache(MIPSOpcode op) {
CONDITIONAL_DISABLE(LSU);
int func = (op >> 16) & 0x1F;
// See Int_Cache for the definitions.
switch (func) {
case 24: break;
case 25: break;
case 27: break;
case 30: break;
default:
// Fall back to the interpreter.
DISABLE;
}
}
}
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
-43
View File
@@ -1,43 +0,0 @@
// Copyright (c) 2013- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Core/MemMap.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/x86/RegCache.h"
#include "Core/MIPS/x86/Jit.h"
alignas(16) static const u64 ssNoSignMask[2] = {0x7FFFFFFF7FFFFFFFULL, 0x7FFFFFFF7FFFFFFFULL};
namespace MIPSComp {
using namespace Gen;
int Jit::Replace_fabsf() {
fpr.SpillLock(0, 12);
fpr.MapReg(0, false, true);
MOVSS(fpr.RX(0), fpr.R(12));
MOV(PTRBITS, R(RAX), ImmPtr(&ssNoSignMask));
ANDPS(fpr.RX(0), MatR(RAX));
fpr.ReleaseSpillLocks();
return 4; // Number of instructions in the MIPS function
}
}
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
File diff suppressed because it is too large. Load diff
-971
View File
@@ -1,971 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include <algorithm>
#include <iterator>
#include "Common/Math/math_util.h"
#include "Common/Profiler/Profiler.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Core/Core.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/CoreTiming.h"
#include "Core/Config.h"
#include "Core/Reporting.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/Interpreter.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/HLE/ReplaceTables.h"
#include "RegCache.h"
#include "Jit.h"
#include "Core/Debugger/Breakpoints.h"
namespace MIPSComp
{
using namespace Gen;
const bool USE_JIT_MISSMAP = false;
static std::map<std::string, u32> notJitOps;
template<typename A, typename B>
std::pair<B,A> flip_pair(const std::pair<A,B> &p) {
return std::pair<B, A>(p.second, p.first);
}
// This is called when Jit hits a breakpoint. Returns 1 when hit.
u32 JitBreakpoint(uint32_t addr)
{
// Should we skip this breakpoint?
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc || g_breakpoints.CheckSkipFirst() == addr)
return 0;
BreakAction result = g_breakpoints.ExecBreakPoint(addr);
if ((result & BREAK_ACTION_PAUSE) == 0)
return 0;
// There's probably a better place for this.
if (USE_JIT_MISSMAP) {
std::map<u32, std::string> notJitSorted;
std::transform(notJitOps.begin(), notJitOps.end(), std::inserter(notJitSorted, notJitSorted.begin()), flip_pair<std::string, u32>);
std::string message;
char temp[256];
int remaining = 15;
for (auto it = notJitSorted.rbegin(), end = notJitSorted.rend(); it != end && --remaining >= 0; ++it)
{
snprintf(temp, 256, " (%d), ", it->first);
message += it->second + temp;
}
if (message.size() > 2)
message.resize(message.size() - 2);
NOTICE_LOG(Log::JIT, "Top ops compiled to interpreter: %s", message.c_str());
}
return 1;
}
static u32 JitMemCheck(u32 addr, u32 pc) {
// Should we skip this breakpoint?
if (g_breakpoints.CheckSkipFirst() == currentMIPS->pc)
return 0;
// Did we already hit one?
if (coreState != CORE_RUNNING_CPU && coreState != CORE_NEXTFRAME)
return 1;
// Note: pc may be the delay slot.
g_breakpoints.ExecOpMemCheck(addr, pc);
return coreState == CORE_RUNNING_CPU || coreState == CORE_NEXTFRAME ? 0 : 1;
}
static void JitLogMiss(MIPSOpcode op)
{
if (USE_JIT_MISSMAP)
notJitOps[MIPSGetName(op)]++;
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
func(currentMIPS, op);
}
#ifdef _MSC_VER
// JitBlockCache doesn't use this, just stores it.
#pragma warning(disable:4355)
#endif
Jit::Jit(MIPSState *mipsState)
: blocks(mipsState, this), mips_(mipsState) {
blocks.Init();
gpr.SetEmitter(this);
fpr.SetEmitter(this);
AllocCodeSpace(1024 * 1024 * 16);
GenerateFixedCode(jo);
safeMemFuncs.Init(&thunks);
js.startDefaultPrefix = mips_->HasDefaultPrefix();
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.
g_breakpoints.SetSkipFirst(0);
}
Jit::~Jit() {
}
void Jit::DoState(PointerWrap &p) {
auto s = p.Section("Jit", 1, 2);
if (!s)
return;
Do(p, js.startDefaultPrefix);
if (p.mode == PointerWrap::MODE_READ && !js.startDefaultPrefix) {
WARN_LOG(Log::CPU, "Jit: An uneaten prefix was previously detected. Jitting in unknown-prefix mode.");
}
if (s >= 2) {
Do(p, js.hasSetRounding);
if (p.mode == PointerWrap::MODE_READ) {
js.lastSetRounding = 0;
}
} else {
js.hasSetRounding = 1;
}
// The debugger sets this so that "go" on a breakpoint will actually... go.
// But if they load a state, we can end up hitting it by mistake, since it's based on PC and ticks.
g_breakpoints.SetSkipFirst(0);
}
void Jit::UpdateFCR31() {
// fcr31 can reach a non-default mode without a compiled ctc1 (an interpreted one, a savestate,
// a core switch, a thread switch). Blocks compiled before that don't switch modes around
// syscalls or conversions, so they're thrown away, at the next safe point.
if ((mips_->fcr31 & 0x01000003) && !js.hasSetRounding) {
js.hasSetRounding = true;
js.lastSetRounding = true;
mips_->ClearJitCacheDeferred();
}
}
void Jit::GetStateAndFlushAll(RegCacheState &state) {
gpr.GetState(state.gpr);
fpr.GetState(state.fpr);
FlushAll();
}
void Jit::RestoreState(const RegCacheState& state) {
gpr.RestoreState(state.gpr);
fpr.RestoreState(state.fpr);
}
void Jit::FlushAll() {
gpr.Flush();
fpr.Flush();
FlushPrefixV();
}
void Jit::FlushPrefixV() {
if (js.startDefaultPrefix && !js.blockWrotePrefixes && js.HasNoPrefix()) {
// They started default, we never modified in memory, and they're default now.
// No reason to modify memory. This is common at end of blocks. Just clear dirty.
js.prefixSFlag = (JitState::PrefixState)(js.prefixSFlag & ~JitState::PREFIX_DIRTY);
js.prefixTFlag = (JitState::PrefixState)(js.prefixTFlag & ~JitState::PREFIX_DIRTY);
js.prefixDFlag = (JitState::PrefixState)(js.prefixDFlag & ~JitState::PREFIX_DIRTY);
return;
}
if ((js.prefixSFlag & JitState::PREFIX_DIRTY) != 0) {
MOV(32, MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_SPREFIX]), Imm32(js.prefixS));
js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {
MOV(32, MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_TPREFIX]), Imm32(js.prefixT));
js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);
}
if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {
MOV(32, MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_DPREFIX]), Imm32(js.prefixD));
js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);
}
// If we got here, we must've written prefixes to memory in this block.
js.blockWrotePrefixes = true;
}
void Jit::WriteDowncount(int offset) {
const int downcount = js.downcountAmount + offset;
SUB(32, MIPSSTATE_VAR(downcount), downcount > 127 ? Imm32(downcount) : Imm8(downcount));
}
void Jit::RestoreRoundingMode(bool force) {
// If the game has never set an interesting rounding mode, we can safely skip this.
if (force || js.hasSetRounding) {
CALL(restoreRoundingMode);
}
}
void Jit::ApplyRoundingMode(bool force) {
// If the game has never set an interesting rounding mode, we can safely skip this.
if (force || js.hasSetRounding) {
CALL(applyRoundingMode);
}
}
void Jit::UpdateRoundingMode(u32 fcr31) {
// We must set js.hasSetRounding at compile time, or this block will use the wrong rounding mode.
// The fcr31 parameter is -1 when not known at compile time, so we just assume it was changed.
if (fcr31 & 0x01000003) {
js.hasSetRounding = true;
}
}
void Jit::ClearCache()
{
blocks.Clear();
ClearCodeSpace(0);
GenerateFixedCode(jo);
// The compile path may have moved, and replacement hooks' block shadow entries point to it.
Replacement_SetBlockShadowHook(GetBlockShadowHookValue());
}
void Jit::SaveFlags() {
PUSHF();
#if PPSSPP_ARCH(AMD64)
// On X64, the above misaligns the stack. However there might be a cheaper solution than this.
POP(64, R(EAX));
MOV(64, MIPSSTATE_VAR(saved_flags), R(EAX));
#endif
}
void Jit::LoadFlags() {
#if PPSSPP_ARCH(AMD64)
MOV(64, R(EAX), MIPSSTATE_VAR(saved_flags));
PUSH(64, R(EAX));
#endif
POPF();
}
void Jit::CompileDelaySlot(int flags, RegCacheState *state) {
// Need to offset the downcount which was already incremented for the branch + delay slot.
CheckJitBreakpoint(GetCompilerPC() + 4, -2);
if (flags & DELAYSLOT_SAFE)
SaveFlags(); // preserve flag around the delay slot!
js.inDelaySlot = true;
MIPSOpcode op = GetOffsetInstruction(1);
MIPSCompileOp(op, this);
js.inDelaySlot = false;
if (flags & DELAYSLOT_FLUSH) {
if (state != NULL)
GetStateAndFlushAll(*state);
else
FlushAll();
}
if (flags & DELAYSLOT_SAFE)
LoadFlags(); // restore flag!
}
void Jit::EatInstruction(MIPSOpcode op) {
MIPSInfo info = MIPSGetInfo(op);
if (info & DELAYSLOT) {
ERROR_LOG_REPORT_ONCE(ateDelaySlot, Log::JIT, "Ate a branch op.");
}
if (js.inDelaySlot) {
ERROR_LOG_REPORT_ONCE(ateInDelaySlot, Log::JIT, "Ate an instruction inside a delay slot.");
}
CheckJitBreakpoint(GetCompilerPC() + 4, 0);
js.numInstructions++;
js.compilerPC += 4;
js.downcountAmount += MIPSGetInstructionCycleEstimate(op);
}
void Jit::Compile(u32 em_address) {
PROFILE_THIS_SCOPE("jitc");
if (!Memory::IsValid4AlignedAddress(em_address)) {
Core_ExecException(em_address, em_address, ExecExceptionType::JUMP);
return;
}
if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) {
ClearCache();
}
// Sometimes we compile fairly large blocks, although it's uncommon.
BeginWrite(JitBlockCache::MAX_BLOCK_INSTRUCTIONS * 16);
int block_num = blocks.AllocateBlock(em_address);
JitBlock *b = blocks.GetBlock(block_num);
b->DoIntegrityCheck(em_address, block_num, "BeforeJit");
DoJit(em_address, b);
b->DoIntegrityCheck(em_address, block_num, "BeforeFinalize");
blocks.FinalizeBlock(block_num, jo.enableBlocklink);
b->DoIntegrityCheck(em_address, block_num, "AfterFinalize");
_dbg_assert_(js.nextExit <= 2);
EndWrite();
bool cleanSlate = false;
if (js.hasSetRounding && !js.lastSetRounding) {
WARN_LOG(Log::JIT, "Detected rounding mode usage, rebuilding jit with checks");
// Won't loop, since hasSetRounding is only ever set to 1.
js.lastSetRounding = js.hasSetRounding;
cleanSlate = true;
}
// Drat. The VFPU hit an uneaten prefix at the end of a block.
if (js.startDefaultPrefix && js.MayHavePrefix()) {
WARN_LOG_REPORT(Log::JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4);
js.LogPrefix();
// Let's try that one more time. We won't get back here because we toggled the value.
js.startDefaultPrefix = false;
cleanSlate = true;
}
if (cleanSlate) {
// Our assumptions are all wrong so it's clean-slate time.
ClearCache();
Compile(em_address);
}
}
void Jit::RunLoopUntil(u64 globalticks) {
PROFILE_THIS_SCOPE("jit");
((void (*)())enterDispatcher)();
}
u32 Jit::GetCompilerPC() {
return js.compilerPC;
}
MIPSOpcode Jit::GetOffsetInstruction(int offset) {
const u32 addr = GetCompilerPC() + 4 * offset;
return Memory::IsValid4AlignedAddress(addr) ? ReadExecutedOp(addr) : Memory::Read_Instruction(addr);
}
void Jit::DoJit(u32 em_address, JitBlock *b) {
js.Begin(b);
// We add a check before the block, used when entering from a linked block.
b->checkedEntry = GetCodePtr();
// Downcount flag check. The last block decremented downcounter, and the flag should still be available.
FixupBranch skip = J_CC(CC_NS);
MOV(32, MIPSSTATE_VAR(pc), Imm32(js.blockStart));
JMP(outerLoop, true); // downcount hit zero - go advance.
SetJumpTarget(skip);
b->normalEntry = GetCodePtr();
gpr.Start(mips_, &js, &jo);
fpr.Start(mips_, &js, &jo, RipAccessible(&mips_->v[0]));
js.numInstructions = 0;
while (js.compiling) {
// Jit breakpoints are quite fast, so let's do them in release too.
CheckJitBreakpoint(GetCompilerPC(), 0);
MIPSOpcode inst = ReadExecutedOp(GetCompilerPC());
js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);
MIPSCompileOp(inst, this);
if (js.afterOp & JitState::AFTER_CORE_STATE) {
// CORE_RUNNING is <= CORE_NEXTFRAME.
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME)); // rip accessible
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm32(CORE_NEXTFRAME));
}
FixupBranch skipCheck = J_CC(CC_LE, true);
// All cases of AFTER_CORE_STATE should update PC. We don't update here.
RegCacheState state;
GetStateAndFlushAll(state);
WriteSyscallExit();
SetJumpTarget(skipCheck);
// If we didn't jump, we can keep our regs as they were.
RestoreState(state);
js.afterOp = JitState::AFTER_NONE;
}
js.compilerPC += 4;
js.numInstructions++;
if (jo.Disabled(JitDisable::REGALLOC_GPR)) {
gpr.Flush();
}
if (jo.Disabled(JitDisable::REGALLOC_FPR)) {
fpr.Flush();
FlushPrefixV();
}
// Safety check, in case we get a bunch of really large jit ops without a lot of branching.
if (GetSpaceLeft() < 0x800 || js.numInstructions >= JitBlockCache::MAX_BLOCK_INSTRUCTIONS) {
FlushAll();
WriteExit(GetCompilerPC(), js.nextExit++);
js.compiling = false;
}
}
b->codeSize = (u32)(GetCodePtr() - b->normalEntry);
NOP();
AlignCode4();
b->originalSize = js.numInstructions;
}
bool Jit::DescribeCodePtr(const u8 *ptr, std::string &name) {
if (ptr == applyRoundingMode)
name = "applyRoundingMode";
else if (ptr == dispatcher)
name = "dispatcher";
else if (ptr == dispatcherInEAXNoCheck)
name = "dispatcher (PC in EAX)";
else if (ptr == dispatcherNoCheck)
name = "dispatcherNoCheck";
else if (ptr == dispatcherCheckCoreState)
name = "dispatcherCheckCoreState";
else if (ptr == enterDispatcher)
name = "enterDispatcher";
else if (ptr == restoreRoundingMode)
name = "restoreRoundingMode";
else if (ptr == crashHandler)
name = "crashHandler";
else {
u32 jitAddr = blocks.GetAddressFromBlockPtr(ptr);
// Returns 0 when it's valid, but unknown.
if (jitAddr == 0) {
name = "UnknownOrDeletedBlock";
} else if (jitAddr != (u32)-1) {
char temp[1024];
const std::string label = g_symbolMap ? g_symbolMap->GetDescription(jitAddr) : "";
if (!label.empty())
snprintf(temp, sizeof(temp), "%08x_%s", jitAddr, label.c_str());
else
snprintf(temp, sizeof(temp), "%08x", jitAddr);
name = temp;
} else if (IsInSpace(ptr)) {
if (ptr < endOfPregeneratedCode) {
name = "PreGenCode";
} else {
name = "Unknown";
}
} else if (thunks.IsInSpace(ptr)) {
name = "Thunk";
} else if (safeMemFuncs.IsInSpace(ptr)) {
name = "JitSafeMem";
} else {
// Not anywhere in jit, then.
return false;
}
}
// If we got here, one of the above cases matched.
return true;
}
void Jit::LinkBlock(u8 *exitPoint, const u8 *checkedEntry) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_WRITE);
}
XEmitter emit(exitPoint);
// Okay, this is a bit ugly, but we check here if it already has a JMP.
// That means it doesn't have a full exit to pad with INT 3.
bool prelinked = *emit.GetCodePointer() == 0xE9;
emit.JMP(checkedEntry, true);
if (!prelinked) {
ptrdiff_t actualSize = emit.GetWritableCodePtr() - exitPoint;
int pad = JitBlockCache::GetBlockExitSize() - (int)actualSize;
for (int i = 0; i < pad; ++i) {
emit.INT3();
}
}
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(exitPoint, 32, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
void Jit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_WRITE);
}
// Send anyone who tries to run this block back to the dispatcher.
// Not entirely ideal, but .. pretty good.
// Spurious entrances from previously linked blocks can only come through checkedEntry
XEmitter emit(checkedEntry);
emit.MOV(32, MIPSSTATE_VAR(pc), Imm32(originalAddress));
emit.JMP(MIPSComp::jit->GetDispatcher(), true);
if (PlatformIsWXExclusive()) {
ProtectMemoryPages(checkedEntry, 16, MEM_PROT_READ | MEM_PROT_EXEC);
}
}
void Jit::Comp_ReplacementFunc(MIPSOpcode op) {
// We get here if we execute the first instruction of a replaced function. This means
// that we do need to return to RA.
// Inlined function calls (caught in jal) are handled differently.
// Memory never holds these: if it does, the game is executing data. Let the interpreter raise it.
if (Memory::Read_Instruction(GetCompilerPC()).encoding == op.encoding) {
Comp_Generic(op);
return;
}
int index = op.encoding & MIPS_REPLACEMENT_INDEX_MASK;
const ReplacementTableEntry *entry = GetReplacementFunc(index);
if (!entry) {
ERROR_LOG_REPORT_ONCE(replFunc, Log::HLE, "Invalid replacement op %08x at %08x", op.encoding, js.compilerPC);
return;
}
u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());
bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;
if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {
// We don't need to disable hooks, the code will still run.
if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {
// Any breakpoint at the func entry was already tripped, so we can still run the replacement.
// That's a common case - just to see how often the replacement hits.
disabled = g_breakpoints.RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));
}
}
Memory::Opcode origInstruction = Memory::Read_Instruction(GetCompilerPC());
if (disabled) {
MIPSCompileOp(origInstruction, this);
} else if (entry->jitReplaceFunc) {
MIPSReplaceFunc repl = entry->jitReplaceFunc;
int cycles = (this->*repl)();
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
// Compile the original instruction at this address. We ignore cycles for hooks.
MIPSCompileOp(origInstruction, this);
} else {
FlushAll();
MOV(32, R(ECX), MIPSSTATE_VAR(r[MIPS_REG_RA]));
js.downcountAmount += cycles;
WriteExitDestInReg(ECX);
js.compiling = false;
}
} else if (entry->replaceFunc) {
FlushAll();
// Standard function call, nothing fancy.
// The function returns the number of cycles it took in EAX.
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
RestoreRoundingMode();
ABI_CallFunction(entry->replaceFunc);
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
// Compile the original instruction at this address. We ignore cycles for hooks.
ApplyRoundingMode();
MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC()), this);
} else {
CMP(32, R(EAX), Imm32(0));
FixupBranch positive = J_CC(CC_GE);
MOV(32, R(ECX), MIPSSTATE_VAR(pc));
ADD(32, MIPSSTATE_VAR(downcount), R(EAX));
FixupBranch done = J();
SetJumpTarget(positive);
MOV(32, R(ECX), MIPSSTATE_VAR(r[MIPS_REG_RA]));
SUB(32, MIPSSTATE_VAR(downcount), R(EAX));
SetJumpTarget(done);
ApplyRoundingMode();
// Need to set flags again, ApplyRoundingMode destroyed them (and EAX.)
SUB(32, MIPSSTATE_VAR(downcount), Imm8(0));
WriteExitDestInReg(ECX);
js.compiling = false;
}
} else {
ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
}
}
void Jit::Comp_Generic(MIPSOpcode op) {
FlushAll();
MIPSInterpretFunc func = MIPSGetInterpretFunc(op);
_dbg_assert_msg_((MIPSGetInfo(op) & DELAYSLOT) == 0, "Cannot use interpreter for branch ops.");
const MIPSInfo info = MIPSGetInfo(op);
if (func)
{
// Interpreted ops run in the guest's rounding mode, like on the interpreter. A syscall is host code
// (like a compiled one), and only ctc1 to fcr31 changes the mode, so only those need it switched.
const bool isSyscall = (info & IS_SYSCALL) != 0;
const bool setsMode = MIPS_IS_CTC1_FCR31(op.encoding);
if (isSyscall)
RestoreRoundingMode();
if (setsMode)
UpdateRoundingMode();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
if (USE_JIT_MISSMAP)
ABI_CallFunctionC(&JitLogMiss, op.encoding);
else
ABI_CallFunctionC(&MIPSInterpretTrampoline, op.encoding);
if (setsMode)
RestoreRoundingMode(true);
if (isSyscall || setsMode)
ApplyRoundingMode(setsMode);
} else {
// These are basically always due to some kind of crash or corruption now.
ERROR_LOG(Log::JIT, "Trying to compile instruction %08x that can't be interpreted", op.encoding);
}
if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0)
{
// If it does eat them, it'll happen in MIPSCompileOp().
if ((info & OUT_EAT_PREFIX) == 0)
js.PrefixUnknown();
// Even if DISABLE'd, we want to set this flag so we overwrite.
if ((info & OUT_VFPU_PREFIX) != 0)
js.blockWrotePrefixes = true;
}
}
static void HitInvalidBranch(uint32_t dest) {
Core_ExecException(dest, currentMIPS->pc, ExecExceptionType::JUMP);
}
void Jit::WriteExit(u32 destination, int exit_num) {
_assert_msg_(exit_num < MAX_JIT_BLOCK_EXITS, "Expected a valid exit_num. dest=%08x", destination);
if (!Memory::IsValidAddress(destination) || (destination & 3) != 0) {
ERROR_LOG_REPORT(Log::JIT, "Trying to write block exit to illegal destination %08x: pc = %08x", destination, currentMIPS->pc);
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
ABI_CallFunctionC(&HitInvalidBranch, destination);
js.afterOp |= JitState::AFTER_CORE_STATE;
}
// If we need to verify coreState, we may not jump yet.
if (js.afterOp & JitState::AFTER_CORE_STATE) {
// CORE_RUNNING is <= CORE_NEXTFRAME.
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME)); // rip accessible
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm32(CORE_NEXTFRAME));
}
FixupBranch skipCheck = J_CC(CC_LE);
// All cases of AFTER_CORE_STATE should update PC. We don't update here.
WriteSyscallExit();
SetJumpTarget(skipCheck);
}
WriteDowncount();
//If nobody has taken care of this yet (this can be removed when all branches are done)
JitBlock *b = js.curBlock;
b->exitAddress[exit_num] = destination;
b->exitPtrs[exit_num] = GetWritableCodePtr();
// Link opportunity!
int block = blocks.GetBlockNumberFromStartAddress(destination);
if (block >= 0 && jo.enableBlocklink) {
// It exists! Joy of joy!
JMP(blocks.GetBlock(block)->checkedEntry, true);
b->linkStatus[exit_num] = true;
} else {
// No blocklinking.
MOV(32, MIPSSTATE_VAR(pc), Imm32(destination));
JMP(dispatcher, true);
// Normally, exits are 15 bytes (MOV + &pc + dest + JMP + dest) on 64 or 32 bit.
// But just in case we somehow optimized, pad.
ptrdiff_t actualSize = GetWritableCodePtr() - b->exitPtrs[exit_num];
int pad = JitBlockCache::GetBlockExitSize() - (int)actualSize;
for (int i = 0; i < pad; ++i) {
INT3();
}
}
}
static u32 IsValidJumpTarget(uint32_t addr) {
if (Memory::IsValidAddress(addr) && (addr & 3) == 0)
return 1;
return 0;
}
static void HitInvalidJumpReg(uint32_t source) {
Core_ExecException(currentMIPS->pc, source, ExecExceptionType::JUMP);
currentMIPS->pc = source + 8;
}
void Jit::WriteExitDestInReg(X64Reg reg) {
// If we need to verify coreState, we may not jump yet.
if (js.afterOp & JitState::AFTER_CORE_STATE) {
// CORE_RUNNING is <= CORE_NEXTFRAME.
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME)); // rip accessible
} else {
X64Reg temp = reg == RAX ? RDX : RAX;
MOV(PTRBITS, R(temp), ImmPtr((const void *)&coreState));
CMP(32, MatR(temp), Imm32(CORE_NEXTFRAME));
}
FixupBranch skipCheck = J_CC(CC_LE);
// All cases of AFTER_CORE_STATE should update PC. We don't update here.
WriteSyscallExit();
SetJumpTarget(skipCheck);
}
MOV(32, MIPSSTATE_VAR(pc), R(reg));
WriteDowncount();
// Validate the jump to avoid a crash?
if (!g_Config.bFastMemory) {
CMP(32, R(reg), Imm32(PSP_GetKernelMemoryBase()));
FixupBranch tooLow = J_CC(CC_B);
CMP(32, R(reg), Imm32(PSP_GetUserMemoryEnd()));
FixupBranch tooHigh = J_CC(CC_AE);
// Need to set neg flag again.
SUB(32, MIPSSTATE_VAR(downcount), Imm8(0));
if (reg == EAX)
J_CC(CC_NS, dispatcherInEAXNoCheck, true);
JMP(dispatcher, true);
SetJumpTarget(tooLow);
SetJumpTarget(tooHigh);
ABI_CallFunctionA((const void *)&IsValidJumpTarget, R(reg));
// If we're ignoring, coreState didn't trip - so trip it now.
CMP(32, R(EAX), Imm32(0));
FixupBranch skip = J_CC(CC_NE);
ABI_CallFunctionC(&HitInvalidJumpReg, GetCompilerPC());
SetJumpTarget(skip);
SUB(32, MIPSSTATE_VAR(downcount), Imm8(0));
JMP(dispatcherCheckCoreState, true);
} else if (reg == EAX) {
J_CC(CC_NS, dispatcherInEAXNoCheck, true);
JMP(dispatcher, true);
} else {
JMP(dispatcher, true);
}
}
void Jit::WriteSyscallExit() {
WriteDowncount();
JMP(dispatcherCheckCoreState, true);
}
bool Jit::CheckJitBreakpoint(u32 addr, int downcountOffset) {
if (g_breakpoints.NeedsBreakCheckAt(addr)) {
SaveFlags();
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
RestoreRoundingMode();
ABI_CallFunctionC(&JitBreakpoint, addr);
// If 0, the conditional breakpoint wasn't taken.
CMP(32, R(EAX), Imm32(0));
FixupBranch skip = J_CC(CC_Z);
WriteDowncount(downcountOffset);
ApplyRoundingMode();
// Just to fix the stack.
LoadFlags();
JMP(dispatcherCheckCoreState, true);
SetJumpTarget(skip);
ApplyRoundingMode();
LoadFlags();
return true;
}
return false;
}
void Jit::CheckMemoryBreakpoint(int instructionOffset, MIPSGPReg rs, int offset) {
if (!g_breakpoints.HasMemChecks())
return;
int totalInstructionOffset = instructionOffset + (js.inDelaySlot ? 1 : 0);
uint32_t checkedPC = GetCompilerPC() + totalInstructionOffset * 4;
int size = MIPSAnalyst::OpMemoryAccessSize(checkedPC);
bool isWrite = MIPSAnalyst::IsOpMemoryWrite(checkedPC);
// 0 because we normally execute before increasing.
int downcountOffset = js.inDelaySlot ? -2 : -1;
// TODO: In likely branches, downcount will be incorrect. This might make resume fail.
if (js.downcountAmount + downcountOffset < 0) {
downcountOffset = 0;
}
if (gpr.IsImm(rs)) {
uint32_t iaddr = gpr.GetImm(rs) + offset;
MemCheck check;
if (g_breakpoints.GetMemCheckInRange(iaddr, size, &check)) {
if (!(check.cond & MEMCHECK_READ) && !isWrite)
return;
if (!(check.cond & MEMCHECK_WRITE) && isWrite)
return;
// We need to flush, or conditions and log expressions will see old register values.
FlushAll();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
CallProtectedFunction(&JitMemCheck, iaddr, checkedPC);
CMP(32, R(RAX), Imm32(0));
FixupBranch skipCheck = J_CC(CC_E);
WriteDowncount(downcountOffset);
JMP(dispatcherCheckCoreState, true);
SetJumpTarget(skipCheck);
}
} else {
const auto memchecks = g_breakpoints.GetMemCheckRanges(isWrite);
bool possible = !memchecks.empty();
if (!possible)
return;
gpr.Lock(rs);
gpr.MapReg(rs, true, false);
LEA(32, RAX, MDisp(gpr.RX(rs), offset));
gpr.UnlockAll();
// We need to flush, or conditions and log expressions will see old register values.
FlushAll();
std::vector<FixupBranch> hitChecks;
hitChecks.reserve(memchecks.size());
for (auto it = memchecks.begin(), end = memchecks.end(); it != end; ++it) {
if (it->end != 0) {
CMP(32, R(RAX), Imm32(it->start - size));
FixupBranch skipNext = J_CC(CC_BE);
CMP(32, R(RAX), Imm32(it->end));
hitChecks.push_back(J_CC(CC_B, true));
SetJumpTarget(skipNext);
} else {
CMP(32, R(RAX), Imm32(it->start));
hitChecks.push_back(J_CC(CC_E, true));
}
}
FixupBranch noHits = J(true);
// Okay, now land any hit here.
for (auto &fixup : hitChecks)
SetJumpTarget(fixup);
hitChecks.clear();
MOV(32, MIPSSTATE_VAR(pc), Imm32(GetCompilerPC()));
CallProtectedFunction(&JitMemCheck, R(RAX), checkedPC);
CMP(32, R(RAX), Imm32(0));
FixupBranch skipCheck = J_CC(CC_E);
WriteDowncount(downcountOffset);
JMP(dispatcherCheckCoreState, true);
SetJumpTarget(skipCheck);
SetJumpTarget(noHits);
}
}
void Jit::CallProtectedFunction(const void *func, const OpArg &arg1) {
// We don't regcache RCX, so the below is safe (and also faster, maybe branch prediction?)
ABI_CallFunctionA(thunks.ProtectFunction(func, 1), arg1);
}
void Jit::CallProtectedFunction(const void *func, const OpArg &arg1, const OpArg &arg2) {
// We don't regcache RCX/RDX, so the below is safe (and also faster, maybe branch prediction?)
ABI_CallFunctionAA(thunks.ProtectFunction(func, 2), arg1, arg2);
}
void Jit::CallProtectedFunction(const void *func, const u32 arg1, const u32 arg2) {
// We don't regcache RCX/RDX, so the below is safe (and also faster, maybe branch prediction?)
ABI_CallFunctionCC(thunks.ProtectFunction(func, 2), arg1, arg2);
}
void Jit::CallProtectedFunction(const void *func, const OpArg &arg1, const u32 arg2) {
// We don't regcache RCX/RDX, so the below is safe (and also faster, maybe branch prediction?)
ABI_CallFunctionAC(thunks.ProtectFunction(func, 2), arg1, arg2);
}
#if PPSSPP_ARCH(AMD64)
void Jit::CallProtectedLeaf(const void *func) {
// The registers a call may clobber, apart from RAX and XMM0-1, which are scratch.
#ifdef _WIN32
static const X64Reg callerSavedGPRs[] = { RCX, RDX, R8, R9, R10, R11 };
const int lastXMM = 5;
const int shadowSpace = 32;
#else
static const X64Reg callerSavedGPRs[] = { RCX, RDX, R8, R9, R10, R11, RSI, RDI };
const int lastXMM = 15;
const int shadowSpace = 0;
#endif
X64Reg xmms[16];
X64Reg gprs[ARRAY_SIZE(callerSavedGPRs)];
int numXMMs = 0, numGPRs = 0;
for (int i = 2; i <= lastXMM; i++) {
if (fpr.IsXRegInUse((X64Reg)(XMM0 + i)))
xmms[numXMMs++] = (X64Reg)(XMM0 + i);
}
for (X64Reg reg : callerSavedGPRs) {
if (gpr.IsXRegInUse(reg))
gprs[numGPRs++] = reg;
}
// The JIT runs with RSP 16-byte aligned, so this keeps it aligned for the call.
const int gprBase = shadowSpace + numXMMs * 16;
const int frameSize = (gprBase + numGPRs * 8 + 15) & ~15;
if (frameSize)
SUB(64, R(RSP), Imm32(frameSize));
for (int i = 0; i < numXMMs; i++)
MOVAPS(MDisp(RSP, shadowSpace + i * 16), xmms[i]);
for (int i = 0; i < numGPRs; i++)
MOV(64, MDisp(RSP, gprBase + i * 8), R(gprs[i]));
ABI_CallFunction(func);
for (int i = 0; i < numXMMs; i++)
MOVAPS(xmms[i], MDisp(RSP, shadowSpace + i * 16));
for (int i = 0; i < numGPRs; i++)
MOV(64, R(gprs[i]), MDisp(RSP, gprBase + i * 8));
if (frameSize)
ADD(64, R(RSP), Imm32(frameSize));
}
#endif
void Jit::Comp_DoNothing(MIPSOpcode op) { }
} // namespace
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
-315
View File
@@ -1,315 +0,0 @@
// Copyright (c) 2012- 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/.
// TODO: Implement https://github.com/dolphin-emu/dolphin/pull/1025/commits/b597ec3e081a289d9ac782586617a876535183d6 .
#pragma once
#include "Common/CommonTypes.h"
#include "Common/Thunk.h"
#include "Common/x64Emitter.h"
#include "Common/x64Emitter.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/x86/JitSafeMem.h"
#include "Core/MIPS/x86/RegCache.h"
#include "Core/MIPS/x86/RegCacheFPU.h"
class PointerWrap;
namespace MIPSComp {
struct RegCacheState {
GPRRegCacheState gpr;
FPURegCacheState fpr;
};
class Jit : public Gen::XCodeBlock, public JitInterface, public MIPSFrontendInterface {
public:
Jit(MIPSState *mipsState);
~Jit();
const JitOptions &GetJitOptions() { return jo; }
void DoState(PointerWrap &p) override;
// Compiled ops should ignore delay slots
// the compiler will take care of them by itself
// OR NOT
void Comp_Generic(MIPSOpcode op) override;
void RunLoopUntil(u64 globalticks) override;
void Compile(u32 em_address) override; // Compiles a block at current MIPS PC
void DoJit(u32 em_address, JitBlock *b);
const u8 *GetCrashHandler() const override { return crashHandler; }
bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); }
bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
void Comp_ReplacementFunc(MIPSOpcode op) override;
// Ops
void Comp_ITypeMem(MIPSOpcode op) override;
void Comp_StoreSync(MIPSOpcode op) override;
void Comp_Cache(MIPSOpcode op) override;
void Comp_RelBranch(MIPSOpcode op) override;
void Comp_RelBranchRI(MIPSOpcode op) override;
void Comp_FPUBranch(MIPSOpcode op) override;
void Comp_FPULS(MIPSOpcode op) override;
void Comp_FPUComp(MIPSOpcode op) override;
void Comp_Jump(MIPSOpcode op) override;
void Comp_JumpReg(MIPSOpcode op) override;
void Comp_Syscall(MIPSOpcode op) override;
void Comp_Break(MIPSOpcode op) override;
void Comp_IType(MIPSOpcode op) override;
void Comp_RType2(MIPSOpcode op) override;
void Comp_RType3(MIPSOpcode op) override;
void Comp_ShiftType(MIPSOpcode op) override;
void Comp_Allegrex(MIPSOpcode op) override;
void Comp_Allegrex2(MIPSOpcode op) override;
void Comp_VBranch(MIPSOpcode op) override;
void Comp_MulDivType(MIPSOpcode op) override;
void Comp_Special3(MIPSOpcode op) override;
void Comp_FPU3op(MIPSOpcode op) override;
void Comp_FPU2op(MIPSOpcode op) override;
void Comp_mxc1(MIPSOpcode op) override;
void Comp_SV(MIPSOpcode op) override;
void Comp_SVQ(MIPSOpcode op) override;
void Comp_VPFX(MIPSOpcode op) override;
void Comp_VVectorInit(MIPSOpcode op) override;
void Comp_VMatrixInit(MIPSOpcode op) override;
void Comp_VDot(MIPSOpcode op) override;
void Comp_VecDo3(MIPSOpcode op) override;
void Comp_VV2Op(MIPSOpcode op) override;
void Comp_Mftv(MIPSOpcode op) override;
void Comp_Vmfvc(MIPSOpcode op) override;
void Comp_Vmtvc(MIPSOpcode op) override;
void Comp_Vmmov(MIPSOpcode op) override;
void Comp_VScl(MIPSOpcode op) override;
void Comp_Vmmul(MIPSOpcode op) override;
void Comp_Vmscl(MIPSOpcode op) override;
void Comp_Vtfm(MIPSOpcode op) override;
void Comp_VHdp(MIPSOpcode op) override;
void Comp_VCrs(MIPSOpcode op) override;
void Comp_VDet(MIPSOpcode op) override;
void Comp_Vi2x(MIPSOpcode op) override;
void Comp_Vx2i(MIPSOpcode op) override;
void Comp_Vf2i(MIPSOpcode op) override;
void Comp_Vi2f(MIPSOpcode op) override;
void Comp_Vh2f(MIPSOpcode op) override;
void Comp_Vcst(MIPSOpcode op) override;
void Comp_Vhoriz(MIPSOpcode op) override;
void Comp_VRot(MIPSOpcode op) override;
void Comp_VIdt(MIPSOpcode op) override;
void Comp_Vcmp(MIPSOpcode op) override;
void Comp_Vcmov(MIPSOpcode op) override;
void Comp_Viim(MIPSOpcode op) override;
void Comp_Vfim(MIPSOpcode op) override;
void Comp_VCrossQuat(MIPSOpcode op) override;
void Comp_Vsgn(MIPSOpcode op) override;
void Comp_Vocp(MIPSOpcode op) override;
void Comp_ColorConv(MIPSOpcode op) override;
void Comp_Vbfy(MIPSOpcode op) override;
void Comp_DoNothing(MIPSOpcode op) override;
int Replace_fabsf() override;
void ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz);
void ApplyPrefixD(const u8 *vregs, VectorSize sz);
void GetVectorRegsPrefixS(u8 *regs, VectorSize sz, int vectorReg) {
_assert_(js.prefixSFlag & JitState::PREFIX_KNOWN);
GetVectorRegs(regs, sz, vectorReg);
ApplyPrefixST(regs, js.prefixS, sz);
}
void GetVectorRegsPrefixT(u8 *regs, VectorSize sz, int vectorReg) {
_assert_(js.prefixTFlag & JitState::PREFIX_KNOWN);
GetVectorRegs(regs, sz, vectorReg);
ApplyPrefixST(regs, js.prefixT, sz);
}
void GetVectorRegsPrefixD(u8 *regs, VectorSize sz, int vectorReg);
void EatPrefix() override { js.EatPrefix(); }
void RestoreRoundingMode(bool force = false);
void ApplyRoundingMode(bool force = false);
void UpdateRoundingMode(u32 fcr31 = -1);
JitBlockCache *GetBlockCache() override { return &blocks; }
JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; }
void ClearCache() override;
void InvalidateCacheAt(u32 em_address, int length = 4) override {
if (blocks.RangeMayHaveBlocks(em_address, em_address + length)) {
blocks.InvalidateICache(em_address, length);
}
}
void UpdateFCR31() override;
const u8 *GetDispatcher() const override {
return dispatcher;
}
void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override;
void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override;
const u8 *GetCodeBase() const override { return GetCodePtr(); }
private:
void GenerateFixedCode(JitOptions &jo);
void GetStateAndFlushAll(RegCacheState &state);
void RestoreState(const RegCacheState& state);
void FlushAll();
void FlushPrefixV();
void WriteDowncount(int offset = 0);
u32 GetCompilerPC();
// See CompileDelaySlotFlags for flags.
void CompileDelaySlot(int flags, RegCacheState *state = NULL);
void CompileDelaySlot(int flags, RegCacheState &state) {
CompileDelaySlot(flags, &state);
}
void EatInstruction(MIPSOpcode op);
MIPSOpcode GetOffsetInstruction(int offset);
void WriteExit(u32 destination, int exit_num);
void WriteExitDestInReg(Gen::X64Reg reg);
// void WriteRfiExitDestInEAX();
void WriteSyscallExit();
bool CheckJitBreakpoint(u32 addr, int downcountOffset);
void CheckMemoryBreakpoint(int instructionOffset, MIPSGPReg rs, int offset);
// Utility compilation functions
void BranchFPFlag(MIPSOpcode op, Gen::CCFlags cc, bool likely);
void BranchVFPUFlag(MIPSOpcode op, Gen::CCFlags cc, bool likely);
void BranchRSZeroComp(MIPSOpcode op, Gen::CCFlags cc, bool andLink, bool likely);
void BranchRSRTComp(MIPSOpcode op, Gen::CCFlags cc, bool likely);
void BranchLog(MIPSOpcode op);
void BranchLogExit(MIPSOpcode op, u32 dest, bool useEAX);
// Utilities to reduce duplicated code
void CompImmLogic(MIPSOpcode op, void (XEmitter::*arith)(int, const Gen::OpArg &, const Gen::OpArg &));
void CompTriArith(MIPSOpcode op, void (XEmitter::*arith)(int, const Gen::OpArg &, const Gen::OpArg &), u32 (*doImm)(const u32, const u32), bool invertResult = false);
void CompShiftImm(MIPSOpcode op, void (XEmitter::*shift)(int, Gen::OpArg, Gen::OpArg), u32 (*doImm)(const u32, const u32));
void CompShiftVar(MIPSOpcode op, void (XEmitter::*shift)(int, Gen::OpArg, Gen::OpArg), u32 (*doImm)(const u32, const u32));
void CompITypeMemRead(MIPSOpcode op, u32 bits, void (XEmitter::*mov)(int, int, Gen::X64Reg, Gen::OpArg), const void *safeFunc);
template <typename T>
void CompITypeMemRead(MIPSOpcode op, u32 bits, void (XEmitter::*mov)(int, int, Gen::X64Reg, Gen::OpArg), T (*safeFunc)(u32 addr)) {
CompITypeMemRead(op, bits, mov, (const void *)safeFunc);
}
void CompITypeMemWrite(MIPSOpcode op, u32 bits, const void *safeFunc, bool makeRTWritable = false);
template <typename T>
void CompITypeMemWrite(MIPSOpcode op, u32 bits, void (*safeFunc)(T val, u32 addr)) {
CompITypeMemWrite(op, bits, (const void *)safeFunc);
}
void CompITypeMemUnpairedLR(MIPSOpcode op, bool isStore);
void CompITypeMemUnpairedLRInner(MIPSOpcode op, Gen::X64Reg shiftReg);
void CompBranchExits(Gen::CCFlags cc, u32 targetAddr, u32 notTakenAddr, const BranchInfo &branchInfo);
void CompBranchExit(bool taken, u32 targetAddr, u32 notTakenAddr, const BranchInfo &branchInfo);
static Gen::CCFlags FlipCCFlag(Gen::CCFlags flag);
static Gen::CCFlags SwapCCFlag(Gen::CCFlags flag);
void CopyFPReg(Gen::X64Reg dst, Gen::OpArg src);
void CompFPTriArith(MIPSOpcode op, void (XEmitter::*arith)(Gen::X64Reg reg, Gen::OpArg), bool orderMatters);
void CompFPComp(int lhs, int rhs, u8 compare, bool allowNaN = false);
void CompVrotShuffle(u8 *dregs, int imm, int n, bool negSin);
void CallProtectedFunction(const void *func, const Gen::OpArg &arg1);
void CallProtectedFunction(const void *func, const Gen::OpArg &arg1, const Gen::OpArg &arg2);
void CallProtectedFunction(const void *func, const Gen::OpArg &arg1, const u32 arg2);
void CallProtectedFunction(const void *func, const u32 arg1, const u32 arg2);
#if PPSSPP_ARCH(AMD64)
// A lighter CallProtectedFunction for a function that leaves MXCSR alone: saves only the
// caller-saved registers the caches are using. Set up the arguments first - the argument
// registers are never cached.
void CallProtectedLeaf(const void *func);
#endif
template <typename Tr, typename T1>
void CallProtectedFunction(Tr (*func)(T1), const Gen::OpArg &arg1) {
CallProtectedFunction((const void *)func, arg1);
}
template <typename Tr, typename T1, typename T2>
void CallProtectedFunction(Tr (*func)(T1, T2), const Gen::OpArg &arg1, const Gen::OpArg &arg2) {
CallProtectedFunction((const void *)func, arg1, arg2);
}
template <typename Tr, typename T1, typename T2>
void CallProtectedFunction(Tr(*func)(T1, T2), const Gen::OpArg &arg1, const u32 arg2) {
CallProtectedFunction((const void *)func, arg1, arg2);
}
template <typename Tr, typename T1, typename T2>
void CallProtectedFunction(Tr(*func)(T1, T2), const u32 arg1, const u32 arg2) {
CallProtectedFunction((const void *)func, arg1, arg2);
}
bool PredictTakeBranch(u32 targetAddr, bool likely);
u32 GetBlockShadowHookValue() const override {
return (u32)(blockShadowHook - GetBasePtr());
}
bool IsAtDispatchFetch(const u8 *codePtr) const override {
return codePtr == dispatcherFetch;
}
void SaveFlags();
void LoadFlags();
JitBlockCache blocks;
JitOptions jo;
JitState js;
GPRRegCache gpr;
FPURegCache fpr;
ThunkManager thunks;
JitSafeMemFuncs safeMemFuncs;
MIPSState *mips_;
const u8 *enterDispatcher;
const u8 *outerLoop;
const u8 *dispatcher;
const u8 *dispatcherCheckCoreState;
const u8 *dispatcherNoCheck;
const u8 *dispatcherInEAXNoCheck;
const u8 *dispatcherFetch;
const u8 *restoreRoundingMode;
const u8 *applyRoundingMode;
const u8 *endOfPregeneratedCode;
const u8 *crashHandler;
const u8 *blockShadowHook;
friend class JitSafeMem;
friend class JitSafeMemFuncs;
};
} // namespace MIPSComp
-488
View File
@@ -1,488 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Core/Config.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/MemMap.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/x86/Jit.h"
#include "Core/MIPS/x86/JitSafeMem.h"
#include "Core/System.h"
namespace MIPSComp
{
using namespace Gen;
using namespace X64JitConstants;
JitSafeMem::JitSafeMem(Jit *jit, MIPSGPReg raddr, s32 offset, u32 alignMask)
: jit_(jit), raddr_(raddr), offset_(offset), needsCheck_(false), needsSkip_(false), alignMask_(alignMask)
{
// Mask out the kernel RAM bit, because we'll end up with a negative offset to MEMBASEREG.
if (jit_->gpr.IsImm(raddr_))
iaddr_ = (jit_->gpr.GetImm(raddr_) + offset_) & 0x7FFFFFFF;
else
iaddr_ = (u32) -1;
fast_ = g_Config.bFastMemory || raddr == MIPS_REG_SP;
// If raddr_ is going to get loaded soon, load it now for more optimal code.
// We assume that it was already locked.
const int LOOKAHEAD_OPS = 3;
if (!jit_->gpr.R(raddr_).IsImm() && MIPSAnalyst::IsRegisterUsed(raddr_, jit_->GetCompilerPC() + 4, LOOKAHEAD_OPS))
jit_->gpr.MapReg(raddr_, true, false);
}
bool JitSafeMem::PrepareWrite(OpArg &dest, int size)
{
size_ = size;
// If it's an immediate, we can do the write if valid.
if (iaddr_ != (u32) -1)
{
if (ImmValid())
{
u32 addr = (iaddr_ & alignMask_);
#ifdef MASKED_PSP_MEMORY
addr &= Memory::MEMVIEW32_MASK;
#endif
#if PPSSPP_ARCH(32BIT)
dest = M(Memory::base + addr); // 32-bit only
#else
dest = MDisp(MEMBASEREG, addr);
#endif
return true;
}
else
return false;
}
// Otherwise, we always can do the write (conditionally.)
else
dest = PrepareMemoryOpArg(MEM_WRITE);
return true;
}
bool JitSafeMem::PrepareRead(OpArg &src, int size)
{
size_ = size;
if (iaddr_ != (u32) -1)
{
if (ImmValid())
{
u32 addr = (iaddr_ & alignMask_);
#ifdef MASKED_PSP_MEMORY
addr &= Memory::MEMVIEW32_MASK;
#endif
#if PPSSPP_ARCH(32BIT)
src = M(Memory::base + addr); // 32-bit only
#else
src = MDisp(MEMBASEREG, addr);
#endif
return true;
}
else
return false;
}
else
src = PrepareMemoryOpArg(MEM_READ);
return true;
}
OpArg JitSafeMem::NextFastAddress(int suboffset)
{
if (iaddr_ != (u32) -1)
{
u32 addr = (iaddr_ + suboffset) & alignMask_;
#ifdef MASKED_PSP_MEMORY
addr &= Memory::MEMVIEW32_MASK;
#endif
#if PPSSPP_ARCH(32BIT)
return M(Memory::base + addr); // 32-bit only
#else
return MDisp(MEMBASEREG, addr);
#endif
}
_dbg_assert_msg_((suboffset & alignMask_) == suboffset, "suboffset must be aligned");
#if PPSSPP_ARCH(32BIT)
return MDisp(xaddr_, (u32) Memory::base + offset_ + suboffset);
#else
return MComplex(MEMBASEREG, xaddr_, SCALE_1, offset_ + suboffset);
#endif
}
OpArg JitSafeMem::PrepareMemoryOpArg(MemoryOpType type)
{
// We may not even need to move into EAX as a temporary.
bool needTemp = alignMask_ != 0xFFFFFFFF;
#ifdef MASKED_PSP_MEMORY
bool needMask = true; // raddr_ != MIPS_REG_SP; // Commented out this speedhack due to low impact
// We always mask on 32 bit in fast memory mode.
needTemp = needTemp || (fast_ && needMask);
#endif
if (jit_->gpr.R(raddr_).IsSimpleReg() && !needTemp)
{
jit_->gpr.MapReg(raddr_, true, false);
xaddr_ = jit_->gpr.RX(raddr_);
}
else
{
jit_->MOV(32, R(EAX), jit_->gpr.R(raddr_));
xaddr_ = EAX;
}
if (!fast_)
{
// Is it in physical ram?
jit_->CMP(32, R(xaddr_), Imm32(PSP_GetKernelMemoryBase() - offset_));
tooLow_ = jit_->J_CC(CC_B);
jit_->CMP(32, R(xaddr_), Imm32(PSP_GetUserMemoryEnd() - offset_ - (size_ - 1)));
tooHigh_ = jit_->J_CC(CC_AE);
// We may need to jump back up here.
safe_ = jit_->GetCodePtr();
}
else
{
#ifdef MASKED_PSP_MEMORY
if (needMask) {
jit_->AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK));
}
#endif
}
// TODO: This could be more optimal, but the common case is that we want xaddr_ not to include offset_.
// Since we need to align them after add, we add and subtract.
if (alignMask_ != 0xFFFFFFFF)
{
jit_->ADD(32, R(xaddr_), Imm32(offset_));
jit_->AND(32, R(xaddr_), Imm32(alignMask_));
jit_->SUB(32, R(xaddr_), Imm32(offset_));
}
#if PPSSPP_ARCH(32BIT)
return MDisp(xaddr_, (u32) Memory::base + offset_);
#else
return MComplex(MEMBASEREG, xaddr_, SCALE_1, offset_);
#endif
}
void JitSafeMem::PrepareSlowAccess()
{
// Skip the fast path (which the caller wrote just now.)
skip_ = jit_->J(true);
needsSkip_ = true;
jit_->SetJumpTarget(tooLow_);
jit_->SetJumpTarget(tooHigh_);
// Might also be the scratchpad.
jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryBase() - offset_));
FixupBranch tooLow = jit_->J_CC(CC_B);
jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryEnd() - offset_ - (size_ - 1)));
jit_->J_CC(CC_B, safe_);
jit_->SetJumpTarget(tooLow);
}
bool JitSafeMem::PrepareSlowWrite()
{
// If it's immediate, we only need a slow write on invalid.
if (iaddr_ != (u32) -1)
return !fast_ && !ImmValid();
if (!fast_)
{
PrepareSlowAccess();
return true;
}
else
return false;
}
void JitSafeMem::DoSlowWrite(const void *safeFunc, const OpArg &src, int suboffset) {
_dbg_assert_msg_(safeFunc != nullptr, "Safe func cannot be null");
if (iaddr_ != (u32) -1)
jit_->MOV(32, R(EAX), Imm32((iaddr_ + suboffset) & alignMask_));
else
{
jit_->LEA(32, EAX, MDisp(xaddr_, offset_ + suboffset));
if (alignMask_ != 0xFFFFFFFF)
jit_->AND(32, R(EAX), Imm32(alignMask_));
}
#if PPSSPP_ARCH(32BIT)
jit_->PUSH(EDX);
#endif
if (!src.IsSimpleReg(EDX)) {
jit_->MOV(32, R(EDX), src);
}
if (!g_Config.bIgnoreBadMemAccess) {
jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC()));
}
// This is a special jit-ABI'd function.
if (jit_->CanCALLDirect(safeFunc)) {
jit_->CALL(safeFunc);
} else {
// We can't safely flush a reg, but this shouldn't be normal.
IndirectCALL(safeFunc);
}
#if PPSSPP_ARCH(32BIT)
jit_->POP(EDX);
#endif
needsCheck_ = true;
}
bool JitSafeMem::PrepareSlowRead(const void *safeFunc) {
_dbg_assert_msg_(safeFunc != nullptr, "Safe func cannot be null");
if (!fast_) {
if (iaddr_ != (u32) -1) {
// No slow read necessary.
if (ImmValid())
return false;
jit_->MOV(32, R(EAX), Imm32(iaddr_ & alignMask_));
} else {
PrepareSlowAccess();
jit_->LEA(32, EAX, MDisp(xaddr_, offset_));
if (alignMask_ != 0xFFFFFFFF)
jit_->AND(32, R(EAX), Imm32(alignMask_));
}
if (!g_Config.bIgnoreBadMemAccess) {
jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC()));
}
// This is a special jit-ABI'd function.
if (jit_->CanCALLDirect(safeFunc)) {
jit_->CALL(safeFunc);
} else {
// We can't safely flush a reg, but this shouldn't be normal.
IndirectCALL(safeFunc);
}
needsCheck_ = true;
return true;
}
else
return false;
}
void JitSafeMem::NextSlowRead(const void *safeFunc, int suboffset) {
_dbg_assert_msg_(safeFunc != nullptr, "Safe func cannot be null");
_dbg_assert_msg_(!fast_, "NextSlowRead() called in fast memory mode?");
// For simplicity, do nothing for 0. We already read in PrepareSlowRead().
if (suboffset == 0)
return;
if (jit_->gpr.IsImm(raddr_))
{
_dbg_assert_msg_(!Memory::IsValidAddress(iaddr_ + suboffset), "NextSlowRead() for an invalid immediate address?");
jit_->MOV(32, R(EAX), Imm32((iaddr_ + suboffset) & alignMask_));
}
// For GPR, if xaddr_ was the dest register, this will be wrong. Don't use in GPR.
else
{
jit_->LEA(32, EAX, MDisp(xaddr_, offset_ + suboffset));
if (alignMask_ != 0xFFFFFFFF)
jit_->AND(32, R(EAX), Imm32(alignMask_));
}
if (!g_Config.bIgnoreBadMemAccess) {
jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC()));
}
// This is a special jit-ABI'd function.
if (jit_->CanCALLDirect(safeFunc)) {
jit_->CALL(safeFunc);
} else {
// We can't safely flush a reg, but this shouldn't be normal.
IndirectCALL(safeFunc);
}
}
bool JitSafeMem::ImmValid()
{
return iaddr_ != (u32) -1 && Memory::IsValidAddress(iaddr_) && Memory::IsValidAddress(iaddr_ + size_ - 1);
}
void JitSafeMem::IndirectCALL(const void *safeFunc) {
#if PPSSPP_ARCH(32BIT)
jit_->PUSH(ECX);
jit_->SUB(PTRBITS, R(ESP), Imm8(16 - 4));
jit_->MOV(PTRBITS, R(ECX), ImmPtr(safeFunc));
jit_->CALLptr(R(RCX));
jit_->ADD(PTRBITS, R(ESP), Imm8(16 - 4));
jit_->POP(ECX);
#else
jit_->PUSH(RCX);
jit_->SUB(PTRBITS, R(RSP), Imm8(8));
jit_->MOV(PTRBITS, R(RCX), ImmPtr(safeFunc));
jit_->CALLptr(R(RCX));
jit_->ADD(64, R(RSP), Imm8(8));
jit_->POP(RCX);
#endif
}
void JitSafeMem::Finish()
{
// Memory::ReadOrException_U32/etc. may have tripped coreState.
if (needsCheck_ && !g_Config.bIgnoreBadMemAccess)
jit_->js.afterOp |= JitState::AFTER_CORE_STATE;
if (needsSkip_)
jit_->SetJumpTarget(skip_);
for (auto it = skipChecks_.begin(), end = skipChecks_.end(); it != end; ++it)
jit_->SetJumpTarget(*it);
}
static const int FUNCS_ARENA_SIZE = 512 * 1024;
void JitSafeMemFuncs::Init(ThunkManager *thunks) {
using namespace Gen;
AllocCodeSpace(FUNCS_ARENA_SIZE);
thunks_ = thunks;
BeginWrite(1024);
readU32 = GetCodePtr();
CreateReadFunc(32, (const void *)&Memory::ReadOrException_U32);
readU16 = GetCodePtr();
CreateReadFunc(16, (const void *)&Memory::ReadOrException_U16);
readU8 = GetCodePtr();
CreateReadFunc(8, (const void *)&Memory::ReadOrException_U8);
writeU32 = GetCodePtr();
CreateWriteFunc(32, (const void *)&Memory::WriteOrException_U32);
writeU16 = GetCodePtr();
CreateWriteFunc(16, (const void *)&Memory::WriteOrException_U16);
writeU8 = GetCodePtr();
CreateWriteFunc(8, (const void *)&Memory::WriteOrException_U8);
EndWrite();
}
void JitSafeMemFuncs::Shutdown() {
ResetCodePtr(0);
FreeCodeSpace();
readU32 = nullptr;
readU16 = nullptr;
readU8 = nullptr;
writeU32 = nullptr;
writeU16 = nullptr;
writeU8 = nullptr;
}
// Mini ABI:
// Read funcs take address in EAX, return in RAX.
// Write funcs take address in EAX, data in RDX.
// On x86-32, Write funcs also have an extra 4 bytes on the stack.
void JitSafeMemFuncs::CreateReadFunc(int bits, const void *fallbackFunc) {
CheckDirectEAX();
// Since we were CALLed, we need to align the stack before calling C++.
#if PPSSPP_ARCH(32BIT)
SUB(32, R(ESP), Imm8(16 - 4));
ABI_CallFunctionA(thunks_->ProtectFunction(fallbackFunc, 1), R(EAX));
ADD(32, R(ESP), Imm8(16 - 4));
#else
SUB(64, R(RSP), Imm8(0x28));
ABI_CallFunctionA(thunks_->ProtectFunction(fallbackFunc, 1), R(EAX));
ADD(64, R(RSP), Imm8(0x28));
#endif
RET();
StartDirectAccess();
#if PPSSPP_ARCH(32BIT)
MOVZX(32, bits, EAX, MDisp(EAX, (u32)Memory::base));
#else
MOVZX(32, bits, EAX, MRegSum(MEMBASEREG, EAX));
#endif
RET();
}
void JitSafeMemFuncs::CreateWriteFunc(int bits, const void *fallbackFunc) {
CheckDirectEAX();
// Since we were CALLed, we need to align the stack before calling C++.
#if PPSSPP_ARCH(32BIT)
// 4 for return, 4 for saved reg on stack.
SUB(32, R(ESP), Imm8(16 - 4 - 4));
ABI_CallFunctionAA(thunks_->ProtectFunction(fallbackFunc, 2), R(EDX), R(EAX));
ADD(32, R(ESP), Imm8(16 - 4 - 4));
#else
SUB(64, R(RSP), Imm8(0x28));
ABI_CallFunctionAA(thunks_->ProtectFunction(fallbackFunc, 2), R(EDX), R(EAX));
ADD(64, R(RSP), Imm8(0x28));
#endif
RET();
StartDirectAccess();
#if PPSSPP_ARCH(32BIT)
MOV(bits, MDisp(EAX, (u32)Memory::base), R(EDX));
#else
MOV(bits, MRegSum(MEMBASEREG, EAX), R(EDX));
#endif
RET();
}
void JitSafeMemFuncs::CheckDirectEAX() {
// Clear any cache/kernel bits.
AND(32, R(EAX), Imm32(0x3FFFFFFF));
CMP(32, R(EAX), Imm32(PSP_GetUserMemoryEnd()));
FixupBranch tooHighRAM = J_CC(CC_AE);
CMP(32, R(EAX), Imm32(PSP_GetKernelMemoryBase()));
skips_.push_back(J_CC(CC_AE));
CMP(32, R(EAX), Imm32(PSP_GetVidMemEnd()));
FixupBranch tooHighVid = J_CC(CC_AE);
CMP(32, R(EAX), Imm32(PSP_GetVidMemBase()));
skips_.push_back(J_CC(CC_AE));
CMP(32, R(EAX), Imm32(PSP_GetScratchpadMemoryEnd()));
FixupBranch tooHighScratch = J_CC(CC_AE);
CMP(32, R(EAX), Imm32(PSP_GetScratchpadMemoryBase()));
skips_.push_back(J_CC(CC_AE));
SetJumpTarget(tooHighRAM);
SetJumpTarget(tooHighVid);
SetJumpTarget(tooHighScratch);
}
void JitSafeMemFuncs::StartDirectAccess() {
for (auto it = skips_.begin(), end = skips_.end(); it != end; ++it) {
SetJumpTarget(*it);
}
skips_.clear();
}
};
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
-121
View File
@@ -1,121 +0,0 @@
// Copyright (c) 2012- 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 <vector>
class ThunkManager;
namespace MIPSComp {
class Jit;
class JitSafeMem {
public:
JitSafeMem(Jit *jit, MIPSGPReg raddr, s32 offset, u32 alignMask = 0xFFFFFFFF);
// Emit code necessary for a memory write, returns true if MOV to dest is needed.
bool PrepareWrite(Gen::OpArg &dest, int size);
// Emit code proceeding a slow write call, returns true if slow write is needed.
bool PrepareSlowWrite();
// Emit a slow write from src.
void DoSlowWrite(const void *safeFunc, const Gen::OpArg& src, int suboffset = 0);
template <typename T>
void DoSlowWrite(void (*safeFunc)(T val, u32 addr), const Gen::OpArg& src, int suboffset = 0) {
DoSlowWrite((const void *)safeFunc, src, suboffset);
}
// Emit code necessary for a memory read, returns true if MOV from src is needed.
bool PrepareRead(Gen::OpArg &src, int size);
// Emit code for a slow read call, and returns true if result is in EAX.
bool PrepareSlowRead(const void *safeFunc);
template <typename T>
bool PrepareSlowRead(T (*safeFunc)(u32 addr)) {
return PrepareSlowRead((const void *)safeFunc);
}
// Cleans up final code for the memory access.
void Finish();
// WARNING: Only works for non-GPR. Do not use for reads into GPR.
Gen::OpArg NextFastAddress(int suboffset);
// WARNING: Only works for non-GPR. Do not use for reads into GPR.
void NextSlowRead(const void *safeFunc, int suboffset);
template <typename T>
void NextSlowRead(T (*safeFunc)(u32 addr), int suboffset) {
NextSlowRead((const void *)safeFunc, suboffset);
}
private:
enum MemoryOpType {
MEM_READ,
MEM_WRITE,
};
Gen::OpArg PrepareMemoryOpArg(MemoryOpType type);
void PrepareSlowAccess();
bool ImmValid();
void IndirectCALL(const void *safeFunc);
Jit *jit_;
MIPSGPReg raddr_;
s32 offset_;
int size_;
bool needsCheck_;
bool needsSkip_;
bool fast_;
u32 alignMask_;
u32 iaddr_;
Gen::X64Reg xaddr_;
Gen::FixupBranch tooLow_, tooHigh_, skip_;
std::vector<Gen::FixupBranch> skipChecks_;
const u8 *safe_;
};
// Kept separate to avoid mistakes in the above class not using jit_.
class JitSafeMemFuncs : public Gen::XCodeBlock
{
public:
JitSafeMemFuncs() {
}
~JitSafeMemFuncs() {
Shutdown();
}
void Init(ThunkManager *thunks);
void Shutdown();
const u8 *readU32;
const u8 *readU16;
const u8 *readU8;
const u8 *writeU32;
const u8 *writeU16;
const u8 *writeU8;
private:
void CreateReadFunc(int bits, const void *fallbackFunc);
void CreateWriteFunc(int bits, const void *fallbackFunc);
void CheckDirectEAX();
void StartDirectAccess();
std::vector<Gen::FixupBranch> skips_;
ThunkManager *thunks_;
};
};
-422
View File
@@ -1,422 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include <cstring>
#include "Common/x64Emitter.h"
#include "Core/Reporting.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/x86/Jit.h"
#include "Core/MIPS/x86/RegCache.h"
using namespace Gen;
using namespace X64JitConstants;
static const X64Reg allocationOrder[] = {
// R12, when used as base register, for example in a LEA, can generate bad code! Need to look into this.
// On x64, RCX and RDX are the first args. CallProtectedFunction() assumes they're not regcached.
#if PPSSPP_ARCH(AMD64)
#ifdef _WIN32
RSI, RDI, R8, R9, R10, R11, R12, R13,
#else
RBP, R8, R9, R10, R11, R12, R13,
#endif
#elif PPSSPP_ARCH(X86)
ESI, EDI, EDX, ECX, EBX,
#endif
};
#if PPSSPP_ARCH(AMD64)
static X64Reg allocationOrderR15[ARRAY_SIZE(allocationOrder) + 1] = {INVALID_REG};
#endif
void GPRRegCache::FlushBeforeCall() {
// TODO: Only flush the non-preserved-by-callee registers.
Flush();
}
GPRRegCache::GPRRegCache() {
}
void GPRRegCache::Start(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo) {
#if PPSSPP_ARCH(AMD64)
if (allocationOrderR15[0] == INVALID_REG) {
memcpy(allocationOrderR15, allocationOrder, sizeof(allocationOrder));
allocationOrderR15[ARRAY_SIZE(allocationOrderR15) - 1] = R15;
}
#endif
mips_ = mipsState;
for (int i = 0; i < NUM_X_REGS; i++) {
xregs[i].free = true;
xregs[i].dirty = false;
xregs[i].allocLocked = false;
}
memset(regs, 0, sizeof(regs));
OpArg base = GetDefaultLocation(MIPS_REG_ZERO);
for (int i = 0; i < 32; i++) {
regs[i].location = base;
base.IncreaseOffset(sizeof(u32));
}
for (int i = 32; i < NUM_MIPS_GPRS; i++) {
regs[i].location = GetDefaultLocation(MIPSGPReg(i));
}
SetImm(MIPS_REG_ZERO, 0);
js_ = js;
jo_ = jo;
}
// these are MIPS reg indices
void GPRRegCache::Lock(MIPSGPReg p1, MIPSGPReg p2, MIPSGPReg p3, MIPSGPReg p4) {
regs[p1].locked = true;
if (p2 != MIPS_REG_INVALID) regs[p2].locked = true;
if (p3 != MIPS_REG_INVALID) regs[p3].locked = true;
if (p4 != MIPS_REG_INVALID) regs[p4].locked = true;
}
// these are x64 reg indices
void GPRRegCache::LockX(int x1, int x2, int x3, int x4) {
_assert_msg_(!xregs[x1].allocLocked, "RegCache: x %d already locked!", x1);
xregs[x1].allocLocked = true;
if (x2 != 0xFF) xregs[x2].allocLocked = true;
if (x3 != 0xFF) xregs[x3].allocLocked = true;
if (x4 != 0xFF) xregs[x4].allocLocked = true;
}
void GPRRegCache::UnlockAll() {
for (int i = 0; i < NUM_MIPS_GPRS; i++)
regs[i].locked = false;
// In case it was stored, discard it now.
SetImm(MIPS_REG_ZERO, 0);
}
void GPRRegCache::UnlockAllX() {
for (int i = 0; i < NUM_X_REGS; i++)
xregs[i].allocLocked = false;
}
X64Reg GPRRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) {
int allocCount;
const X64Reg *allocOrder = GetAllocationOrder(allocCount);
static const int UNUSED_LOOKAHEAD_OPS = 30;
*clobbered = false;
for (int i = 0; i < allocCount; i++) {
X64Reg reg = allocOrder[i];
if (xregs[reg].allocLocked)
continue;
if (xregs[reg].mipsReg != MIPS_REG_INVALID && regs[xregs[reg].mipsReg].locked)
continue;
// Awesome, a clobbered reg. Let's use it.
if (MIPSAnalyst::IsRegisterClobbered(xregs[reg].mipsReg, js_->compilerPC, UNUSED_LOOKAHEAD_OPS)) {
*clobbered = true;
return reg;
}
// Not awesome. A used reg. Let's try to avoid spilling.
if (unusedOnly && MIPSAnalyst::IsRegisterUsed(xregs[reg].mipsReg, js_->compilerPC, UNUSED_LOOKAHEAD_OPS)) {
continue;
}
return reg;
}
return INVALID_REG;
}
X64Reg GPRRegCache::GetFreeXReg()
{
int aCount;
const X64Reg *aOrder = GetAllocationOrder(aCount);
for (int i = 0; i < aCount; i++)
{
X64Reg xr = aOrder[i];
if (!xregs[xr].allocLocked && xregs[xr].free)
{
return xr;
}
}
//Okay, not found :( Force grab one
bool clobbered;
X64Reg bestToSpill = FindBestToSpill(true, &clobbered);
if (bestToSpill == INVALID_REG) {
bestToSpill = FindBestToSpill(false, &clobbered);
}
if (bestToSpill != INVALID_REG) {
// TODO: Broken somehow in Dante's Inferno, but most games work. Bad flags in MIPSTables somewhere?
if (clobbered) {
DiscardRegContentsIfCached(xregs[bestToSpill].mipsReg);
} else {
StoreFromRegister(xregs[bestToSpill].mipsReg);
}
return bestToSpill;
}
// Still no dice? Give up.
_assert_msg_(false, "Regcache ran out of regs");
return (X64Reg)-1;
}
void GPRRegCache::FlushR(X64Reg reg)
{
if (reg >= NUM_X_REGS) {
_assert_msg_(false, "Flushing non existent reg");
} else if (!xregs[reg].free) {
StoreFromRegister(xregs[reg].mipsReg);
}
}
void GPRRegCache::FlushRemap(MIPSGPReg oldreg, MIPSGPReg newreg) {
OpArg oldLocation = regs[oldreg].location;
_assert_msg_(oldLocation.IsSimpleReg(), "FlushRemap: Must already be in an x86 register");
X64Reg xr = oldLocation.GetSimpleReg();
if (oldreg == newreg) {
xregs[xr].dirty = true;
return;
}
StoreFromRegister(oldreg);
// Now, if newreg already was mapped somewhere, get rid of that.
DiscardRegContentsIfCached(newreg);
// Now, take over the old register.
regs[newreg].location = oldLocation;
regs[newreg].away = true;
regs[newreg].locked = true;
xregs[xr].mipsReg = newreg;
xregs[xr].dirty = true;
xregs[xr].free = false;
}
int GPRRegCache::SanityCheck() const {
for (int i = 0; i < NUM_MIPS_GPRS; i++) {
const MIPSGPReg r = MIPSGPReg(i);
if (regs[i].away) {
if (regs[i].location.IsSimpleReg()) {
Gen::X64Reg simple = regs[i].location.GetSimpleReg();
if (xregs[simple].allocLocked)
return 1;
if (xregs[simple].mipsReg != r)
return 2;
}
else if (regs[i].location.IsImm())
return 3;
}
}
return 0;
}
void GPRRegCache::DiscardRegContentsIfCached(MIPSGPReg preg) {
if (regs[preg].away && regs[preg].location.IsSimpleReg()) {
X64Reg xr = regs[preg].location.GetSimpleReg();
xregs[xr].free = true;
xregs[xr].dirty = false;
xregs[xr].mipsReg = MIPS_REG_INVALID;
regs[preg].away = false;
if (preg == MIPS_REG_ZERO) {
regs[preg].location = Imm32(0);
} else {
regs[preg].location = GetDefaultLocation(preg);
}
}
}
void GPRRegCache::DiscardR(MIPSGPReg preg) {
if (regs[preg].away) {
if (regs[preg].location.IsSimpleReg()) {
DiscardRegContentsIfCached(preg);
} else {
regs[preg].away = false;
if (preg == MIPS_REG_ZERO) {
regs[preg].location = Imm32(0);
} else {
regs[preg].location = GetDefaultLocation(preg);
}
}
}
}
void GPRRegCache::SetImm(MIPSGPReg preg, u32 immValue) {
// ZERO is always zero. Let's just make sure.
if (preg == MIPS_REG_ZERO)
immValue = 0;
DiscardRegContentsIfCached(preg);
regs[preg].away = true;
regs[preg].location = Imm32(immValue);
}
bool GPRRegCache::IsImm(MIPSGPReg preg) const {
// Note that ZERO is generally always imm.
return regs[preg].location.IsImm();
}
u32 GPRRegCache::GetImm(MIPSGPReg preg) const {
_dbg_assert_msg_(IsImm(preg), "Reg %d must be an immediate.", preg);
// Always 0 for ZERO.
if (preg == MIPS_REG_ZERO)
return 0;
return regs[preg].location.GetImmValue();
}
const X64Reg *GPRRegCache::GetAllocationOrder(int &count) {
#if PPSSPP_ARCH(AMD64)
if (!jo_->reserveR15ForAsm) {
count = ARRAY_SIZE(allocationOrderR15);
return allocationOrderR15;
}
#endif
count = ARRAY_SIZE(allocationOrder);
return allocationOrder;
}
OpArg GPRRegCache::GetDefaultLocation(MIPSGPReg reg) const {
if (reg < 32) {
return MDisp(CTXREG, -128 + reg * 4);
}
switch (reg) {
case MIPS_REG_HI:
return MIPSSTATE_VAR(hi);
case MIPS_REG_LO:
return MIPSSTATE_VAR(lo);
case MIPS_REG_FPCOND:
return MIPSSTATE_VAR(fpcond);
case MIPS_REG_VFPUCC:
return MIPSSTATE_VAR(vfpuCtrl[VFPU_CTRL_CC]);
default:
ERROR_LOG_REPORT(Log::JIT, "Bad mips register %d", reg);
return MIPSSTATE_VAR(r[0]);
}
}
void GPRRegCache::KillImmediate(MIPSGPReg preg, bool doLoad, bool makeDirty) {
if (regs[preg].away) {
if (regs[preg].location.IsImm())
MapReg(preg, doLoad, makeDirty);
else if (regs[preg].location.IsSimpleReg())
xregs[RX(preg)].dirty |= makeDirty;
}
}
void GPRRegCache::MapReg(MIPSGPReg i, bool doLoad, bool makeDirty) {
if (!regs[i].away && regs[i].location.IsImm()) {
_assert_msg_(false, "Bad immediate");
}
if (!regs[i].away || regs[i].location.IsImm()) {
X64Reg xr = GetFreeXReg();
_assert_msg_(!xregs[xr].dirty, "Xreg already dirty");
_assert_msg_(!xregs[xr].allocLocked, "GetFreeXReg returned locked register");
xregs[xr].free = false;
xregs[xr].mipsReg = i;
xregs[xr].dirty = makeDirty || regs[i].location.IsImm();
OpArg newloc = ::Gen::R(xr);
if (doLoad) {
// Force ZERO to be 0.
if (i == MIPS_REG_ZERO)
emit->MOV(32, newloc, Imm32(0));
else
emit->MOV(32, newloc, regs[i].location);
}
for (int j = 0; j < 32; j++) {
if (i != MIPSGPReg(j) && regs[j].location.IsSimpleReg(xr)) {
_assert_msg_(false, "BindToRegister: Strange condition");
}
}
regs[i].away = true;
regs[i].location = newloc;
} else {
// reg location must be simplereg; memory locations
// and immediates are taken care of above.
xregs[RX(i)].dirty |= makeDirty;
}
_assert_msg_(!xregs[RX(i)].allocLocked, "This reg should have been flushed (r%d)", i);
}
void GPRRegCache::StoreFromRegister(MIPSGPReg i) {
if (regs[i].away) {
bool doStore;
if (regs[i].location.IsSimpleReg()) {
X64Reg xr = RX(i);
xregs[xr].free = true;
xregs[xr].mipsReg = MIPS_REG_INVALID;
doStore = xregs[xr].dirty;
xregs[xr].dirty = false;
} else {
//must be immediate - do nothing
doStore = true;
}
OpArg newLoc = GetDefaultLocation(i);
// But never store to ZERO.
if (doStore && i != MIPS_REG_ZERO)
emit->MOV(32, newLoc, regs[i].location);
regs[i].location = newLoc;
regs[i].away = false;
}
}
void GPRRegCache::Flush() {
for (int i = 0; i < NUM_X_REGS; i++) {
_assert_msg_(!xregs[i].allocLocked, "Someone forgot to unlock X64 reg %d.", i);
}
SetImm(MIPS_REG_ZERO, 0);
for (int i = 1; i < NUM_MIPS_GPRS; i++) {
const MIPSGPReg r = MIPSGPReg(i);
_assert_msg_(!regs[i].locked, "Somebody forgot to unlock MIPS reg %d.", i);
if (regs[i].away) {
if (regs[i].location.IsSimpleReg()) {
X64Reg xr = RX(r);
StoreFromRegister(r);
xregs[xr].dirty = false;
}
else if (regs[i].location.IsImm()) {
StoreFromRegister(r);
} else {
_assert_msg_(false, "Jit64 - Flush unhandled case, reg %d PC: %08x", i, mips_->pc);
}
}
}
}
void GPRRegCache::GetState(GPRRegCacheState &state) const {
memcpy(state.regs, regs, sizeof(regs));
memcpy(state.xregs, xregs, sizeof(xregs));
}
void GPRRegCache::RestoreState(const GPRRegCacheState& state) {
memcpy(regs, state.regs, sizeof(regs));
memcpy(xregs, state.xregs, sizeof(xregs));
}
#endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
-140
View File
@@ -1,140 +0,0 @@
// Copyright (c) 2012- 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 "ppsspp_config.h"
#include "Common/x64Emitter.h"
#include "Core/MIPS/MIPS.h"
namespace X64JitConstants {
#if PPSSPP_ARCH(AMD64)
const Gen::X64Reg MEMBASEREG = Gen::RBX;
const Gen::X64Reg CTXREG = Gen::R14;
const Gen::X64Reg JITBASEREG = Gen::R15;
#else
const Gen::X64Reg CTXREG = Gen::EBP;
#endif
// This must be one of EAX, EBX, ECX, EDX as they have 8-bit subregisters.
const Gen::X64Reg TEMPREG = Gen::EAX;
const int NUM_MIPS_GPRS = 36;
#if PPSSPP_ARCH(AMD64)
const u32 NUM_X_REGS = 16;
#elif PPSSPP_ARCH(X86)
const u32 NUM_X_REGS = 8;
#endif
}
struct MIPSCachedReg {
Gen::OpArg location;
bool away; // value not in source register
bool locked;
};
struct X64CachedReg {
MIPSGPReg mipsReg;
bool dirty;
bool free;
bool allocLocked;
};
struct GPRRegCacheState {
MIPSCachedReg regs[X64JitConstants::NUM_MIPS_GPRS];
X64CachedReg xregs[X64JitConstants::NUM_X_REGS];
};
namespace MIPSComp {
struct JitOptions;
struct JitState;
}
class GPRRegCache
{
public:
GPRRegCache();
~GPRRegCache() {}
void Start(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo);
void DiscardRegContentsIfCached(MIPSGPReg preg);
void DiscardR(MIPSGPReg preg);
void SetEmitter(Gen::XEmitter *emitter) {emit = emitter;}
void FlushR(Gen::X64Reg reg);
void FlushLockX(Gen::X64Reg reg) {
FlushR(reg);
LockX(reg);
}
void FlushLockX(Gen::X64Reg reg1, Gen::X64Reg reg2) {
FlushR(reg1); FlushR(reg2);
LockX(reg1); LockX(reg2);
}
void Flush();
void FlushBeforeCall();
// Flushes one register and reuses the register for another one. Dirtyness is implied.
void FlushRemap(MIPSGPReg oldreg, MIPSGPReg newreg);
int SanityCheck() const;
void KillImmediate(MIPSGPReg preg, bool doLoad, bool makeDirty);
void MapReg(MIPSGPReg preg, bool doLoad = true, bool makeDirty = true);
void StoreFromRegister(MIPSGPReg preg);
const Gen::OpArg &R(MIPSGPReg preg) const {return regs[preg].location;}
Gen::X64Reg RX(MIPSGPReg preg) const
{
if (regs[preg].away && regs[preg].location.IsSimpleReg())
return regs[preg].location.GetSimpleReg();
_assert_msg_(false, "Not so simple - %d", preg);
return (Gen::X64Reg)-1;
}
Gen::OpArg GetDefaultLocation(MIPSGPReg reg) const;
// Register locking.
void Lock(MIPSGPReg p1, MIPSGPReg p2 = MIPS_REG_INVALID, MIPSGPReg p3 = MIPS_REG_INVALID, MIPSGPReg p4 = MIPS_REG_INVALID);
void LockX(int x1, int x2=0xff, int x3=0xff, int x4=0xff);
void UnlockAll();
void UnlockAllX();
void SetImm(MIPSGPReg preg, u32 immValue);
bool IsImm(MIPSGPReg preg) const;
u32 GetImm(MIPSGPReg preg) const;
void GetState(GPRRegCacheState &state) const;
void RestoreState(const GPRRegCacheState& state);
// Whether the register holds something the cache cares about: a MIPS register or a locked temp.
bool IsXRegInUse(Gen::X64Reg reg) const {
return !xregs[reg].free || xregs[reg].allocLocked;
}
MIPSState *mips_ = nullptr;
private:
Gen::X64Reg GetFreeXReg();
Gen::X64Reg FindBestToSpill(bool unusedOnly, bool *clobbered);
const Gen::X64Reg *GetAllocationOrder(int &count);
MIPSCachedReg regs[X64JitConstants::NUM_MIPS_GPRS]{};
X64CachedReg xregs[X64JitConstants::NUM_X_REGS]{};
Gen::XEmitter *emit = nullptr;
MIPSComp::JitState *js_;
MIPSComp::JitOptions *jo_;
};
File diff suppressed because it is too large. Load diff
-248
View File
@@ -1,248 +0,0 @@
// Copyright (c) 2012- 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/x64Emitter.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSVFPUUtils.h"
#undef MAP_NOINIT
// GPRs are numbered 0 to 31
// VFPU regs are numbered 32 to 159.
// Then we have some temp regs for VFPU handling from 160 to 175.
// Temp regs: 4 from S prefix, 4 from T prefix, 4 from D mask, and 4 for work (worst case.)
// But most of the time prefixes aren't used that heavily so we won't use all of them.
// PLANS FOR PROPER SIMD
// 1, 2, 3, and 4-vectors will be loaded into single XMM registers
// Matrices will be loaded into pairs, triads, or quads of XMM registers - simply by loading
// the columns or the rows one by one.
// On x86 this means that only one 4x4 matrix can be fully loaded at once but that's alright.
// We might want to keep "linearized" columns in memory.
// Implement optimized vec/matrix multiplications of all types and transposes that
// take into account in which XMM registers the values are. Fallback: Just dump out the values
// and do it the old way.
#include "ppsspp_config.h"
enum {
TEMP0 = 32 + 128,
NUM_MIPS_FPRS = 32 + 128 + MIPSState::NUM_X86_FPU_TEMPS,
};
#if PPSSPP_ARCH(AMD64)
#define NUM_X_FPREGS 16
#elif PPSSPP_ARCH(X86)
#define NUM_X_FPREGS 8
#endif
struct X64CachedFPReg {
union {
int mipsReg;
int mipsRegs[4];
};
bool dirty;
};
struct MIPSCachedFPReg {
Gen::OpArg location;
int lane;
bool away; // value not in source register (memory)
u8 locked;
// Only for temp regs.
bool tempLocked;
};
struct FPURegCacheState {
MIPSCachedFPReg regs[NUM_MIPS_FPRS];
X64CachedFPReg xregs[NUM_X_FPREGS];
};
namespace MIPSComp {
struct JitOptions;
struct JitState;
}
enum {
MAP_DIRTY = 1,
MAP_NOINIT = 2 | MAP_DIRTY,
// Only for MapRegsV, MapRegsVS.
MAP_NOLOCK = 4,
};
// The PSP has 160 FP registers: 32 FPRs + 128 VFPU registers.
// Soon we will support them all.
class FPURegCache
{
public:
FPURegCache();
~FPURegCache() {}
void Start(MIPSState *mipsState, MIPSComp::JitState *js, MIPSComp::JitOptions *jo, bool useRip);
void MapReg(int preg, bool doLoad = true, bool makeDirty = true);
void StoreFromRegister(int preg);
void StoreFromRegisterV(int preg) {
StoreFromRegister(preg + 32);
}
Gen::OpArg GetDefaultLocation(int reg) const;
void DiscardR(int freg);
void DiscardV(int vreg) {
DiscardR(vreg + 32);
}
void DiscardVS(int vreg);
bool IsTempX(Gen::X64Reg xreg);
int GetTempR();
int GetTempV() {
return GetTempR() - 32;
}
int GetTempVS(u8 *v, VectorSize vsz);
void SetEmitter(Gen::XEmitter *emitter) {emit = emitter;}
// Flushes one register and reuses the register for another one. Dirtyness is implied.
void FlushRemap(int oldreg, int newreg);
void Flush();
int SanityCheck() const;
const Gen::OpArg &R(int freg) const {return regs[freg].location;}
const Gen::OpArg &V(int vreg) const {
_dbg_assert_msg_(vregs[vreg].lane == 0, "SIMD reg %d used as V reg (use VS instead). pc=%08x", vreg, mips_->pc);
return vregs[vreg].location;
}
const Gen::OpArg &VS(const u8 *vs) const {
_dbg_assert_msg_(vregs[vs[0]].lane != 0, "V reg %d used as VS reg (use V instead). pc=%08x", vs[0], mips_->pc);
return vregs[vs[0]].location;
}
Gen::X64Reg RX(int freg) const {
if (regs[freg].away && regs[freg].location.IsSimpleReg())
return regs[freg].location.GetSimpleReg();
_assert_msg_(false, "Not so simple - f%i", freg);
return (Gen::X64Reg)-1;
}
Gen::X64Reg VX(int vreg) const {
_dbg_assert_msg_(vregs[vreg].lane == 0, "SIMD reg %d used as V reg (use VSX instead). pc=%08x", vreg, mips_->pc);
if (vregs[vreg].away && vregs[vreg].location.IsSimpleReg())
return vregs[vreg].location.GetSimpleReg();
_assert_msg_(false, "Not so simple - v%i", vreg);
return (Gen::X64Reg)-1;
}
Gen::X64Reg VSX(const u8 *vs) const {
_dbg_assert_msg_(vregs[vs[0]].lane != 0, "V reg %d used as VS reg (use VX instead). pc=%08x", vs[0], mips_->pc);
if (vregs[vs[0]].away && vregs[vs[0]].location.IsSimpleReg())
return vregs[vs[0]].location.GetSimpleReg();
_assert_msg_(false, "Not so simple - v%i", vs[0]);
return (Gen::X64Reg)-1;
}
// Just to avoid coding mistakes, defined here to prevent compilation.
void R(Gen::X64Reg r);
// Register locking. Prevents them from being spilled.
void SpillLock(int p1, int p2=0xff, int p3=0xff, int p4=0xff);
void ReleaseSpillLock(int mipsreg);
void ReleaseSpillLocks();
bool IsMapped(int r) {
return R(r).IsSimpleReg();
}
bool IsMappedV(int v) {
return vregs[v].lane == 0 && V(v).IsSimpleReg();
}
// Whether the register holds a MIPS register or a temp.
bool IsXRegInUse(Gen::X64Reg reg) const {
return xregs[reg].mipsReg != -1;
}
bool IsMappedVS(u8 v) {
return vregs[v].lane != 0 && VS(&v).IsSimpleReg();
}
bool IsMappedVS(const u8 *v, VectorSize vsz);
bool CanMapVS(const u8 *v, VectorSize vsz);
void MapRegV(int vreg, int flags);
void MapRegsV(int vec, VectorSize vsz, int flags);
void MapRegsV(const u8 *v, VectorSize vsz, int flags);
void SpillLockV(int vreg) {
SpillLock(vreg + 32);
}
void SpillLockV(const u8 *v, VectorSize vsz);
void SpillLockV(int vec, VectorSize vsz);
void ReleaseSpillLockV(int vreg) {
ReleaseSpillLock(vreg + 32);
}
void ReleaseSpillLockV(const u8 *vec, VectorSize sz);
// TODO: This may trash XMM0/XMM1 some day.
void MapRegsVS(const u8 *v, VectorSize vsz, int flags);
bool TryMapRegsVS(const u8 *v, VectorSize vsz, int flags);
bool TryMapDirtyInVS(const u8 *vd, VectorSize vdsz, const u8 *vs, VectorSize vssz, bool avoidLoad = true);
bool TryMapDirtyInInVS(const u8 *vd, VectorSize vdsz, const u8 *vs, VectorSize vssz, const u8 *vt, VectorSize vtsz, bool avoidLoad = true);
// TODO: If s/t overlap differently, need read-only copies? Maybe finalize d? Major design flaw...
// TODO: Matrix versions? Cols/Rows?
// No MapRegVS, that'd be silly.
void SimpleRegsV(const u8 *v, VectorSize vsz, int flags);
void SimpleRegsV(const u8 *v, MatrixSize msz, int flags);
void SimpleRegV(const u8 v, int flags);
void GetState(FPURegCacheState &state) const;
void RestoreState(const FPURegCacheState& state);
MIPSState *mips_ = nullptr;
void FlushX(Gen::X64Reg reg);
Gen::X64Reg GetFreeXReg();
int GetFreeXRegs(Gen::X64Reg *regs, int n, bool spill = true);
void Invariant() const;
private:
const int *GetAllocationOrder(int &count);
void SetupInitialRegs();
// These are intentionally not public so the interface is "locked" or "unlocked", no levels.
void ReduceSpillLock(int mreg);
void ReduceSpillLockV(int vreg) {
ReduceSpillLock(vreg + 32);
}
void ReduceSpillLockV(const u8 *vec, VectorSize sz);
Gen::X64Reg LoadRegsVS(const u8 *v, int n);
MIPSCachedFPReg regs[NUM_MIPS_FPRS]{};
X64CachedFPReg xregs[NUM_X_FPREGS]{};
MIPSCachedFPReg *vregs;
bool useRip_;
bool pendingFlush;
bool initialReady = false;
MIPSCachedFPReg regsInitial[NUM_MIPS_FPRS];
X64CachedFPReg xregsInitial[NUM_X_FPREGS];
Gen::XEmitter *emit = nullptr;
MIPSComp::JitState *js_;
MIPSComp::JitOptions *jo_;
};
+2 -5
View File
@@ -152,13 +152,10 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) {
});
if (!canUseJit) {
core->HideChoice(1);
core->HideChoice(3);
}
// TODO: Enable "JIT using IR" on more architectures. ARM32 needs more testing.
// Note also that Loongarch and RISC-V only have a jit-ir backend, and it's used for the JIT option, so the fourth option isn't shown.
#if !PPSSPP_ARCH(X86) && !PPSSPP_ARCH(AMD64) && !PPSSPP_ARCH(ARM64)
// Every native JIT except ARM32's is IR-based and used for the JIT option, and ARM32 uses its
// legacy JIT for both, so "JIT using IR" is the same as "JIT" everywhere.
core->HideChoice(3);
#endif
// Only the interpreter raises these, but don't hide it behind the CPU core choice - it'd just
// look like the setting had vanished.
+13 -5
View File
@@ -33,6 +33,14 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) {
return;
}
// Every native JIT except ARM32's is IR-based.
#if PPSSPP_ARCH(ARM)
const bool hasIR = core == CPUCore::IR_INTERPRETER;
#else
const bool hasIR = true;
#endif
const bool hasTarget = core == CPUCore::JIT || core == CPUCore::JIT_IR;
// Options menu
if (ImGui::BeginPopup("Options")) {
// ImGui::Checkbox("Auto-scroll", &AutoScroll);
@@ -55,7 +63,7 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) {
// Three or four columns: One table for blocks that can be sorted in various ways,
// then the remaining are either MIPS/IR/TARGET or MIPS/TARGET or MIPS/IR depending on the JIT type.
int numColumns = 3;
if (core == CPUCore::JIT_IR) {
if (hasIR && hasTarget) {
numColumns = 4;
}
@@ -135,10 +143,10 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) {
if (ImGui::BeginTable("columns", numColumns, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Blocks");
ImGui::TableSetupColumn("MIPS");
if (core == CPUCore::JIT_IR || core == CPUCore::IR_INTERPRETER) {
if (hasIR) {
ImGui::TableSetupColumn("IR");
}
if (core == CPUCore::JIT_IR || core == CPUCore::JIT) {
if (hasTarget) {
ImGui::TableSetupColumn(TARGET);
}
@@ -221,7 +229,7 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) {
}
ImGui::EndChild();
if (core == CPUCore::JIT_IR || core == CPUCore::IR_INTERPRETER) {
if (hasIR) {
ImGui::TableNextColumn();
ImGui::BeginChild("IRPane", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar);
@@ -232,7 +240,7 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) {
ImGui::EndChild();
}
if (core == CPUCore::JIT_IR || core == CPUCore::JIT) {
if (hasTarget) {
ImGui::TableNextColumn();
ImGui::BeginChild("TargetPane", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar);
-28
View File
@@ -276,9 +276,6 @@
<ClInclude Include="..\..\Core\MemFault.h" />
<ClInclude Include="..\..\Core\MemMap.h" />
<ClInclude Include="..\..\Core\MemMapHelpers.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64Jit.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64RegCache.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64RegCacheFPU.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64IRJit.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64IRRegCache.h" />
<ClInclude Include="..\..\Core\MIPS\ARM\ArmJit.h" />
@@ -309,10 +306,6 @@
<ClInclude Include="..\..\Core\MIPS\MIPSTables.h" />
<ClInclude Include="..\..\Core\MIPS\MIPSVFPUUtils.h" />
<ClInclude Include="..\..\Core\MIPS\MIPSTracer.h" />
<ClInclude Include="..\..\Core\MIPS\x86\Jit.h" />
<ClInclude Include="..\..\Core\MIPS\x86\JitSafeMem.h" />
<ClInclude Include="..\..\Core\MIPS\x86\RegCache.h" />
<ClInclude Include="..\..\Core\MIPS\x86\RegCacheFPU.h" />
<ClInclude Include="..\..\Core\MIPS\x86\X64IRJit.h" />
<ClInclude Include="..\..\Core\MIPS\x86\X64IRRegCache.h" />
<ClInclude Include="..\..\Core\Opcode.h" />
@@ -561,16 +554,6 @@
<ClCompile Include="..\..\Core\MemFault.cpp" />
<ClCompile Include="..\..\Core\MemMap.cpp" />
<ClCompile Include="..\..\Core\MemMapFunctions.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64Asm.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompBranch.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompLoadStore.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompReplace.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompVFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64Jit.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64RegCache.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64RegCacheFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64IRAsm.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64IRCompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64IRCompBranch.cpp" />
@@ -620,17 +603,6 @@
<ClCompile Include="..\..\Core\MIPS\MIPSTables.cpp" />
<ClCompile Include="..\..\Core\MIPS\MIPSVFPUUtils.cpp" />
<ClCompile Include="..\..\Core\MIPS\MIPSTracer.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\Asm.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompBranch.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompLoadStore.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompReplace.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompVFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\Jit.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\JitSafeMem.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\RegCache.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\RegCacheFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\X64IRAsm.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompBranch.cpp" />
-28
View File
@@ -183,16 +183,6 @@
<ClCompile Include="..\..\Core\MemFault.cpp" />
<ClCompile Include="..\..\Core\MemMap.cpp" />
<ClCompile Include="..\..\Core\MemMapFunctions.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64Asm.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompBranch.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompLoadStore.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompReplace.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64CompVFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64Jit.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64RegCache.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64RegCacheFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64IRAsm.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64IRCompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\ARM64\Arm64IRCompBranch.cpp" />
@@ -242,17 +232,6 @@
<ClCompile Include="..\..\Core\MIPS\MIPSTables.cpp" />
<ClCompile Include="..\..\Core\MIPS\MIPSVFPUUtils.cpp" />
<ClCompile Include="..\..\Core\MIPS\MIPSTracer.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\Asm.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompBranch.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompLoadStore.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompReplace.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\CompVFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\Jit.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\JitSafeMem.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\RegCache.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\RegCacheFPU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\X64IRAsm.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompALU.cpp" />
<ClCompile Include="..\..\Core\MIPS\x86\X64IRCompBranch.cpp" />
@@ -617,9 +596,6 @@
<ClInclude Include="..\..\Core\MemFault.h" />
<ClInclude Include="..\..\Core\MemMap.h" />
<ClInclude Include="..\..\Core\MemMapHelpers.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64Jit.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64RegCache.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64RegCacheFPU.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64IRJit.h" />
<ClInclude Include="..\..\Core\MIPS\ARM64\Arm64IRRegCache.h" />
<ClInclude Include="..\..\Core\MIPS\ARM\ArmJit.h" />
@@ -650,10 +626,6 @@
<ClInclude Include="..\..\Core\MIPS\MIPSTables.h" />
<ClInclude Include="..\..\Core\MIPS\MIPSVFPUUtils.h" />
<ClInclude Include="..\..\Core\MIPS\MIPSTracer.h" />
<ClInclude Include="..\..\Core\MIPS\x86\Jit.h" />
<ClInclude Include="..\..\Core\MIPS\x86\JitSafeMem.h" />
<ClInclude Include="..\..\Core\MIPS\x86\RegCache.h" />
<ClInclude Include="..\..\Core\MIPS\x86\RegCacheFPU.h" />
<ClInclude Include="..\..\Core\MIPS\x86\X64IRJit.h" />
<ClInclude Include="..\..\Core\MIPS\x86\X64IRRegCache.h" />
<ClInclude Include="..\..\Core\Opcode.h" />
-21
View File
@@ -498,17 +498,6 @@ LOCAL_WHOLE_STATIC_LIBRARIES += ppsspp_common libchdr lua pugixml
ifeq ($(TARGET_ARCH_ABI),x86_64)
ARCH_FILES := \
$(SRC)/Core/MIPS/x86/CompALU.cpp \
$(SRC)/Core/MIPS/x86/CompBranch.cpp \
$(SRC)/Core/MIPS/x86/CompFPU.cpp \
$(SRC)/Core/MIPS/x86/CompLoadStore.cpp \
$(SRC)/Core/MIPS/x86/CompVFPU.cpp \
$(SRC)/Core/MIPS/x86/CompReplace.cpp \
$(SRC)/Core/MIPS/x86/Asm.cpp \
$(SRC)/Core/MIPS/x86/Jit.cpp \
$(SRC)/Core/MIPS/x86/JitSafeMem.cpp \
$(SRC)/Core/MIPS/x86/RegCache.cpp \
$(SRC)/Core/MIPS/x86/RegCacheFPU.cpp \
$(SRC)/Core/MIPS/x86/X64IRAsm.cpp \
$(SRC)/Core/MIPS/x86/X64IRCompALU.cpp \
$(SRC)/Core/MIPS/x86/X64IRCompBranch.cpp \
@@ -535,16 +524,6 @@ ARCH_FILES := \
ArmEmitterTest.cpp
else ifeq ($(findstring arm64-v8a,$(TARGET_ARCH_ABI)),arm64-v8a)
ARCH_FILES := \
$(SRC)/Core/MIPS/ARM64/Arm64CompALU.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64CompBranch.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64CompFPU.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64CompLoadStore.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64CompVFPU.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64CompReplace.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64Asm.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64Jit.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64RegCache.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64IRAsm.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64IRCompALU.cpp \
$(SRC)/Core/MIPS/ARM64/Arm64IRCompBranch.cpp \
+7 -8
View File
@@ -27,7 +27,7 @@ Environment variables:
| `PPSSPP_CPUFUZZ_CLASSES` | weighted string of `a` (ALU), `i` (immediate), `f` (FPU), `v` (VFPU), `m` (memory) |
| `PPSSPP_CPUFUZZ_CODE` | comma-separated hex instructions to run instead of the generated code |
| `PPSSPP_CPUFUZZ_CODE_SEED` | apply `PPSSPP_CPUFUZZ_CODE` to this seed only |
| `PPSSPP_CPUFUZZ_SKIP` | cores to skip, by digit: 1 IR interpreter, 2 JIT, 3 JIT IR |
| `PPSSPP_CPUFUZZ_SKIP` | cores to skip, by digit: 1 IR interpreter, 2 JIT |
| `PPSSPP_CPUFUZZ_REPORTS` | how many differences to minimize and print (default 20) |
| `PPSSPP_CPUFUZZ_VERBOSE` | print every case before running it, to find one that crashes |
| `PPSSPP_CPUFUZZ_DUMP` | write every core's end state of every case to a file |
@@ -99,7 +99,8 @@ PSP's. Where matching the PSP is expensive, agreeing with each other comes first
### Answered on hardware
Each of these has a pspautotests test recorded on a PSP (6.61). Distinct outputs across the eight
configurations (four cores, ARM64 and x86-64 under Rosetta), and lines that differ from the PSP:
configurations (four cores, ARM64 and x86-64 under Rosetta), and lines that differ from the PSP.
The `jit` columns were measured with the legacy JITs, since removed; `jit` is now `jit-ir` on both.
| Test | Distinct outputs | ARM64 interp / ir / jit / jit-ir | x86-64 interp / ir / jit / jit-ir |
|---|---|---|---|
@@ -159,7 +160,7 @@ sign.
`0x7F800001` or `0xFF800001`. The sign is + for add, sub and every summing op, the XOR of the
operand signs for mul, div and vscl, the input's sign for vrcp, vrsq, vsin and vasin, and + for
vsqrt, vlog2 and vexp2. vsat0/vsat1, vabs and vneg pass NaN bits through. The cores make the
host's NaN instead, so x86-64 and ARM disagree (and the ARM64 JIT's vsat quiets a NaN). The
host's NaN instead, so x86-64 and ARM disagree. The
branch `cpu-equiv-4` fixes the single-lane ops (vadd, vsub, vmul, vdiv, vscl, vcrs, vsat0/1, vbfy,
vocp, vsocp) on every core, through four new IR ops and checks in the legacy JITs, and is held
back until its cost on every multiplication is measured. The horizontal ops wait for the
@@ -173,7 +174,7 @@ sign.
around, vtfm/vhtfm size mismatches, vmfvc past register 15 reading 0, vcmov with bit 20 set),
now including `vcmov` with imm3 = 7, which tests a CC bit that is always clear: vcmovt never
moves and vcmovf always does (fixed on every core). The JITs still differ on the single-lane
dot products and vcrsp/vqmul at .s/.p (for the accurate dot product), and the IR and x86 JIT on
dot products and vcrsp/vqmul at .s/.p (for the accurate dot product), and the IR cores on
matrix registers whose offset doesn't fit the size.
### Known, no hardware test needed
@@ -181,7 +182,6 @@ sign.
8. **Divide-by-zero flags.** Only the interpreter sets the cause and flag bits for `div.s` by zero
(see `Int_FPU3op`). The other cores leave fcr31 alone. Making the others match needs a new IR op
or an interpreter fallback for div.s, and the latter costs every division.
9. The legacy ARM64 JIT ignores the D prefix's write mask for `viim.s`/`vfim.s`.
### ARM32
@@ -205,9 +205,8 @@ UNITTEST=1 SKIPAPP=1`, run over adb). ARM32 has three cores: `CreateNativeJit` r
### Not looked into yet
- With VFPU code in the mix, the legacy x86-64 JIT differs from the interpreter in 205 of 2000
default cases and the legacy ARM64 JIT in 127, against 147 for the IR cores. Most of those
looked at are the horizontal ops (items 4 and 6) and item 7.
- With VFPU code in the mix, the IR cores differ from the interpreter in 147 of 2000 default
cases. Most of those looked at are the horizontal ops (items 4 and 6) and item 7.
- Branches, delay slots, and VFPU prefixes across blocks: the generated code is straight-line.
## Unrelated
-21
View File
@@ -939,16 +939,6 @@ ifeq ($(WITH_DYNAREC),1)
else ifeq ($(TARGET_ARCH),arm64)
DYNAFLAGS += -D_ARCH_64
SOURCES_CXX += $(COMMONDIR)/Arm64Emitter.cpp \
$(COREDIR)/MIPS/ARM64/Arm64Asm.cpp \
$(COREDIR)/MIPS/ARM64/Arm64CompALU.cpp \
$(COREDIR)/MIPS/ARM64/Arm64CompBranch.cpp \
$(COREDIR)/MIPS/ARM64/Arm64CompFPU.cpp \
$(COREDIR)/MIPS/ARM64/Arm64CompLoadStore.cpp \
$(COREDIR)/MIPS/ARM64/Arm64CompVFPU.cpp \
$(COREDIR)/MIPS/ARM64/Arm64CompReplace.cpp \
$(COREDIR)/MIPS/ARM64/Arm64Jit.cpp \
$(COREDIR)/MIPS/ARM64/Arm64RegCache.cpp \
$(COREDIR)/MIPS/ARM64/Arm64RegCacheFPU.cpp \
$(COREDIR)/MIPS/ARM64/Arm64IRAsm.cpp \
$(COREDIR)/MIPS/ARM64/Arm64IRCompALU.cpp \
$(COREDIR)/MIPS/ARM64/Arm64IRCompBranch.cpp \
@@ -981,17 +971,6 @@ ifeq ($(WITH_DYNAREC),1)
$(COMMONDIR)/x64Analyzer.cpp \
$(COMMONDIR)/ABI.cpp \
$(COMMONDIR)/Thunk.cpp \
$(COREDIR)/MIPS/x86/CompReplace.cpp \
$(COREDIR)/MIPS/x86/CompBranch.cpp \
$(COREDIR)/MIPS/x86/Asm.cpp \
$(COREDIR)/MIPS/x86/CompALU.cpp \
$(COREDIR)/MIPS/x86/CompVFPU.cpp \
$(COREDIR)/MIPS/x86/CompLoadStore.cpp \
$(COREDIR)/MIPS/x86/CompFPU.cpp \
$(COREDIR)/MIPS/x86/Jit.cpp \
$(COREDIR)/MIPS/x86/JitSafeMem.cpp \
$(COREDIR)/MIPS/x86/RegCache.cpp \
$(COREDIR)/MIPS/x86/RegCacheFPU.cpp \
$(COREDIR)/MIPS/x86/X64IRAsm.cpp \
$(COREDIR)/MIPS/x86/X64IRCompALU.cpp \
$(COREDIR)/MIPS/x86/X64IRCompBranch.cpp \
+3 -10
View File
@@ -206,7 +206,7 @@ bool TestJit() {
printf("\n");
double jit_speed = 0.0, jit_ir_speed = 0.0, ir_speed = 0.0, interp_speed = 0.0;
double jit_speed = 0.0, ir_speed = 0.0, interp_speed = 0.0;
if (compileSuccess) {
interp_speed = ExecCPUTest();
checkMemoryClean("Interpreter");
@@ -215,12 +215,8 @@ bool TestJit() {
checkMemoryClean("IR interpreter");
MIPSComp::jit->ClearCache();
mipsr4k.UpdateCore(CPUCore::JIT);
jit_speed = ExecCPUTest(false);
jit_speed = ExecCPUTest(false); // not clearing, so the below can do things.
checkMemoryClean("JIT");
MIPSComp::jit->ClearCache();
mipsr4k.UpdateCore(CPUCore::JIT_IR);
jit_ir_speed = ExecCPUTest(false); // not clearing, so the below can do things.
checkMemoryClean("JIT IR");
// Disassemble
JitBlockCacheDebugInterface *cache = MIPSComp::jit->GetBlockCacheDebugInterface();
@@ -235,7 +231,7 @@ bool TestJit() {
if (lines.size() > cutoff)
printf("...\n");
}
printf("Jit was %fx faster than interp, IR was %fx faster, JIT IR %fx.\n\n", jit_speed / interp_speed, ir_speed / interp_speed, jit_ir_speed / interp_speed);
printf("Jit was %fx faster than interp, IR was %fx faster.\n\n", jit_speed / interp_speed, ir_speed / interp_speed);
}
printf("\n");
@@ -270,7 +266,6 @@ static bool CheckReplacementHooks(u32 func, u32 size, const std::vector<u8> &cod
static const CPUCore cores[] = {
CPUCore::JIT,
CPUCore::IR_INTERPRETER,
CPUCore::JIT_IR,
};
for (CPUCore core : cores) {
mipsr4k.UpdateCore(core);
@@ -921,7 +916,6 @@ bool TestCPUEquivalence() {
CPUCore::INTERPRETER,
CPUCore::IR_INTERPRETER,
CPUCore::JIT,
CPUCore::JIT_IR,
};
const int numCores = (int)ARRAY_SIZE(cores);
@@ -1096,7 +1090,6 @@ bool TestCPURoundingMode() {
static const CPUCore cores[] = {
CPUCore::IR_INTERPRETER,
CPUCore::JIT,
CPUCore::JIT_IR,
};
// No flags, then ctc1 left to the interpreter, then everything the JITs can leave to it.
static const u32 disableFlags[] = { 0, (u32)MIPSComp::JitDisable::FPU_XFER, 0xFFFF };
-2
View File
@@ -5,8 +5,6 @@
#include "Common/CPUDetect.h"
#include "Common/x64Analyzer.h"
#include "Common/x64Emitter.h"
#include "Core/MIPS/x86/RegCacheFPU.h"
#include "Core/MIPS/x86/Jit.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/MIPSVFPUUtils.h"