mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
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:
46 files changed
+38
-19314
No files matched your search
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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)
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -169,5 +169,5 @@ namespace MIPSComp {
|
||||
|
||||
void DoDummyJitState(PointerWrap &p);
|
||||
|
||||
JitInterface *CreateNativeJit(MIPSState *mipsState, bool useIR);
|
||||
JitInterface *CreateNativeJit(MIPSState *mipsState);
|
||||
}
|
||||
+2
-2
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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)
|
||||
@@ -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
@@ -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_;
|
||||
};
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user