2012-11-01 16:19:01 +01:00
// Copyright (c) 2012- PPSSPP Project / 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
2012-11-04 23:01:49 +01:00
// the Free Software Foundation, version 2.0 or later versions.
2012-11-01 16:19:01 +01:00
// 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/.
2021-03-02 20:42:55 -08:00
# include "ppsspp_config.h"
2014-02-07 23:04:44 +01:00
# include <cstddef>
2014-07-05 16:25:16 -07:00
# include <algorithm>
2014-02-07 23:04:44 +01:00
2021-12-19 10:40:33 -08:00
# include "ext/xxhash.h"
2022-01-30 15:49:02 -08:00
# include "Common/CommonTypes.h"
2021-12-19 10:40:33 -08:00
# include "Common/Profiler/Profiler.h"
2012-11-01 16:19:01 +01:00
# ifdef _WIN32
2013-07-28 20:43:25 -07:00
# include "Common/CommonWindows.h"
2012-11-01 16:19:01 +01:00
# endif
2013-04-27 19:35:42 +02:00
# include "Core/Core.h"
# include "Core/MemMap.h"
# include "Core/CoreTiming.h"
2013-09-01 00:19:41 -07:00
# include "Core/Reporting.h"
2023-09-22 10:58:43 +02:00
# include "Core/Config.h"
2012-11-01 16:19:01 +01:00
2013-04-27 19:35:42 +02:00
# include "Core/MIPS/MIPS.h"
# include "Core/MIPS/MIPSTables.h"
# include "Core/MIPS/MIPSAnalyst.h"
2012-11-01 16:19:01 +01:00
2013-04-27 19:35:42 +02:00
# include "Core/MIPS/JitCommon/JitBlockCache.h"
# include "Core/MIPS/JitCommon/JitCommon.h"
2013-04-26 23:58:20 +02:00
2012-11-01 16:19:01 +01:00
// #include "JitBase.h"
2021-03-02 20:42:55 -08:00
// Enable define below to enable oprofile integration. For this to work,
// it requires at least oprofile version 0.9.4, and changing the build
// system to link the Dolphin executable against libopagent. Since the
// dependency is a little inconvenient and this is possibly a slight
// performance hit, it's not enabled by default, but it's useful for
// locating performance issues.
2012-11-01 16:19:01 +01:00
# if defined USE_OPROFILE && USE_OPROFILE
# include <opagent.h>
op_agent_t agent ;
# endif
# if defined USE_VTUNE
# include <jitprofiling.h>
# pragma comment(lib, "libittnotify.lib")
# pragma comment(lib, "jitprofiling.lib")
# endif
2013-09-29 13:51:09 -07:00
const u32 INVALID_EXIT = 0xFFFFFFFF ;
2012-11-01 16:19:01 +01:00
2021-12-19 10:40:33 -08:00
static uint64_t HashJitBlock ( const JitBlock & b ) {
PROFILE_THIS_SCOPE ( " jithash " ) ;
if ( JIT_USE_COMPILEDHASH ) {
// Includes the emuhack (or emuhacks) in memory.
2023-01-01 21:12:04 +01:00
if ( Memory : : IsValidRange ( b . originalAddress , b . originalSize * 4 ) ) {
return XXH3_64bits ( Memory : : GetPointerUnchecked ( b . originalAddress ) , b . originalSize * 4 ) ;
} else {
// Hm, this would be bad.
return 0 ;
}
2021-12-19 10:40:33 -08:00
}
return 0 ;
}
2021-02-25 20:20:36 -08:00
JitBlockCache : : JitBlockCache ( MIPSState * mipsState , CodeBlockCommon * codeBlock ) :
2023-05-02 21:52:43 +02:00
codeBlock_ ( codeBlock ) {
2013-04-27 00:22:18 +02:00
}
JitBlockCache : : ~ JitBlockCache ( ) {
Shutdown ( ) ;
}
2023-06-04 10:09:20 +02:00
bool JitBlock : : ContainsAddress ( u32 em_address ) const {
2013-01-18 10:43:40 -08:00
// WARNING - THIS DOES NOT WORK WITH JIT INLINING ENABLED.
2013-04-27 00:22:18 +02:00
// However, that doesn't exist yet so meh.
2012-11-01 16:19:01 +01:00
return ( em_address > = originalAddress & & em_address < originalAddress + 4 * originalSize ) ;
}
2013-12-19 13:34:06 +01:00
bool JitBlockCache : : IsFull ( ) const {
2023-05-02 21:52:43 +02:00
// -10 to safely leave space for some proxy blocks, which we don't check before we allocate (not ideal, but should work).
return num_blocks_ > = MAX_NUM_BLOCKS - 10 ;
2012-11-01 16:19:01 +01:00
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : Init ( ) {
2012-11-01 16:19:01 +01:00
# if defined USE_OPROFILE && USE_OPROFILE
agent = op_open_agent ( ) ;
# endif
2013-12-19 13:34:06 +01:00
blocks_ = new JitBlock [ MAX_NUM_BLOCKS ] ;
2012-11-01 16:19:01 +01:00
Clear ( ) ;
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : Shutdown ( ) {
2016-02-10 15:43:36 +01:00
Clear ( ) ; // Make sure proxy block links are deleted
2013-12-19 13:34:06 +01:00
delete [ ] blocks_ ;
blocks_ = 0 ;
num_blocks_ = 0 ;
2012-11-01 16:19:01 +01:00
# if defined USE_OPROFILE && USE_OPROFILE
op_close_agent ( agent ) ;
# endif
# ifdef USE_VTUNE
iJIT_NotifyEvent ( iJVM_EVENT_TYPE_SHUTDOWN , NULL ) ;
# endif
}
// This clears the JIT cache. It's called from JitCache.cpp when the JIT cache
// is full and when saving and loading states.
2013-12-19 13:34:06 +01:00
void JitBlockCache : : Clear ( ) {
2014-10-12 18:57:56 -07:00
block_map_ . clear ( ) ;
proxyBlockMap_ . clear ( ) ;
2013-12-19 13:34:06 +01:00
for ( int i = 0 ; i < num_blocks_ ; i + + )
2017-12-28 11:12:06 -08:00
DestroyBlock ( i , DestroyType : : CLEAR ) ;
2013-12-19 13:34:06 +01:00
links_to_ . clear ( ) ;
num_blocks_ = 0 ;
2014-07-05 16:25:16 -07:00
blockMemRanges_ [ JITBLOCK_RANGE_SCRATCH ] = std : : make_pair ( 0xFFFFFFFF , 0x00000000 ) ;
blockMemRanges_ [ JITBLOCK_RANGE_RAMBOTTOM ] = std : : make_pair ( 0xFFFFFFFF , 0x00000000 ) ;
blockMemRanges_ [ JITBLOCK_RANGE_RAMTOP ] = std : : make_pair ( 0xFFFFFFFF , 0x00000000 ) ;
2012-11-01 16:19:01 +01:00
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : Reset ( ) {
2012-11-01 16:19:01 +01:00
Shutdown ( ) ;
Init ( ) ;
}
2013-12-19 13:34:06 +01:00
JitBlock * JitBlockCache : : GetBlock ( int no ) {
return & blocks_ [ no ] ;
2012-11-01 16:19:01 +01:00
}
2018-01-04 12:22:49 +01:00
const JitBlock * JitBlockCache : : GetBlock ( int no ) const {
return & blocks_ [ no ] ;
}
2013-12-19 13:34:06 +01:00
int JitBlockCache : : AllocateBlock ( u32 startAddress ) {
JitBlock & b = blocks_ [ num_blocks_ ] ;
2014-03-29 22:21:38 +01:00
b . proxyFor = 0 ;
2013-12-19 13:31:13 +01:00
// If there's an existing pure proxy block at the address, we need to ditch it and create a new one,
// taking over the proxied blocks.
int num = GetBlockNumberFromStartAddress ( startAddress , false ) ;
if ( num > = 0 ) {
2013-12-19 13:34:06 +01:00
if ( blocks_ [ num ] . IsPureProxy ( ) ) {
2014-10-12 17:13:04 -07:00
RemoveBlockMap ( num ) ;
2013-12-19 13:34:06 +01:00
blocks_ [ num ] . invalid = true ;
2014-03-29 22:21:38 +01:00
b . proxyFor = new std : : vector < u32 > ( ) ;
* b . proxyFor = * blocks_ [ num ] . proxyFor ;
blocks_ [ num ] . proxyFor - > clear ( ) ;
delete blocks_ [ num ] . proxyFor ;
blocks_ [ num ] . proxyFor = 0 ;
2013-12-19 13:31:13 +01:00
}
}
2012-11-01 16:19:01 +01:00
b . invalid = false ;
2013-12-19 13:31:13 +01:00
b . originalAddress = startAddress ;
2013-12-19 13:34:06 +01:00
for ( int i = 0 ; i < MAX_JIT_BLOCK_EXITS ; + + i ) {
2013-08-15 23:23:39 -07:00
b . exitAddress [ i ] = INVALID_EXIT ;
b . exitPtrs [ i ] = 0 ;
b . linkStatus [ i ] = false ;
}
2013-12-19 13:34:06 +01:00
b . blockNum = num_blocks_ ;
num_blocks_ + + ; //commit the current block
return num_blocks_ - 1 ;
2013-12-19 00:39:49 +01:00
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : ProxyBlock ( u32 rootAddress , u32 startAddress , u32 size , const u8 * codePtr ) {
2013-12-19 13:31:13 +01:00
// If there's an existing block at the startAddress, add rootAddress as a proxy root of that block
// instead of creating a new block.
int num = GetBlockNumberFromStartAddress ( startAddress , false ) ;
if ( num ! = - 1 ) {
2014-10-12 17:34:59 -07:00
DEBUG_LOG ( HLE , " Adding proxy root %08x to block at %08x " , rootAddress , startAddress ) ;
2014-03-29 22:21:38 +01:00
if ( ! blocks_ [ num ] . proxyFor ) {
blocks_ [ num ] . proxyFor = new std : : vector < u32 > ( ) ;
}
blocks_ [ num ] . proxyFor - > push_back ( rootAddress ) ;
2013-12-19 13:31:13 +01:00
}
2013-12-19 11:45:39 +01:00
2013-12-19 13:34:06 +01:00
JitBlock & b = blocks_ [ num_blocks_ ] ;
2013-12-19 00:39:49 +01:00
b . invalid = false ;
b . originalAddress = startAddress ;
b . originalSize = size ;
for ( int i = 0 ; i < MAX_JIT_BLOCK_EXITS ; + + i ) {
b . exitAddress [ i ] = INVALID_EXIT ;
b . exitPtrs [ i ] = 0 ;
b . linkStatus [ i ] = false ;
}
b . exitAddress [ 0 ] = rootAddress ;
2013-12-19 13:34:06 +01:00
b . blockNum = num_blocks_ ;
2014-04-20 21:44:10 -07:00
b . proxyFor = new std : : vector < u32 > ( ) ;
2013-12-19 13:34:06 +01:00
b . SetPureProxy ( ) ; // flag as pure proxy block.
2013-12-19 00:39:49 +01:00
// Make binary searches and stuff work ok
b . normalEntry = codePtr ;
2020-05-16 22:12:35 -07:00
b . checkedEntry = codePtr ;
2022-09-30 12:35:28 +03:00
proxyBlockMap_ . emplace ( startAddress , num_blocks_ ) ;
2014-10-12 17:13:04 -07:00
AddBlockMap ( num_blocks_ ) ;
2013-12-19 13:34:06 +01:00
num_blocks_ + + ; //commit the current block
2012-11-01 16:19:01 +01:00
}
2014-10-12 17:13:04 -07:00
void JitBlockCache : : AddBlockMap ( int block_num ) {
const JitBlock & b = blocks_ [ block_num ] ;
// Convert the logical address to a physical address for the block map
// Yeah, this'll work fine for PSP too I think.
u32 pAddr = b . originalAddress & 0x1FFFFFFF ;
block_map_ [ std : : make_pair ( pAddr + 4 * b . originalSize , pAddr ) ] = block_num ;
}
void JitBlockCache : : RemoveBlockMap ( int block_num ) {
const JitBlock & b = blocks_ [ block_num ] ;
2014-10-12 18:57:56 -07:00
if ( b . invalid ) {
return ;
}
const u32 pAddr = b . originalAddress & 0x1FFFFFFF ;
2014-10-21 11:52:19 -07:00
auto it = block_map_ . find ( std : : make_pair ( pAddr + 4 * b . originalSize , pAddr ) ) ;
2014-11-03 08:34:34 -08:00
if ( it ! = block_map_ . end ( ) & & it - > second = = ( u32 ) block_num ) {
2014-10-12 17:47:07 -07:00
block_map_ . erase ( it ) ;
} else {
// It wasn't in there, or it has the wrong key. Let's search...
for ( auto it = block_map_ . begin ( ) ; it ! = block_map_ . end ( ) ; + + it ) {
2014-11-03 08:34:34 -08:00
if ( it - > second = = ( u32 ) block_num ) {
2014-10-12 17:47:07 -07:00
block_map_ . erase ( it ) ;
break ;
}
}
}
2014-10-12 17:13:04 -07:00
}
2014-07-05 16:25:16 -07:00
static void ExpandRange ( std : : pair < u32 , u32 > & range , u32 newStart , u32 newEnd ) {
range . first = std : : min ( range . first , newStart ) ;
range . second = std : : max ( range . second , newEnd ) ;
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : FinalizeBlock ( int block_num , bool block_link ) {
JitBlock & b = blocks_ [ block_num ] ;
2012-11-01 16:19:01 +01:00
2023-09-22 10:58:43 +02:00
_assert_msg_ ( Memory : : IsValidAddress ( b . originalAddress ) , " FinalizeBlock: Bad originalAddress %08x in block %d. core=%d " , b . originalAddress , block_num , g_Config . iCpuCore ) ;
2023-09-11 12:20:48 +02:00
2012-11-01 16:19:01 +01:00
b . originalFirstOpcode = Memory : : Read_Opcode_JIT ( b . originalAddress ) ;
2013-08-24 14:43:49 -07:00
MIPSOpcode opcode = GetEmuHackOpForBlock ( block_num ) ;
2012-11-01 16:19:01 +01:00
Memory : : Write_Opcode_JIT ( b . originalAddress , opcode ) ;
2013-12-19 13:31:13 +01:00
2021-12-19 10:40:33 -08:00
// Note that this hashes the emuhack too, which is intentional.
b . compiledHash = HashJitBlock ( b ) ;
2014-10-12 17:13:04 -07:00
AddBlockMap ( block_num ) ;
2012-11-01 16:19:01 +01:00
2013-12-19 13:34:06 +01:00
if ( block_link ) {
for ( int i = 0 ; i < MAX_JIT_BLOCK_EXITS ; i + + ) {
2014-07-05 16:25:16 -07:00
if ( b . exitAddress [ i ] ! = INVALID_EXIT ) {
2022-09-30 12:35:28 +03:00
links_to_ . emplace ( b . exitAddress [ i ] , block_num ) ;
2014-07-05 16:25:16 -07:00
}
2012-11-01 16:19:01 +01:00
}
2013-12-19 13:34:06 +01:00
2012-11-01 16:19:01 +01:00
LinkBlock ( block_num ) ;
LinkBlockExits ( block_num ) ;
}
2015-02-28 15:09:41 -08:00
const u32 blockEnd = b . originalAddress + b . originalSize * 4 - 4 ;
2014-07-05 16:25:16 -07:00
if ( Memory : : IsScratchpadAddress ( b . originalAddress ) ) {
2015-02-28 15:09:41 -08:00
ExpandRange ( blockMemRanges_ [ JITBLOCK_RANGE_SCRATCH ] , b . originalAddress , blockEnd ) ;
2014-07-05 16:25:16 -07:00
}
const u32 halfUserMemory = ( PSP_GetUserMemoryEnd ( ) - PSP_GetUserMemoryBase ( ) ) / 2 ;
if ( b . originalAddress < PSP_GetUserMemoryBase ( ) + halfUserMemory ) {
2015-02-28 15:09:41 -08:00
ExpandRange ( blockMemRanges_ [ JITBLOCK_RANGE_RAMBOTTOM ] , b . originalAddress , blockEnd ) ;
2014-07-05 16:25:16 -07:00
}
2015-02-28 15:09:41 -08:00
if ( blockEnd > PSP_GetUserMemoryBase ( ) + halfUserMemory ) {
ExpandRange ( blockMemRanges_ [ JITBLOCK_RANGE_RAMTOP ] , b . originalAddress , blockEnd ) ;
2014-07-05 16:25:16 -07:00
}
2012-11-01 16:19:01 +01:00
# if defined USE_OPROFILE && USE_OPROFILE
char buf [ 100 ] ;
2023-04-28 21:04:05 +02:00
snprintf ( buf , sizeof ( buf ) , " EmuCode%x " , b . originalAddress ) ;
2013-12-19 13:34:06 +01:00
const u8 * blockStart = blocks_ [ block_num ] . checkedEntry ;
2013-12-01 02:34:37 -08:00
op_write_native_code ( agent , buf , ( uint64_t ) blockStart , blockStart , b . normalEntry + b . codeSize - b . checkedEntry ) ;
2012-11-01 16:19:01 +01:00
# endif
# ifdef USE_VTUNE
2023-04-28 21:04:05 +02:00
snprintf ( b . blockName , sizeof ( b . blockName ) , " EmuCode_0x%08x " , b . originalAddress ) ;
2012-11-01 16:19:01 +01:00
iJIT_Method_Load jmethod = { 0 } ;
jmethod . method_id = iJIT_GetNewMethodID ( ) ;
jmethod . class_file_name = " " ;
jmethod . source_file_name = __FILE__ ;
2013-12-19 13:34:06 +01:00
jmethod . method_load_address = ( void * ) blocks_ [ block_num ] . checkedEntry ;
2013-12-01 02:34:37 -08:00
jmethod . method_size = b . normalEntry + b . codeSize - b . checkedEntry ;
2012-11-01 16:19:01 +01:00
jmethod . line_number_size = 0 ;
jmethod . method_name = b . blockName ;
iJIT_NotifyEvent ( iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED , ( void * ) & jmethod ) ;
# endif
}
2014-07-05 16:25:16 -07:00
bool JitBlockCache : : RangeMayHaveEmuHacks ( u32 start , u32 end ) const {
for ( int i = 0 ; i < JITBLOCK_RANGE_COUNT ; + + i ) {
if ( end > = blockMemRanges_ [ i ] . first & & start < = blockMemRanges_ [ i ] . second ) {
return true ;
}
}
return false ;
}
2022-12-10 21:02:44 -08:00
static int binary_search ( const JitBlock blocks_ [ ] , const u8 * baseoff , int imin , int imax ) {
2013-12-19 00:39:49 +01:00
while ( imin < imax ) {
2013-04-27 23:40:17 +02:00
int imid = ( imin + imax ) / 2 ;
2013-12-19 13:34:06 +01:00
if ( blocks_ [ imid ] . normalEntry < baseoff )
2013-04-27 23:40:17 +02:00
imin = imid + 1 ;
else
imax = imid ;
}
2013-12-19 13:34:06 +01:00
if ( ( imax = = imin ) & & ( blocks_ [ imin ] . normalEntry = = baseoff ) )
2013-04-27 23:40:17 +02:00
return imin ;
else
return - 1 ;
}
2013-12-10 13:06:57 +01:00
int JitBlockCache : : GetBlockNumberFromEmuHackOp ( MIPSOpcode inst , bool ignoreBad ) const {
2013-12-19 13:34:06 +01:00
if ( ! num_blocks_ | | ! MIPS_IS_EMUHACK ( inst ) ) // definitely not a JIT block
2013-04-27 00:22:18 +02:00
return - 1 ;
int off = ( inst & MIPS_EMUHACK_VALUE_MASK ) ;
const u8 * baseoff = codeBlock_ - > GetBasePtr ( ) + off ;
2013-12-01 14:12:16 +01:00
if ( baseoff < codeBlock_ - > GetBasePtr ( ) | | baseoff > = codeBlock_ - > GetCodePtr ( ) ) {
2013-12-10 13:06:57 +01:00
if ( ! ignoreBad ) {
ERROR_LOG ( JIT , " JitBlockCache: Invalid Emuhack Op %08x " , inst . encoding ) ;
}
2013-12-01 14:12:16 +01:00
return - 1 ;
}
2013-12-19 13:34:06 +01:00
int bl = binary_search ( blocks_ , baseoff , 0 , num_blocks_ - 1 ) ;
2014-05-27 20:12:35 +02:00
if ( bl > = 0 & & blocks_ [ bl ] . invalid ) {
2013-09-01 00:19:41 -07:00
return - 1 ;
2014-05-27 20:12:35 +02:00
} else {
return bl ;
}
2013-04-27 00:22:18 +02:00
}
2013-08-24 14:43:49 -07:00
MIPSOpcode JitBlockCache : : GetEmuHackOpForBlock ( int blockNum ) const {
2013-12-19 13:34:06 +01:00
int off = ( int ) ( blocks_ [ blockNum ] . normalEntry - codeBlock_ - > GetBasePtr ( ) ) ;
2013-08-24 14:43:49 -07:00
return MIPSOpcode ( MIPS_EMUHACK_OPCODE | off ) ;
2012-11-01 16:19:01 +01:00
}
2018-01-04 12:22:49 +01:00
int JitBlockCache : : GetBlockNumberFromStartAddress ( u32 addr , bool realBlocksOnly ) const {
2014-08-17 12:19:04 +02:00
if ( ! blocks_ | | ! Memory : : IsValidAddress ( addr ) )
2013-08-15 23:23:39 -07:00
return - 1 ;
2013-12-19 00:39:49 +01:00
2013-08-24 14:43:49 -07:00
MIPSOpcode inst = MIPSOpcode ( Memory : : Read_U32 ( addr ) ) ;
2013-04-27 00:22:18 +02:00
int bl = GetBlockNumberFromEmuHackOp ( inst ) ;
2013-12-19 00:39:49 +01:00
if ( bl < 0 ) {
2013-12-19 13:31:13 +01:00
if ( ! realBlocksOnly ) {
2014-10-12 17:34:59 -07:00
// Wasn't an emu hack op, look through proxyBlockMap_.
auto range = proxyBlockMap_ . equal_range ( addr ) ;
for ( auto it = range . first ; it ! = range . second ; + + it ) {
const int blockIndex = it - > second ;
2014-03-29 22:21:38 +01:00
if ( blocks_ [ blockIndex ] . originalAddress = = addr & & ! blocks_ [ blockIndex ] . proxyFor & & ! blocks_ [ blockIndex ] . invalid )
2013-12-19 13:31:13 +01:00
return blockIndex ;
}
2013-12-19 00:39:49 +01:00
}
2012-11-01 16:19:01 +01:00
return - 1 ;
2013-12-19 00:39:49 +01:00
}
2013-12-19 13:34:06 +01:00
if ( blocks_ [ bl ] . originalAddress ! = addr )
2013-12-19 13:31:13 +01:00
return - 1 ;
2013-12-19 00:39:49 +01:00
2012-11-01 16:19:01 +01:00
return bl ;
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : GetBlockNumbersFromAddress ( u32 em_address , std : : vector < int > * block_numbers ) {
for ( int i = 0 ; i < num_blocks_ ; i + + )
if ( blocks_ [ i ] . ContainsAddress ( em_address ) )
2012-11-01 16:19:01 +01:00
block_numbers - > push_back ( i ) ;
}
2022-12-20 20:26:41 -08:00
int JitBlockCache : : GetBlockNumberFromAddress ( u32 em_address ) {
for ( int i = 0 ; i < num_blocks_ ; i + + ) {
if ( blocks_ [ i ] . ContainsAddress ( em_address ) )
return i ;
}
return - 1 ;
}
2013-11-30 18:21:47 -08:00
u32 JitBlockCache : : GetAddressFromBlockPtr ( const u8 * ptr ) const {
if ( ! codeBlock_ - > IsInSpace ( ptr ) )
return ( u32 ) - 1 ;
2013-12-19 13:34:06 +01:00
for ( int i = 0 ; i < num_blocks_ ; + + i ) {
const auto & b = blocks_ [ i ] ;
2013-12-01 02:34:37 -08:00
if ( ! b . invalid & & ptr > = b . checkedEntry & & ptr < b . normalEntry + b . codeSize ) {
2013-11-30 18:21:47 -08:00
return b . originalAddress ;
}
}
2013-12-19 00:39:49 +01:00
// It's in jit somewhere, but we must have deleted it.
2013-11-30 18:21:47 -08:00
return 0 ;
}
2013-12-19 13:34:06 +01:00
MIPSOpcode JitBlockCache : : GetOriginalFirstOp ( int block_num ) {
if ( block_num > = num_blocks_ | | block_num < 0 ) {
2013-08-24 14:43:49 -07:00
return MIPSOpcode ( block_num ) ;
2012-11-01 16:19:01 +01:00
}
2013-12-19 13:34:06 +01:00
return blocks_ [ block_num ] . originalFirstOpcode ;
2012-11-01 16:19:01 +01:00
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : LinkBlockExits ( int i ) {
JitBlock & b = blocks_ [ i ] ;
2013-04-27 19:35:42 +02:00
if ( b . invalid ) {
2012-11-01 16:19:01 +01:00
// This block is dead. Don't relink it.
return ;
}
2016-01-30 20:44:57 -08:00
if ( b . IsPureProxy ( ) ) {
// Pure proxies can't link, since they don't have code.
return ;
}
2013-12-19 00:39:49 +01:00
2013-08-15 23:23:39 -07:00
for ( int e = 0 ; e < MAX_JIT_BLOCK_EXITS ; e + + ) {
2013-04-27 19:35:42 +02:00
if ( b . exitAddress [ e ] ! = INVALID_EXIT & & ! b . linkStatus [ e ] ) {
2016-01-30 20:44:57 -08:00
int destinationBlock = GetBlockNumberFromStartAddress ( b . exitAddress [ e ] , true ) ;
if ( destinationBlock = = - 1 ) {
continue ;
}
JitBlock & eb = blocks_ [ destinationBlock ] ;
// Make sure the destination is not invalid.
if ( ! eb . invalid ) {
2016-05-01 10:25:01 +02:00
MIPSComp : : jit - > LinkBlock ( b . exitPtrs [ e ] , eb . checkedEntry ) ;
2012-11-01 16:19:01 +01:00
b . linkStatus [ e ] = true ;
}
}
}
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : LinkBlock ( int i ) {
2012-11-01 16:19:01 +01:00
LinkBlockExits ( i ) ;
2013-12-19 13:34:06 +01:00
JitBlock & b = blocks_ [ i ] ;
2012-11-01 16:19:01 +01:00
// equal_range(b) returns pair<iterator,iterator> representing the range
// of element with key b
2014-11-03 08:24:53 -08:00
auto ppp = links_to_ . equal_range ( b . originalAddress ) ;
2012-11-01 16:19:01 +01:00
if ( ppp . first = = ppp . second )
return ;
2014-11-03 08:24:53 -08:00
for ( auto iter = ppp . first ; iter ! = ppp . second ; + + iter ) {
2020-07-19 20:32:15 +02:00
// INFO_LOG(JIT, "Linking block %i to block %i", iter->second, i);
2013-02-04 08:49:58 -05:00
LinkBlockExits ( iter - > second ) ;
2012-11-01 16:19:01 +01:00
}
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : UnlinkBlock ( int i ) {
JitBlock & b = blocks_ [ i ] ;
2014-11-03 08:24:53 -08:00
auto ppp = links_to_ . equal_range ( b . originalAddress ) ;
2012-11-01 16:19:01 +01:00
if ( ppp . first = = ppp . second )
return ;
2014-11-03 08:24:53 -08:00
for ( auto iter = ppp . first ; iter ! = ppp . second ; + + iter ) {
2013-12-19 13:34:06 +01:00
JitBlock & sourceBlock = blocks_ [ iter - > second ] ;
for ( int e = 0 ; e < MAX_JIT_BLOCK_EXITS ; e + + ) {
2012-11-01 16:19:01 +01:00
if ( sourceBlock . exitAddress [ e ] = = b . originalAddress )
sourceBlock . linkStatus [ e ] = false ;
}
}
}
2013-12-19 13:34:06 +01:00
std : : vector < u32 > JitBlockCache : : SaveAndClearEmuHackOps ( ) {
2013-09-29 13:51:09 -07:00
std : : vector < u32 > result ;
2013-12-19 13:34:06 +01:00
result . resize ( num_blocks_ ) ;
2013-09-29 13:51:09 -07:00
2013-12-19 13:34:06 +01:00
for ( int block_num = 0 ; block_num < num_blocks_ ; + + block_num ) {
JitBlock & b = blocks_ [ block_num ] ;
2013-09-29 13:51:09 -07:00
if ( b . invalid )
continue ;
const u32 emuhack = GetEmuHackOpForBlock ( block_num ) . encoding ;
2015-03-17 23:50:20 -07:00
if ( Memory : : ReadUnchecked_U32 ( b . originalAddress ) = = emuhack )
{
result [ block_num ] = emuhack ;
2013-09-29 13:51:09 -07:00
Memory : : Write_Opcode_JIT ( b . originalAddress , b . originalFirstOpcode ) ;
2015-03-17 23:50:20 -07:00
}
else
result [ block_num ] = 0 ;
2013-09-29 13:51:09 -07:00
}
return result ;
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : RestoreSavedEmuHackOps ( std : : vector < u32 > saved ) {
if ( num_blocks_ ! = ( int ) saved . size ( ) ) {
2013-09-29 13:51:09 -07:00
ERROR_LOG ( JIT , " RestoreSavedEmuHackOps: Wrong saved block size. " ) ;
return ;
}
2013-12-19 13:34:06 +01:00
for ( int block_num = 0 ; block_num < num_blocks_ ; + + block_num ) {
const JitBlock & b = blocks_ [ block_num ] ;
2015-03-17 23:50:20 -07:00
if ( b . invalid | | saved [ block_num ] = = 0 )
2013-09-29 13:51:09 -07:00
continue ;
// Only if we restored it, write it back.
if ( Memory : : ReadUnchecked_U32 ( b . originalAddress ) = = b . originalFirstOpcode . encoding )
Memory : : Write_Opcode_JIT ( b . originalAddress , MIPSOpcode ( saved [ block_num ] ) ) ;
}
}
2017-12-28 11:12:06 -08:00
void JitBlockCache : : DestroyBlock ( int block_num , DestroyType type ) {
2013-12-19 13:34:06 +01:00
if ( block_num < 0 | | block_num > = num_blocks_ ) {
2013-09-01 00:19:41 -07:00
ERROR_LOG_REPORT ( JIT , " DestroyBlock: Invalid block number %d " , block_num ) ;
2012-11-01 16:19:01 +01:00
return ;
}
2013-12-19 13:34:06 +01:00
JitBlock * b = & blocks_ [ block_num ] ;
2014-10-12 17:13:04 -07:00
// No point it being in there anymore.
RemoveBlockMap ( block_num ) ;
2013-12-19 00:39:49 +01:00
2013-12-19 13:31:13 +01:00
// Pure proxy blocks always point directly to a real block, there should be no chains of
// proxy-only blocks pointing to proxy-only blocks.
2013-12-19 00:39:49 +01:00
// Follow a block proxy chain.
// Destroy the block that transitively has this as a proxy. Likely the root block once inlined
// this block or its 'parent', so now that this block has changed, the root block must be destroyed.
2014-03-29 22:21:38 +01:00
if ( b - > proxyFor ) {
for ( size_t i = 0 ; i < b - > proxyFor - > size ( ) ; i + + ) {
int proxied_blocknum = GetBlockNumberFromStartAddress ( ( * b - > proxyFor ) [ i ] , false ) ;
2014-05-04 01:25:19 -07:00
// If it was already cleared, we don't know which to destroy.
if ( proxied_blocknum ! = - 1 ) {
2017-12-28 11:12:06 -08:00
DestroyBlock ( proxied_blocknum , type ) ;
2014-05-04 01:25:19 -07:00
}
2014-03-29 22:21:38 +01:00
}
b - > proxyFor - > clear ( ) ;
delete b - > proxyFor ;
b - > proxyFor = 0 ;
2013-12-19 00:39:49 +01:00
}
2014-10-12 17:34:59 -07:00
auto range = proxyBlockMap_ . equal_range ( b - > originalAddress ) ;
for ( auto it = range . first ; it ! = range . second ; + + it ) {
if ( it - > second = = block_num ) {
// Found it. Delete and bail.
proxyBlockMap_ . erase ( it ) ;
break ;
}
}
2013-12-19 00:39:49 +01:00
// TODO: Handle the case when there's a proxy block and a regular JIT block at the same location.
// In this case we probably "leak" the proxy block currently (no memory leak but it'll stay enabled).
if ( b - > invalid ) {
2017-12-28 11:12:06 -08:00
if ( type = = DestroyType : : INVALIDATE )
2012-11-01 16:19:01 +01:00
ERROR_LOG ( JIT , " Invalidating invalid block %d " , block_num ) ;
return ;
}
2013-12-19 13:34:06 +01:00
2013-12-19 00:39:49 +01:00
b - > invalid = true ;
2015-07-19 13:04:06 -07:00
if ( ! b - > IsPureProxy ( ) ) {
if ( Memory : : ReadUnchecked_U32 ( b - > originalAddress ) = = GetEmuHackOpForBlock ( block_num ) . encoding )
Memory : : Write_Opcode_JIT ( b - > originalAddress , b - > originalFirstOpcode ) ;
}
2012-11-01 16:19:01 +01:00
2013-12-19 00:39:49 +01:00
// It's not safe to set normalEntry to 0 here, since we use a binary search
// that looks at that later to find blocks. Marking it invalid is enough.
2012-11-01 16:19:01 +01:00
2013-12-19 00:39:49 +01:00
UnlinkBlock ( block_num ) ;
2013-04-26 23:58:20 +02:00
2015-07-19 13:04:06 -07:00
// Don't change the jit code when invalidating a pure proxy block.
if ( b - > IsPureProxy ( ) ) {
return ;
}
2017-01-26 08:43:26 +01:00
if ( b - > checkedEntry ) {
2017-12-28 11:12:06 -08:00
// We can skip this if we're clearing anyway, which cuts down on protect back and forth on WX exclusive.
if ( type ! = DestroyType : : CLEAR ) {
2020-05-16 22:12:35 -07:00
u8 * writableEntry = codeBlock_ - > GetWritablePtrFromCodePtr ( b - > checkedEntry ) ;
MIPSComp : : jit - > UnlinkBlock ( writableEntry , b - > originalAddress ) ;
2017-12-28 11:12:06 -08:00
}
2017-01-26 08:43:26 +01:00
} else {
ERROR_LOG ( JIT , " Unlinking block with no entry: %08x (%d) " , b - > originalAddress , block_num ) ;
}
2012-11-01 16:19:01 +01:00
}
2013-12-19 13:34:06 +01:00
void JitBlockCache : : InvalidateICache ( u32 address , const u32 length ) {
2012-11-01 16:19:01 +01:00
// Convert the logical address to a physical address for the block map
2014-10-12 17:13:04 -07:00
const u32 pAddr = address & 0x1FFFFFFF ;
const u32 pEnd = pAddr + length ;
2015-02-04 10:56:38 +01:00
if ( pEnd < pAddr ) {
ERROR_LOG ( JIT , " Bad InvalidateICache: %08x with len=%d " , address , length ) ;
return ;
}
2015-07-19 13:08:32 -07:00
if ( pAddr = = 0 & & pEnd > = 0x1FFFFFFF ) {
InvalidateChangedBlocks ( ) ;
return ;
}
2014-10-12 17:13:04 -07:00
// Blocks may start and end in overlapping ways, and destroying one invalidates iterators.
// So after destroying one, we start over.
2014-10-22 00:50:39 -07:00
do {
restart :
2014-10-12 17:13:04 -07:00
auto next = block_map_ . lower_bound ( std : : make_pair ( pAddr , 0 ) ) ;
2014-10-27 19:05:52 -07:00
auto last = block_map_ . upper_bound ( std : : make_pair ( pEnd + MAX_BLOCK_INSTRUCTIONS , 0 ) ) ;
2014-10-22 00:50:39 -07:00
// Note that if next is end(), last will be end() too (equal.)
2014-10-12 17:13:04 -07:00
for ( ; next ! = last ; + + next ) {
const u32 blockStart = next - > first . second ;
const u32 blockEnd = next - > first . first ;
if ( blockStart < pEnd & & blockEnd > pAddr ) {
2017-12-28 11:12:06 -08:00
DestroyBlock ( next - > second , DestroyType : : INVALIDATE ) ;
2014-10-12 17:13:04 -07:00
// Our iterator is now invalid. Break and search again.
// Most of the time there shouldn't be a bunch of matching blocks.
2014-10-22 00:50:39 -07:00
goto restart ;
2014-10-12 17:13:04 -07:00
}
}
2014-10-22 00:50:39 -07:00
// We got here - it wasn't in the map at all (or anymore.)
} while ( false ) ;
2012-11-01 16:19:01 +01:00
}
2014-10-12 16:00:58 -07:00
2015-07-19 13:08:32 -07:00
void JitBlockCache : : InvalidateChangedBlocks ( ) {
// The primary goal of this is to make sure block linking is cleared up.
for ( int block_num = 0 ; block_num < num_blocks_ ; + + block_num ) {
JitBlock & b = blocks_ [ block_num ] ;
if ( b . invalid | | b . IsPureProxy ( ) )
continue ;
2021-12-19 10:40:33 -08:00
bool changed = false ;
if ( JIT_USE_COMPILEDHASH ) {
changed = b . compiledHash ! = HashJitBlock ( b ) ;
} else {
const u32 emuhack = GetEmuHackOpForBlock ( block_num ) . encoding ;
changed = Memory : : ReadUnchecked_U32 ( b . originalAddress ) ! = emuhack ;
}
if ( changed ) {
2015-07-19 13:08:32 -07:00
DEBUG_LOG ( JIT , " Invalidating changed block at %08x " , b . originalAddress ) ;
2017-12-28 11:12:06 -08:00
DestroyBlock ( block_num , DestroyType : : INVALIDATE ) ;
2015-07-19 13:08:32 -07:00
}
}
}
2014-10-12 16:00:58 -07:00
int JitBlockCache : : GetBlockExitSize ( ) {
2020-08-29 08:45:50 -07:00
# if PPSSPP_ARCH(ARM)
2014-11-17 21:16:03 +01:00
// Will depend on the sequence found to encode the destination address.
2014-10-12 16:00:58 -07:00
return 0 ;
2020-08-29 08:45:50 -07:00
# elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
2014-10-12 16:00:58 -07:00
return 15 ;
2020-08-29 08:45:50 -07:00
# elif PPSSPP_ARCH(ARM64)
2015-03-07 23:05:31 +01:00
// Will depend on the sequence found to encode the destination address.
return 0 ;
2022-12-30 11:42:46 -08:00
# elif PPSSPP_ARCH(RISCV64)
// Will depend on the sequence found to encode the destination address.
return 0 ;
2014-12-16 21:58:38 +01:00
# else
# warning GetBlockExitSize unimplemented
2014-10-12 16:00:58 -07:00
return 0 ;
# endif
}
2014-11-24 21:57:27 +01:00
2018-01-04 12:22:49 +01:00
void JitBlockCache : : ComputeStats ( BlockCacheStats & bcStats ) const {
2014-11-24 21:57:27 +01:00
double totalBloat = 0.0 ;
double maxBloat = 0.0 ;
double minBloat = 1000000000.0 ;
for ( int i = 0 ; i < num_blocks_ ; i + + ) {
2018-01-04 12:22:49 +01:00
const JitBlock * b = GetBlock ( i ) ;
2014-11-24 21:57:27 +01:00
double codeSize = ( double ) b - > codeSize ;
if ( codeSize = = 0 )
continue ;
double origSize = ( double ) ( 4 * b - > originalSize ) ;
double bloat = codeSize / origSize ;
if ( bloat < minBloat ) {
minBloat = bloat ;
bcStats . minBloatBlock = b - > originalAddress ;
}
if ( bloat > maxBloat ) {
maxBloat = bloat ;
bcStats . maxBloatBlock = b - > originalAddress ;
}
totalBloat + = bloat ;
2022-01-30 15:15:38 -08:00
bcStats . bloatMap [ ( float ) bloat ] = b - > originalAddress ;
2014-11-24 21:57:27 +01:00
}
bcStats . numBlocks = num_blocks_ ;
2022-01-30 15:15:38 -08:00
bcStats . minBloat = ( float ) minBloat ;
bcStats . maxBloat = ( float ) maxBloat ;
bcStats . avgBloat = ( float ) ( totalBloat / ( double ) num_blocks_ ) ;
2014-11-24 21:57:27 +01:00
}
2018-01-04 12:22:49 +01:00
JitBlockDebugInfo JitBlockCache : : GetBlockDebugInfo ( int blockNum ) const {
JitBlockDebugInfo debugInfo { } ;
const JitBlock * block = GetBlock ( blockNum ) ;
debugInfo . originalAddress = block - > originalAddress ;
for ( u32 addr = block - > originalAddress ; addr < = block - > originalAddress + block - > originalSize * 4 ; addr + = 4 ) {
char temp [ 256 ] ;
2023-04-29 09:07:25 -07:00
MIPSDisAsm ( Memory : : Read_Instruction ( addr ) , addr , temp , sizeof ( temp ) , true ) ;
2018-01-04 12:22:49 +01:00
std : : string mipsDis = temp ;
debugInfo . origDisasm . push_back ( mipsDis ) ;
}
2020-08-29 08:45:50 -07:00
# if PPSSPP_ARCH(ARM)
2018-01-04 12:22:49 +01:00
debugInfo . targetDisasm = DisassembleArm2 ( block - > normalEntry , block - > codeSize ) ;
2020-08-29 08:45:50 -07:00
# elif PPSSPP_ARCH(ARM64)
2018-01-04 12:22:49 +01:00
debugInfo . targetDisasm = DisassembleArm64 ( block - > normalEntry , block - > codeSize ) ;
2020-08-29 08:45:50 -07:00
# elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
2018-01-04 12:22:49 +01:00
debugInfo . targetDisasm = DisassembleX86 ( block - > normalEntry , block - > codeSize ) ;
2023-02-12 12:10:29 -08:00
# elif PPSSPP_ARCH(RISCV64)
2022-12-30 11:36:32 -08:00
debugInfo . targetDisasm = DisassembleRV64 ( block - > normalEntry , block - > codeSize ) ;
2018-01-04 12:22:49 +01:00
# endif
return debugInfo ;
}