2015-03-05 23:31:03 +01:00
// 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/.
2016-10-12 17:32:52 +02:00
# include "ppsspp_config.h"
2020-11-03 00:13:52 +01:00
2016-10-12 17:32:52 +02:00
# if PPSSPP_ARCH(ARM64)
2020-08-15 12:25:39 +02:00
# include "Common/Log.h"
# include "Common/MemoryUtil.h"
# include "Common/CPUDetect.h"
# include "Common/Arm64Emitter.h"
2015-03-05 23:31:03 +01:00
# include "Core/MemMap.h"
# include "Core/MIPS/MIPS.h"
# include "Core/System.h"
# include "Core/CoreTiming.h"
# include "Core/MIPS/ARM64/Arm64Jit.h"
# include "Core/MIPS/JitCommon/JitCommon.h"
using namespace Arm64Gen ;
//static int temp32; // unused?
2015-03-15 23:38:21 +01:00
static const bool enableDebug = false ;
2017-01-27 14:18:29 +01:00
static const bool enableDisasm = false ;
2015-03-05 23:31:03 +01:00
//static bool enableStatistics = false; //unused?
2015-03-07 17:58:15 +01:00
// 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
2015-03-05 23:31:03 +01:00
2015-03-07 17:58:15 +01:00
// So: Scratch registers: x16, x17
// Mappable registers in priority order:
2015-03-18 16:04:52 +01:00
// 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,
2015-03-07 17:58:15 +01:00
// 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):
2015-07-03 09:58:56 -07:00
// x23 : Down counter
// x24 : PC save on JR with non-nice delay slot (to be eliminated later?)
2015-03-07 22:29:44 +01:00
// x25 : MSR/MRS temporary (to be eliminated later)
2015-03-07 17:58:15 +01:00
// x26 : JIT base reg
2015-03-07 22:29:44 +01:00
// x27 : MIPS state (Could eliminate by placing the MIPS state right at the memory base)
2015-03-07 17:58:15 +01:00
// x28 : Memory base pointer.
2015-03-05 23:31:03 +01:00
extern volatile CoreState coreState ;
2015-07-05 20:50:03 +02:00
void ShowPC ( u32 downcount , void * membase , void * jitbase ) {
2015-03-07 23:05:31 +01:00
static int count = 0 ;
2015-03-05 23:31:03 +01:00
if ( currentMIPS ) {
2020-08-15 12:25:39 +02:00
ERROR_LOG ( JIT , " ShowPC : %08x Downcount : %08x %d %p %p " , currentMIPS - > pc , downcount , count , membase , jitbase ) ;
2015-03-05 23:31:03 +01:00
} else {
2020-08-15 12:25:39 +02:00
ERROR_LOG ( JIT , " Universe corrupt? " ) ;
2015-03-05 23:31:03 +01:00
}
2015-03-07 23:05:31 +01:00
//if (count > 2000)
// exit(0);
count + + ;
2015-03-05 23:31:03 +01:00
}
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 ;
2015-07-05 20:50:03 +02:00
void Arm64Jit : : GenerateFixedCode ( const JitOptions & jo ) {
2022-11-20 09:35:05 -08:00
BeginWrite ( GetMemoryProtectPageSize ( ) ) ;
2016-08-28 13:35:27 +02:00
const u8 * start = AlignCodePage ( ) ;
2016-08-28 14:52:08 +02:00
2015-07-06 21:16:49 +02:00
if ( jo . useStaticAlloc ) {
saveStaticRegisters = AlignCode16 ( ) ;
STR ( INDEX_UNSIGNED , DOWNCOUNTREG , CTXREG , offsetof ( MIPSState , downcount ) ) ;
2015-10-08 14:43:16 +02:00
gpr . EmitSaveStaticRegisters ( ) ;
2015-07-06 21:16:49 +02:00
RET ( ) ;
2015-07-05 20:50:03 +02:00
2015-07-06 21:16:49 +02:00
loadStaticRegisters = AlignCode16 ( ) ;
2015-10-08 14:43:16 +02:00
gpr . EmitLoadStaticRegisters ( ) ;
2015-07-06 21:16:49 +02:00
LDR ( INDEX_UNSIGNED , DOWNCOUNTREG , CTXREG , offsetof ( MIPSState , downcount ) ) ;
RET ( ) ;
start = saveStaticRegisters ;
} else {
saveStaticRegisters = nullptr ;
loadStaticRegisters = nullptr ;
}
2015-07-05 20:50:03 +02:00
2015-10-08 19:58:37 +02:00
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 ) ;
2015-10-10 13:11:38 +02:00
// We can skip if the rounding mode is nearest (0) and flush is not set.
// (as restoreRoundingMode cleared it out anyway)
CMPI2R ( SCRATCH2 , 0 ) ;
2015-10-08 19:58:37 +02:00
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 ) ;
2015-10-10 14:32:34 +02:00
// Actually change the system FPCR register
2015-10-08 19:58:37 +02:00
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 ) ;
2015-10-10 13:30:30 +02:00
SetJumpTarget ( skip ) ;
2015-10-08 19:58:37 +02:00
RET ( ) ;
}
updateRoundingMode = AlignCode16 ( ) ; {
LDR ( INDEX_UNSIGNED , SCRATCH2 , CTXREG , offsetof ( MIPSState , fcr31 ) ) ;
2018-03-31 21:03:02 -07:00
// Set SCRATCH2 to FZ:RM (FZ is bit 24, and RM are lowest 2 bits.)
2015-10-08 19:58:37 +02:00
TSTI2R ( SCRATCH2 , 1 < < 24 ) ;
ANDI2R ( SCRATCH2 , SCRATCH2 , 3 ) ;
FixupBranch skip = B ( CC_EQ ) ;
ADDI2R ( SCRATCH2 , SCRATCH2 , 4 ) ;
SetJumpTarget ( skip ) ;
2015-10-10 14:32:34 +02:00
// 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 ) ;
2015-10-10 11:56:59 +02:00
RET ( ) ;
2015-10-08 19:58:37 +02:00
}
enterDispatcher = AlignCode16 ( ) ;
2015-03-07 17:58:15 +01:00
2017-01-26 19:34:21 +01:00
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 ) ;
2015-03-07 17:58:15 +01:00
// Fixed registers, these are always kept when in Jit context.
MOVP2R ( MEMBASEREG , Memory : : base ) ;
MOVP2R ( CTXREG , mips_ ) ;
MOVP2R ( JITBASEREG , GetBasePtr ( ) ) ;
2015-07-05 20:50:03 +02:00
LoadStaticRegisters ( ) ;
2015-03-07 17:58:15 +01:00
MovFromPC ( SCRATCH1 ) ;
2015-03-07 22:29:44 +01:00
outerLoopPCInSCRATCH1 = GetCodePtr ( ) ;
2015-03-07 17:58:15 +01:00
MovToPC ( SCRATCH1 ) ;
outerLoop = GetCodePtr ( ) ;
2015-07-05 20:50:03 +02:00
SaveStaticRegisters ( ) ; // Advance can change the downcount, so must save/restore
2015-03-07 17:58:15 +01:00
RestoreRoundingMode ( true ) ;
QuickCallFunction ( SCRATCH1_64 , & CoreTiming : : Advance ) ;
ApplyRoundingMode ( true ) ;
2015-07-05 20:50:03 +02:00
LoadStaticRegisters ( ) ;
2017-03-14 12:32:20 +01:00
FixupBranch skipToCoreStateCheck = B ( ) ; //skip the downcount check
2015-03-07 17:58:15 +01:00
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 ) ;
2017-03-14 12:32:20 +01:00
SetJumpTarget ( skipToCoreStateCheck ) ;
2015-03-07 17:58:15 +01:00
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 ) {
2015-03-15 23:38:21 +01:00
MOV ( W0 , DOWNCOUNTREG ) ;
MOV ( X1 , MEMBASEREG ) ;
MOV ( X2 , JITBASEREG ) ;
2015-10-08 14:43:16 +02:00
QuickCallFunction ( SCRATCH1_64 , ( void * ) & ShowPC ) ;
2015-03-07 17:58:15 +01:00
}
LDR ( INDEX_UNSIGNED , SCRATCH1 , CTXREG , offsetof ( MIPSState , pc ) ) ;
2019-04-15 12:07:57 +02:00
# ifdef MASKED_PSP_MEMORY
ANDI2R ( SCRATCH1 , SCRATCH1 , 0x3FFFFFFF ) ;
# endif
2021-02-09 01:30:31 +01:00
dispatcherFetch = GetCodePtr ( ) ;
2015-03-07 17:58:15 +01:00
LDR ( SCRATCH1 , MEMBASEREG , SCRATCH1_64 ) ;
2015-10-08 23:11:57 +02:00
LSR ( SCRATCH2 , SCRATCH1 , 24 ) ; // or UBFX(SCRATCH2, SCRATCH1, 24, 8)
2015-03-15 23:38:21 +01:00
ANDI2R ( SCRATCH1 , SCRATCH1 , 0x00FFFFFF ) ;
CMP ( SCRATCH2 , MIPS_EMUHACK_OPCODE > > 24 ) ;
2015-03-07 17:58:15 +01:00
FixupBranch skipJump = B ( CC_NEQ ) ;
2015-03-08 00:56:15 +01:00
ADD ( SCRATCH1_64 , JITBASEREG , SCRATCH1_64 ) ;
2015-03-07 17:58:15 +01:00
BR ( SCRATCH1_64 ) ;
SetJumpTarget ( skipJump ) ;
2015-03-15 23:38:21 +01:00
2015-07-07 01:12:42 +02:00
// 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.
2015-10-08 14:43:16 +02:00
// Also, rounding mode gotta be irrelevant here..
2015-07-05 20:50:03 +02:00
SaveStaticRegisters ( ) ;
2015-03-07 17:58:15 +01:00
RestoreRoundingMode ( true ) ;
QuickCallFunction ( SCRATCH1_64 , ( void * ) & MIPSComp : : JitAt ) ;
ApplyRoundingMode ( true ) ;
2015-07-05 20:50:03 +02:00
LoadStaticRegisters ( ) ;
2015-03-07 17:58:15 +01:00
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 ) ;
2019-02-12 10:58:20 +01:00
const uint8_t * quitLoop = GetCodePtr ( ) ;
2015-03-07 17:58:15 +01:00
SetJumpTarget ( badCoreState ) ;
2015-07-05 20:50:03 +02:00
SaveStaticRegisters ( ) ;
2015-03-07 17:58:15 +01:00
RestoreRoundingMode ( true ) ;
2017-01-26 19:34:21 +01:00
fp . ABI_PopRegisters ( regs_to_save , regs_to_save_fp ) ;
2015-03-07 17:58:15 +01:00
2015-03-15 23:38:21 +01:00
RET ( ) ;
2015-06-28 12:40:29 -07:00
2019-02-12 10:58:20 +01:00
crashHandler = GetCodePtr ( ) ;
MOVP2R ( SCRATCH1_64 , & coreState ) ;
2020-07-13 09:17:42 +02:00
MOVI2R ( SCRATCH2 , CORE_RUNTIME_ERROR ) ;
2019-02-12 10:58:20 +01:00
STR ( INDEX_UNSIGNED , SCRATCH2 , SCRATCH1_64 , 0 ) ;
B ( quitLoop ) ;
2015-06-28 12:40:29 -07:00
// Generate some integer conversion funcs.
2015-10-10 14:32:34 +02:00
// MIPS order!
static const RoundingMode roundModes [ 8 ] = { ROUND_N , ROUND_Z , ROUND_P , ROUND_M , ROUND_N , ROUND_Z , ROUND_P , ROUND_M } ;
2015-06-28 12:40:29 -07:00
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 ( ) ;
}
2015-07-05 10:03:04 +02:00
2015-07-05 20:50:03 +02:00
// Leave this at the end, add more stuff above.
2015-07-06 21:46:00 +02:00
if ( enableDisasm ) {
2023-07-25 20:30:05 -07:00
std : : vector < std : : string > lines = DisassembleArm64 ( start , ( int ) ( GetCodePtr ( ) - start ) ) ;
2015-07-05 20:50:03 +02:00
for ( auto s : lines ) {
INFO_LOG ( JIT , " %s " , s . c_str ( ) ) ;
}
}
2016-08-28 14:52:08 +02:00
// Let's spare the pre-generated code from unprotect-reprotect.
AlignCodePage ( ) ;
2017-05-26 15:39:27 +02:00
jitStartOffset = ( int ) ( GetCodePtr ( ) - start ) ;
// Don't forget to zap the instruction cache! This must stay at the end of this function.
FlushIcache ( ) ;
2016-08-28 14:52:08 +02:00
EndWrite ( ) ;
2015-03-05 23:31:03 +01:00
}
} // namespace MIPSComp
2016-10-12 17:32:52 +02:00
# endif // PPSSPP_ARCH(ARM64)