2016-07-07 20:42:39 +02:00
# include "stdafx.h"
2025-10-05 18:28:03 +02:00
2018-09-29 00:12:00 +02:00
# include "PPUAnalyser.h"
2025-10-04 15:46:36 +02:00
# include "cellos/sys_sync.h"
2024-12-22 19:59:48 +01:00
2016-07-07 20:42:39 +02:00
# include "PPUOpcodes.h"
2024-11-11 20:54:44 +01:00
# include "PPUThread.h"
2016-07-07 20:42:39 +02:00
# include <unordered_set>
2020-03-09 17:18:39 +01:00
# include "util/yaml.hpp"
2025-10-05 18:28:03 +02:00
# include "rx/align.hpp"
# include "rx/asm.hpp"
2016-08-19 23:14:10 +02:00
2020-02-01 05:36:53 +01:00
LOG_CHANNEL ( ppu_validator ) ;
2025-02-23 18:04:14 +01:00
extern const ppu_decoder < ppu_itype > g_ppu_itype ;
2016-07-07 20:42:39 +02:00
2025-04-05 21:50:45 +02:00
template < >
2016-08-07 21:01:27 +02:00
void fmt_class_string < ppu_attr > : : format ( std : : string & out , u64 arg )
2016-08-03 22:51:05 +02:00
{
2016-08-07 21:01:27 +02:00
format_enum ( out , arg , [ ] ( ppu_attr value )
{
2025-04-05 21:50:45 +02:00
switch ( value )
{
case ppu_attr : : known_size : return " known_size " ;
case ppu_attr : : no_return : return " no_return " ;
case ppu_attr : : no_size : return " no_size " ;
case ppu_attr : : has_mfvscr : return " has_mfvscr " ;
}
2016-08-07 21:01:27 +02:00
2025-04-05 21:50:45 +02:00
return unknown ;
} ) ;
2016-08-07 21:01:27 +02:00
}
2025-04-05 21:50:45 +02:00
template < >
2025-10-04 21:19:57 +02:00
void fmt_class_string < rx : : EnumBitSet < ppu_attr > > : : format ( std : : string & out , u64 arg )
2016-08-07 21:01:27 +02:00
{
format_bitset ( out , arg , " [ " , " , " , " ] " , & fmt_class_string < ppu_attr > : : format ) ;
2016-08-03 22:51:05 +02:00
}
2024-12-22 19:59:48 +01:00
template < >
void ppu_module < lv2_obj > : : validate ( u32 reloc )
2016-07-07 20:42:39 +02:00
{
// Load custom PRX configuration if available
2017-07-01 01:08:51 +02:00
if ( fs : : file yml { path + " .yml " } )
2016-07-07 20:42:39 +02:00
{
2020-03-09 17:18:39 +01:00
const auto [ cfg , error ] = yaml_load ( yml . to_string ( ) ) ;
if ( ! error . empty ( ) )
{
ppu_validator . error ( " Failed to load %s.yml: %s " , path , error ) ;
return ;
}
2016-07-07 20:42:39 +02:00
u32 index = 0 ;
// Validate detected functions using information provided
for ( const auto func : cfg [ " functions " ] )
{
const u32 addr = func [ " addr " ] . as < u32 > ( - 1 ) ;
const u32 size = func [ " size " ] . as < u32 > ( 0 ) ;
2020-02-19 18:03:59 +01:00
if ( addr ! = umax & & index < funcs . size ( ) )
2016-07-07 20:42:39 +02:00
{
u32 found = funcs [ index ] . addr - reloc ;
while ( addr > found & & index + 1 < funcs . size ( ) )
{
2020-02-01 05:36:53 +01:00
ppu_validator . warning ( " %s.yml : unexpected function at 0x%x (0x%x, 0x%x) " , path , found , addr , size ) ;
2016-07-07 20:42:39 +02:00
index + + ;
found = funcs [ index ] . addr - reloc ;
}
if ( addr < found )
{
2020-02-01 05:36:53 +01:00
ppu_validator . error ( " %s.yml : function not found (0x%x, 0x%x) " , path , addr , size ) ;
2016-07-07 20:42:39 +02:00
continue ;
}
2017-02-12 19:12:08 +01:00
if ( size & & size ! = funcs [ index ] . size )
2016-07-07 20:42:39 +02:00
{
2023-07-22 11:03:45 +02:00
if ( size + 4 ! = funcs [ index ] . size | | get_ref < u32 > ( addr + size ) ! = ppu_instructions : : NOP ( ) )
2017-02-12 19:12:08 +01:00
{
2020-02-01 05:36:53 +01:00
ppu_validator . error ( " %s.yml : function size mismatch at 0x%x(size=0x%x) (0x%x, 0x%x) " , path , found , funcs [ index ] . size , addr , size ) ;
2017-02-12 19:12:08 +01:00
}
2016-07-07 20:42:39 +02:00
}
index + + ;
}
else
{
2020-02-01 05:36:53 +01:00
ppu_validator . error ( " %s.yml : function not found at the end (0x%x, 0x%x) " , path , addr , size ) ;
2016-07-07 20:42:39 +02:00
break ;
}
}
if ( ! index )
{
return ; // ???
}
while ( index < funcs . size ( ) )
{
if ( funcs [ index ] . size )
{
2020-02-01 05:36:53 +01:00
ppu_validator . error ( " %s.yml : function not covered at 0x%x (size=0x%x) " , path , funcs [ index ] . addr , funcs [ index ] . size ) ;
2016-07-07 20:42:39 +02:00
}
index + + ;
}
2020-02-01 05:36:53 +01:00
ppu_validator . success ( " %s.yml : validation completed " , path ) ;
2016-07-07 20:42:39 +02:00
}
}
2023-06-25 14:53:42 +02:00
static u32 ppu_test ( const be_t < u32 > * ptr , const void * fend , ppu_pattern_array pat )
2016-07-09 00:36:42 +02:00
{
2023-06-25 14:53:42 +02:00
const be_t < u32 > * cur = ptr ;
2016-07-09 00:36:42 +02:00
for ( auto & p : pat )
{
if ( cur > = fend )
{
return 0 ;
}
if ( * cur = = ppu_instructions : : NOP ( ) )
{
cur + + ;
if ( cur > = fend )
{
return 0 ;
}
}
if ( ( * cur & p . mask ) ! = p . opcode )
{
return 0 ;
}
cur + + ;
}
2023-12-29 18:33:29 +01:00
return : : narrow < u32 > ( ( cur - ptr ) * sizeof ( * ptr ) ) ;
2016-07-09 00:36:42 +02:00
}
2023-06-25 14:53:42 +02:00
static u32 ppu_test ( const be_t < u32 > * ptr , const void * fend , ppu_pattern_matrix pats )
2016-07-09 00:36:42 +02:00
{
for ( auto pat : pats )
{
if ( const u32 len = ppu_test ( ptr , fend , pat ) )
{
return len ;
}
}
return 0 ;
}
namespace ppu_patterns
{
using namespace ppu_instructions ;
2025-04-05 21:50:45 +02:00
const ppu_pattern abort1 [ ] {
{ STDU ( r1 , r1 , - 0xc0 ) } ,
{ MFLR ( r0 ) } ,
{ STD ( r26 , r1 , 0x90 ) } ,
{ STD ( r27 , r1 , 0x98 ) } ,
{ STD ( r28 , r1 , 0xa0 ) } ,
{ STD ( r29 , r1 , 0xa8 ) } ,
{ STD ( r30 , r1 , 0xb0 ) } ,
{ STD ( r31 , r1 , 0xb8 ) } ,
{ STD ( r0 , r1 , 0xd0 ) } ,
{ LI ( r3 , 4 ) } ,
{ LI ( r4 , 0 ) } ,
{ LI ( r11 , 0x3dc ) } ,
{ SC ( 0 ) } ,
{ MR ( r29 , r1 ) } ,
{ CLRLDI ( r29 , r29 , 32 ) } ,
{ LWZ ( r4 , r2 , 0 ) , 0xffff } ,
{ ADDI ( r31 , r1 , 0x70 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r5 , 0x19 ) } ,
{ MR ( r6 , r31 ) } ,
{ LWZ ( r28 , r29 , 4 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ ADDI ( r26 , r1 , 0x78 ) } ,
{ LD ( r3 , r28 , 0x10 ) } ,
{ MR ( r4 , r26 ) } ,
{ B ( 0 , false , true ) , 0x3fffffc } , // .hex2str
{ LI ( r5 , 0x10 ) } ,
{ CLRLDI ( r4 , r3 , 32 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LWZ ( r27 , r2 , 0 ) , 0xffff } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r5 , 1 ) } ,
{ MR ( r4 , r27 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LD ( r28 , r28 , 0 ) } ,
{ CMPDI ( cr7 , r28 , 0 ) } ,
{ BEQ ( cr7 , + 0x6c ) } ,
{ LWZ ( r30 , r2 , 0 ) , 0xffff } ,
{ LI ( r3 , 1 ) } ,
{ MR ( r4 , r30 ) } ,
{ LI ( r5 , 0x19 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ CLRLDI ( r29 , r28 , 32 ) } ,
{ CLRLDI ( r4 , r26 , 32 ) } ,
{ LD ( r3 , r29 , 0x10 ) } ,
{ 0 , 0xffffffff } , // .hex2str
{ LI ( r5 , 0x10 ) } ,
{ CLRLDI ( r4 , r3 , 32 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LI ( r3 , 1 ) } ,
{ MR ( r4 , r27 ) } ,
{ LI ( r5 , 1 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LD ( r28 , r29 , 0 ) } ,
{ CMPDI ( cr7 , r28 , 0 ) } ,
{ BNE ( cr7 , - 0x60 ) } ,
{ LWZ ( r4 , r2 , 0 ) , 0xffff } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r5 , 0x27 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LI ( r3 , 1 ) } ,
{ B ( 0 , false , true ) , 0x3fffffc } , // .sys_process_exit
{ LD ( r2 , r1 , 0x28 ) } ,
{ LI ( r3 , 1 ) } ,
{ B ( 0 , false , true ) , 0x3fffffc } , // .exit
2016-07-09 00:36:42 +02:00
} ;
2025-04-05 21:50:45 +02:00
const ppu_pattern abort2 [ ] {
{ STDU ( r1 , r1 , - 0xc0 ) } ,
{ MFLR ( r0 ) } ,
{ STD ( r27 , r1 , 0x98 ) } ,
{ STD ( r28 , r1 , 0xa0 ) } ,
{ STD ( r29 , r1 , 0xa8 ) } ,
{ STD ( r30 , r1 , 0xb0 ) } ,
{ STD ( r31 , r1 , 0xb8 ) } ,
{ STD ( r0 , r1 , 0xd0 ) } ,
{ MR ( r9 , r1 ) } ,
{ CLRLDI ( r9 , r9 , 32 ) } ,
{ LWZ ( r4 , r2 , 0 ) , 0xffff } ,
{ ADDI ( r31 , r1 , 0x70 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r5 , 0x19 ) } ,
{ MR ( r6 , r31 ) } ,
{ LWZ ( r29 , r9 , 4 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ ADDI ( r27 , r1 , 0x78 ) } ,
{ LD ( r3 , r29 , 0x10 ) } ,
{ MR ( r4 , r27 ) } ,
{ B ( 0 , false , true ) , 0x3fffffc } , // .hex2str
{ LI ( r5 , 0x10 ) } ,
{ CLRLDI ( r4 , r3 , 32 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LWZ ( r28 , r2 , 0 ) , 0xffff } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r5 , 1 ) } ,
{ MR ( r4 , r28 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LD ( r29 , r29 , 0 ) } ,
{ CMPDI ( cr7 , r29 , 0 ) } ,
{ BEQ ( cr7 , + 0x6c ) } ,
{ LWZ ( r30 , r2 , 0 ) , 0xffff } ,
{ LI ( r3 , 1 ) } ,
{ MR ( r4 , r30 ) } ,
{ LI ( r5 , 0x19 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ CLRLDI ( r29 , r29 , 32 ) } ,
{ CLRLDI ( r4 , r27 , 32 ) } ,
{ LD ( r3 , r29 , 0x10 ) } ,
{ 0 , 0xffffffff } , // .hex2str
{ LI ( r5 , 0x10 ) } ,
{ CLRLDI ( r4 , r3 , 32 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LI ( r3 , 1 ) } ,
{ MR ( r4 , r28 ) } ,
{ LI ( r5 , 1 ) } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LD ( r29 , r29 , 0 ) } ,
{ CMPDI ( cr7 , r29 , 0 ) } ,
{ BNE ( cr7 , - 0x60 ) } ,
{ LWZ ( r4 , r2 , 0 ) , 0xffff } ,
{ MR ( r6 , r31 ) } ,
{ LI ( r3 , 1 ) } ,
{ LI ( r5 , 0x27 ) } ,
{ LI ( r11 , 0x193 ) } ,
{ SC ( 0 ) } ,
{ LI ( r3 , 1 ) } ,
{ B ( 0 , false , true ) , 0x3fffffc } , // .sys_process_exit
{ LD ( r2 , r1 , 0x28 ) } ,
{ LI ( r3 , 1 ) } ,
{ B ( 0 , false , true ) , 0x3fffffc } , // .exit
2016-07-09 00:36:42 +02:00
} ;
2025-04-05 21:50:45 +02:00
const ppu_pattern_array abort [ ] {
2016-07-09 00:36:42 +02:00
abort1 ,
abort2 ,
} ;
2017-06-22 23:52:09 +02:00
2025-04-05 21:50:45 +02:00
const ppu_pattern get_context [ ] {
2017-06-22 23:52:09 +02:00
ADDI ( r3 , r3 , 0xf ) ,
CLRRDI ( r3 , r3 , 4 ) ,
STD ( r1 , r3 , 0 ) ,
STD ( r2 , r3 , 8 ) ,
STD ( r14 , r3 , 0x18 ) ,
STD ( r15 , r3 , 0x20 ) ,
STD ( r16 , r3 , 0x28 ) ,
STD ( r17 , r3 , 0x30 ) ,
STD ( r18 , r3 , 0x38 ) ,
STD ( r19 , r3 , 0x40 ) ,
STD ( r20 , r3 , 0x48 ) ,
STD ( r21 , r3 , 0x50 ) ,
STD ( r22 , r3 , 0x58 ) ,
STD ( r23 , r3 , 0x60 ) ,
STD ( r24 , r3 , 0x68 ) ,
STD ( r25 , r3 , 0x70 ) ,
STD ( r26 , r3 , 0x78 ) ,
STD ( r27 , r3 , 0x80 ) ,
STD ( r28 , r3 , 0x88 ) ,
STD ( r29 , r3 , 0x90 ) ,
STD ( r30 , r3 , 0x98 ) ,
STD ( r31 , r3 , 0xa0 ) ,
MFLR ( r0 ) ,
STD ( r0 , r3 , 0xa8 ) ,
0x7c000026 , // mfcr r0
STD ( r0 , r3 , 0xb0 ) ,
STFD ( f14 , r3 , 0xb8 ) ,
STFD ( f15 , r3 , 0xc0 ) ,
STFD ( F16 , r3 , 0xc8 ) ,
STFD ( f17 , r3 , 0xd0 ) ,
STFD ( f18 , r3 , 0xd8 ) ,
STFD ( f19 , r3 , 0xe0 ) ,
STFD ( f20 , r3 , 0xe8 ) ,
STFD ( f21 , r3 , 0xf0 ) ,
STFD ( f22 , r3 , 0xf8 ) ,
STFD ( f23 , r3 , 0x100 ) ,
STFD ( f24 , r3 , 0x108 ) ,
STFD ( f25 , r3 , 0x110 ) ,
STFD ( f26 , r3 , 0x118 ) ,
STFD ( f27 , r3 , 0x120 ) ,
STFD ( f28 , r3 , 0x128 ) ,
STFD ( f29 , r3 , 0x130 ) ,
STFD ( f30 , r3 , 0x138 ) ,
STFD ( f31 , r3 , 0x140 ) ,
0x7c0042A6 , // mfspr r0, vrsave
STD ( r0 , r3 , 0x148 ) ,
ADDI ( r4 , r3 , 0x150 ) ,
ADDI ( r5 , r3 , 0x160 ) ,
ADDI ( r6 , r3 , 0x170 ) ,
ADDI ( r7 , r3 , 0x180 ) ,
STVX ( v20 , r0 , r4 ) ,
STVX ( v21 , r0 , r5 ) ,
STVX ( v22 , r0 , r6 ) ,
STVX ( v23 , r0 , r7 ) ,
ADDI ( r4 , r4 , 0x40 ) ,
ADDI ( r5 , r5 , 0x40 ) ,
ADDI ( r6 , r6 , 0x40 ) ,
ADDI ( r7 , r7 , 0x40 ) ,
STVX ( v24 , r0 , r4 ) ,
STVX ( v25 , r0 , r5 ) ,
STVX ( v26 , r0 , r6 ) ,
STVX ( v27 , r0 , r7 ) ,
ADDI ( r4 , r4 , 0x40 ) ,
ADDI ( r5 , r5 , 0x40 ) ,
ADDI ( r6 , r6 , 0x40 ) ,
ADDI ( r7 , r7 , 0x40 ) ,
STVX ( v28 , r0 , r4 ) ,
STVX ( v29 , r0 , r5 ) ,
STVX ( v30 , r0 , r6 ) ,
STVX ( v31 , r0 , r7 ) ,
LI ( r3 , 0 ) ,
BLR ( ) ,
} ;
2025-04-05 21:50:45 +02:00
const ppu_pattern set_context [ ] {
2017-06-22 23:52:09 +02:00
ADDI ( r3 , r3 , 0xf ) ,
CLRRDI ( r3 , r3 , 4 ) ,
LD ( r1 , r3 , 0 ) ,
LD ( r2 , r3 , 8 ) ,
LD ( r14 , r3 , 0x18 ) ,
LD ( r15 , r3 , 0x20 ) ,
LD ( r16 , r3 , 0x28 ) ,
LD ( r17 , r3 , 0x30 ) ,
LD ( r18 , r3 , 0x38 ) ,
LD ( r19 , r3 , 0x40 ) ,
LD ( r20 , r3 , 0x48 ) ,
LD ( r21 , r3 , 0x50 ) ,
LD ( r22 , r3 , 0x58 ) ,
LD ( r23 , r3 , 0x60 ) ,
LD ( r24 , r3 , 0x68 ) ,
LD ( r25 , r3 , 0x70 ) ,
LD ( r26 , r3 , 0x78 ) ,
LD ( r27 , r3 , 0x80 ) ,
LD ( r28 , r3 , 0x88 ) ,
LD ( r29 , r3 , 0x90 ) ,
LD ( r30 , r3 , 0x98 ) ,
LD ( r31 , r3 , 0xa0 ) ,
LD ( r0 , r3 , 0xa8 ) ,
MTLR ( r0 ) ,
LD ( r0 , r3 , 0xb0 ) ,
0x7c101120 , // mtocrf 1, r0
0x7c102120 , // mtocrf 2, r0
0x7c104120 , // mtocrf 4, r0
0x7c108120 , // mtocrf 8, r0
0x7c110120 , // mtocrf 0x10, r0
0x7c120120 , // mtocrf 0x20, r0
0x7c140120 , // mtocrf 0x40, r0
0x7c180120 , // mtocrf 0x80, r0
LFD ( f14 , r3 , 0xb8 ) ,
LFD ( f15 , r3 , 0xc0 ) ,
LFD ( F16 , r3 , 0xc8 ) ,
LFD ( f17 , r3 , 0xd0 ) ,
LFD ( f18 , r3 , 0xd8 ) ,
LFD ( f19 , r3 , 0xe0 ) ,
LFD ( f20 , r3 , 0xe8 ) ,
LFD ( f21 , r3 , 0xf0 ) ,
LFD ( f22 , r3 , 0xf8 ) ,
LFD ( f23 , r3 , 0x100 ) ,
LFD ( f24 , r3 , 0x108 ) ,
LFD ( f25 , r3 , 0x110 ) ,
LFD ( f26 , r3 , 0x118 ) ,
LFD ( f27 , r3 , 0x120 ) ,
LFD ( f28 , r3 , 0x128 ) ,
LFD ( f29 , r3 , 0x130 ) ,
LFD ( f30 , r3 , 0x138 ) ,
LFD ( f31 , r3 , 0x140 ) ,
LD ( r0 , r3 , 0x148 ) ,
2025-04-05 21:50:45 +02:00
0x7c0043A6 , // mtspr vrsave, r0
2017-06-22 23:52:09 +02:00
ADDI ( r5 , r3 , 0x150 ) ,
ADDI ( r6 , r3 , 0x160 ) ,
ADDI ( r7 , r3 , 0x170 ) ,
ADDI ( r8 , r3 , 0x180 ) ,
LVX ( v20 , r0 , r5 ) ,
LVX ( v21 , r0 , r6 ) ,
LVX ( v22 , r0 , r7 ) ,
LVX ( v23 , r0 , r8 ) ,
ADDI ( r5 , r5 , 0x40 ) ,
ADDI ( r6 , r6 , 0x40 ) ,
ADDI ( r7 , r7 , 0x40 ) ,
ADDI ( r8 , r8 , 0x40 ) ,
LVX ( v24 , r0 , r5 ) ,
LVX ( v25 , r0 , r6 ) ,
LVX ( v26 , r0 , r7 ) ,
LVX ( v27 , r0 , r8 ) ,
ADDI ( r5 , r5 , 0x40 ) ,
ADDI ( r6 , r6 , 0x40 ) ,
ADDI ( r7 , r7 , 0x40 ) ,
ADDI ( r8 , r8 , 0x40 ) ,
LVX ( v28 , r0 , r5 ) ,
LVX ( v29 , r0 , r6 ) ,
LVX ( v30 , r0 , r7 ) ,
LVX ( v31 , r0 , r8 ) ,
LI ( r3 , 0 ) ,
0x7c041810 , // subfc r0, r4, r3
0x7c640194 , // addze r3, r4
BLR ( ) ,
} ;
2025-04-05 21:50:45 +02:00
const ppu_pattern x26c [ ] {
2017-06-22 23:52:09 +02:00
LI ( r9 , 0 ) ,
STD ( r9 , r6 , 0 ) ,
MR ( r1 , r6 ) ,
STDU ( r1 , r1 , - 0x70 ) ,
STD ( r9 , r1 , 0 ) ,
CLRLDI ( r7 , r3 , 32 ) ,
LWZ ( r0 , r7 , 0 ) ,
MTCTR ( r0 ) ,
LWZ ( r2 , r7 , 4 ) ,
MR ( r3 , r4 ) ,
MR ( r4 , r5 ) ,
BCTRL ( ) ,
} ;
2025-04-05 21:50:45 +02:00
const ppu_pattern x2a0 [ ] {
2017-06-22 23:52:09 +02:00
MR ( r8 , r1 ) ,
0x7d212850 , // subf r9, r1, r5
0x7c21496a , // stdux r1, r1, r9
MFLR ( r0 ) ,
STD ( r0 , r8 , 0x10 ) ,
STD ( r2 , r1 , 0x28 ) ,
CLRLDI ( r7 , r3 , 32 ) ,
LWZ ( r0 , r7 , 0 ) ,
MTCTR ( r0 ) ,
LWZ ( r2 , r7 , 4 ) ,
MR ( r3 , r4 ) ,
BCTRL ( ) ,
LD ( r2 , r1 , 0x28 ) ,
LD ( r9 , r1 , 0x0 ) ,
LD ( r0 , r9 , 0x10 ) ,
MTLR ( r0 ) ,
MR ( r1 , r9 ) ,
BLR ( ) ,
} ;
2025-04-05 21:50:45 +02:00
} // namespace ppu_patterns
2016-07-09 00:36:42 +02:00
2025-04-05 21:50:45 +02:00
static constexpr struct const_tag
{
} is_const ;
static constexpr struct range_tag
{
} is_range ;
static constexpr struct min_value_tag
{
} minv ;
static constexpr struct max_value_tag
{
} maxv ;
static constexpr struct sign_bit_tag
{
} sign_bitv ;
static constexpr struct load_addr_tag
{
} load_addrv ;
2025-01-31 16:15:51 +01:00
struct reg_state_t
{
u64 ge_than ;
u64 value_range ;
u64 bit_range ;
bool is_loaded ; // Is loaded from memory(?) (this includes offsetting from that address)
u32 tag ;
// Check if state is a constant value
bool operator ( ) ( const_tag ) const
{
2025-02-23 18:04:14 +01:00
return ! is_loaded & & value_range = = 1 & & bit_range = = 0 ;
2025-01-31 16:15:51 +01:00
}
// Check if state is a ranged value
bool operator ( ) ( range_tag ) const
{
2025-02-23 18:04:14 +01:00
return ! is_loaded & & bit_range = = 0 ;
2025-01-31 16:15:51 +01:00
}
// Get minimum bound
u64 operator ( ) ( min_value_tag ) const
{
return value_range ? ge_than : 0 ;
}
// Get maximum bound
u64 operator ( ) ( max_value_tag ) const
{
2025-02-08 16:00:07 +01:00
return value_range ? ( ge_than | bit_range ) + value_range : 0 ;
2025-01-31 16:15:51 +01:00
}
2025-02-23 18:04:14 +01:00
u64 operator ( ) ( sign_bit_tag ) const
{
return value_range = = 0 | | ( bit_range > > 63 ) | | ( ge_than + value_range - 1 ) > > 63 ! = ( ge_than > > 63 ) ? u64 { umax } : ( ge_than > > 63 ) ;
}
2025-01-31 16:15:51 +01:00
u64 operator ( ) ( load_addr_tag ) const
{
return is_loaded ? ge_than : 0 ;
}
// Check if value is of the same origin
bool is_equals ( const reg_state_t & rhs ) const
{
return ( rhs . tag & & rhs . tag = = this - > tag ) | | ( rhs ( is_const ) & & ( * this ) ( is_const ) & & rhs . ge_than = = ge_than ) ;
}
void set_lower_bound ( u64 value )
{
const u64 prev_max = ge_than + value_range ;
ge_than = value ;
value_range = prev_max - ge_than ;
}
// lower_bound = Max(bounds)
void lift_lower_bound ( u64 value )
{
const u64 prev_max = ge_than + value_range ;
// Get the value closer to upper bound (may be lower in value)
// Make 0 underflow (it is actually not 0 but UINT64_MAX + 1 in this context)
ge_than = value_range - 1 > prev_max - value - 1 ? value : ge_than ;
value_range = prev_max - ge_than ;
}
// Upper bound is not inclusive
void set_upper_bound ( u64 value )
{
value_range = value - ge_than ;
}
// upper_bound = Min(bounds)
void limit_upper_bound ( u64 value )
{
// Make 0 underflow (it is actually not 0 but UINT64_MAX + 1 in this context)
value_range = std : : min ( value - ge_than - 1 , value_range - 1 ) + 1 ;
}
// Clear bits using mask
2025-04-05 21:50:45 +02:00
// May fail if ge_than(+)value_range is modified by the operation
2025-01-31 16:15:51 +01:00
bool clear_mask ( u64 bit_mask , u32 & reg_tag_allocator )
{
if ( bit_mask = = umax )
{
return true ;
}
if ( ( ge_than & bit_mask ) > ~ value_range )
{
// Discard data: mask clears the carry bit
value_range = 0 ;
tag = reg_tag_allocator + + ;
return false ;
}
if ( ( ( ge_than & bit_mask ) + value_range ) & ~ bit_mask )
{
// Discard data: mask clears range bits
value_range = 0 ;
tag = reg_tag_allocator + + ;
return false ;
}
ge_than & = bit_mask ;
bit_range & = bit_mask ;
return true ;
}
bool clear_lower_bits ( u64 bits , u32 & reg_tag_allocator )
{
const u64 bit_mask = ~ ( ( u64 { 1 } < < bits ) - 1 ) ;
return clear_mask ( bit_mask , reg_tag_allocator ) ;
}
bool clear_higher_bits ( u64 bits , u32 & reg_tag_allocator )
{
const u64 bit_mask = ( u64 { 1 } < < ( ( 64 - bits ) % 64 ) ) - 1 ;
return clear_mask ( bit_mask , reg_tag_allocator ) ;
}
// Undefine bits using mask
void undef_mask ( u64 bit_mask )
{
ge_than & = bit_mask ;
bit_range | = ~ bit_mask ;
}
void undef_lower_bits ( u64 bits )
{
const u64 bit_mask = ~ ( ( u64 { 1 } < < bits ) - 1 ) ;
undef_mask ( bit_mask ) ;
}
void undef_higher_bits ( u64 bits )
{
const u64 bit_mask = ( u64 { 1 } < < ( ( 64 - bits ) % 64 ) ) - 1 ;
undef_mask ( bit_mask ) ;
}
// Add value to state, respecting of bit_range
void add ( u64 value )
{
const u64 old_ge = ge_than ;
ge_than + = value ;
// Adjust bit_range to undefine bits that their state may not be defined anymore
// No need to adjust value_range at the moment (wrapping around is implied)
bit_range | = ( ( old_ge | bit_range ) + value ) ^ ( ( old_ge ) + value ) ;
ge_than & = ~ bit_range ;
}
void sub ( u64 value )
{
// This function should be perfect, so it handles subtraction as well
add ( ~ value + 1 ) ;
}
// TODO: For CMP/CMPD value fixup
bool can_subtract_from_both_without_loss_of_meaning ( const reg_state_t & rhs , u64 value )
{
const reg_state_t & lhs = * this ;
return lhs . ge_than > = value & & rhs . ge_than > = value & & ! ! ( ( lhs . ge_than - value ) & lhs . bit_range ) & & ! ! ( ( rhs . ge_than - value ) & rhs . bit_range ) ;
}
// Bitwise shift left
bool shift_left ( u64 value , u32 & reg_tag_allocator )
{
2025-02-08 16:00:07 +01:00
if ( ! value | | ! value_range )
2025-01-31 16:15:51 +01:00
{
return true ;
}
const u64 mask_out = u64 { umax } > > value ;
if ( ( ge_than & mask_out ) > ~ value_range )
{
// Discard data: shift clears the carry bit
value_range = 0 ;
tag = reg_tag_allocator + + ;
return false ;
}
if ( ( ( ge_than & mask_out ) + value_range ) & ~ mask_out )
{
// Discard data: shift clears range bits
value_range = 0 ;
tag = reg_tag_allocator + + ;
return false ;
}
ge_than < < = value ;
bit_range < < = value ;
2025-02-08 16:00:07 +01:00
value_range = ( ( value_range - 1 ) < < value ) + 1 ;
2025-01-31 16:15:51 +01:00
return true ;
}
void load_const ( u64 value )
{
ge_than = value ;
value_range = 1 ;
is_loaded = false ;
tag = 0 ;
bit_range = 0 ;
}
u64 upper_bound ( ) const
{
return ge_than + value_range ;
}
// Using comparison evaluation data to bound data to our needs
void declare_ordering ( reg_state_t & right_register , bool lt , bool eq , bool gt , bool so , bool nso )
{
if ( lt & & gt )
{
// We don't care about inequality at the moment
// Except for 0
if ( right_register . value_range = = 1 & & right_register . ge_than = = 0 )
{
lift_lower_bound ( 1 ) ;
}
else if ( value_range = = 1 & & ge_than = = 0 )
{
right_register . lift_lower_bound ( 1 ) ;
}
return ;
}
if ( lt | | gt )
{
reg_state_t * rhs = & right_register ;
reg_state_t * lhs = this ;
// This is written as if for operator<
if ( gt )
{
std : : swap ( rhs , lhs ) ;
}
if ( lhs - > value_range = = 1 )
{
rhs - > lift_lower_bound ( lhs - > upper_bound ( ) - ( eq ? 1 : 0 ) ) ;
}
else if ( rhs - > value_range = = 1 )
{
lhs - > limit_upper_bound ( rhs - > ge_than + ( eq ? 1 : 0 ) ) ;
}
}
else if ( eq )
{
if ( value_range = = 1 )
{
right_register = * this ;
}
else if ( right_register . value_range = = 1 )
{
* this = right_register ;
}
else if ( 0 )
{
// set_lower_bound(std::max(ge_than, right_register.ge_than));
// set_upper_bound(std::min(value_range + ge_than, right_register.ge_than + right_register.value_range));
// right_register.ge_than = ge_than;
// right_register.value_range = value_range;
}
}
else if ( so | | nso )
{
// TODO: Implement(?)
}
}
} ;
2025-04-05 21:50:45 +02:00
static constexpr reg_state_t s_reg_const_0 { 0 , 1 } ;
2025-01-31 16:15:51 +01:00
2024-12-22 19:59:48 +01:00
template < >
bool ppu_module < lv2_obj > : : analyse ( u32 lib_toc , u32 entry , const u32 sec_end , const std : : vector < u32 > & applied , const std : : vector < u32 > & exported_funcs , std : : function < bool ( ) > check_aborted )
2016-07-07 20:42:39 +02:00
{
2025-01-23 14:45:31 +01:00
if ( segs . empty ( ) | | ! segs [ 0 ] . addr )
2023-08-06 08:43:13 +02:00
{
return false ;
}
2016-07-07 20:42:39 +02:00
// Assume first segment is executable
2017-07-01 01:08:51 +02:00
const u32 start = segs [ 0 ] . addr ;
2021-01-28 19:32:26 +01:00
2021-02-02 17:54:43 +01:00
// End of executable segment (may change)
u32 end = sec_end ? sec_end : segs [ 0 ] . addr + segs [ 0 ] . size ;
2016-07-07 20:42:39 +02:00
2025-01-31 16:15:51 +01:00
// End of all segments
u32 segs_end = end ;
for ( const auto & s : segs )
{
if ( s . size & & s . addr ! = start )
{
segs_end = std : : max ( segs_end , s . addr + s . size ) ;
}
}
2016-07-07 20:42:39 +02:00
// Known TOCs (usually only 1)
std : : unordered_set < u32 > TOCs ;
2025-01-16 11:30:50 +01:00
struct ppu_function_ext : ppu_function
{
2025-04-05 21:50:45 +02:00
// u32 stack_frame = 0;
2025-01-16 11:30:50 +01:00
u32 single_target = 0 ;
u32 trampoline = 0 ;
2025-10-04 21:19:57 +02:00
rx : : EnumBitSet < ppu_attr > attr { } ;
2025-01-16 11:30:50 +01:00
std : : set < u32 > callers { } ;
} ;
2016-07-07 20:42:39 +02:00
// Known functions
2025-01-16 11:30:50 +01:00
std : : map < u32 , ppu_function_ext > fmap ;
2018-07-07 01:14:15 +02:00
std : : set < u32 > known_functions ;
2016-07-07 20:42:39 +02:00
// Function analysis workload
2025-01-16 11:30:50 +01:00
std : : vector < std : : reference_wrapper < ppu_function_ext > > func_queue ;
2016-07-07 20:42:39 +02:00
2017-04-08 22:58:00 +02:00
// Known references (within segs, addr and value alignment = 4)
2025-01-31 13:38:19 +01:00
// For seg0, must be valid code
2025-01-31 16:15:51 +01:00
// Value is a sample of an address that refernces it
std : : map < u32 , u32 > addr_heap ;
2023-08-26 11:10:25 +02:00
if ( entry )
{
2025-01-31 16:15:51 +01:00
addr_heap . emplace ( entry , 0 ) ;
2023-08-26 11:10:25 +02:00
}
2017-04-08 22:58:00 +02:00
2025-01-18 13:47:12 +01:00
auto verify_ref = [ & ] ( u32 addr )
2023-08-06 13:02:36 +02:00
{
2025-01-31 16:15:51 +01:00
if ( ! is_relocatable )
2023-08-06 13:02:36 +02:00
{
// Fixed addresses
return true ;
}
// Check if the storage address exists within relocations
2025-04-05 21:50:45 +02:00
constexpr auto compare = [ ] ( const ppu_reloc & a , u32 addr )
{
return a . addr < addr ;
} ;
2025-01-18 13:47:12 +01:00
auto it = std : : lower_bound ( this - > relocs . begin ( ) , this - > relocs . end ( ) , ( addr & - 8 ) , compare ) ;
auto end = std : : lower_bound ( it , this - > relocs . end ( ) , ( addr & - 8 ) + 8 , compare ) ;
2023-08-06 13:02:36 +02:00
2025-01-18 13:47:12 +01:00
for ( ; it ! = end ; it + + )
2023-08-06 13:02:36 +02:00
{
2025-01-18 13:47:12 +01:00
const ppu_reloc & rel = * it ;
2023-08-06 13:02:36 +02:00
if ( ( rel . addr & - 8 ) = = ( addr & - 8 ) )
{
if ( rel . type ! = 38 & & rel . type ! = 44 & & ( rel . addr & - 4 ) ! = ( addr & - 4 ) )
{
continue ;
}
return true ;
}
}
return false ;
} ;
2023-09-04 18:31:27 +02:00
auto can_trap_continue = [ ] ( ppu_opcode_t op , ppu_itype : : type type )
{
if ( ( op . bo & 0x1c ) = = 0x1c | | ( op . bo & 0x7 ) = = 0x7 )
{
// All signed or unsigned <=>, must be true
return false ;
}
if ( op . simm16 = = 0 & & ( type = = ppu_itype : : TWI | | type = = ppu_itype : : TDI ) & & ( op . bo & 0x5 ) = = 0x5 )
{
// Logically greater or equal to 0
return false ;
}
return true ;
} ;
2025-01-31 13:38:19 +01:00
auto is_valid_code = [ ] ( std : : span < const be_t < u32 > > range , bool is_fixed_addr , u32 /*cia*/ )
{
for ( usz index = 0 ; index < std : : min < usz > ( range . size ( ) , 10 ) ; index + + )
{
const ppu_opcode_t op { + range [ index ] } ;
2025-02-23 18:04:14 +01:00
switch ( g_ppu_itype . decode ( op . opcode ) )
2025-01-31 13:38:19 +01:00
{
case ppu_itype : : UNK :
{
return false ;
}
case ppu_itype : : BC :
case ppu_itype : : B :
{
if ( ! is_fixed_addr & & op . aa )
{
return false ;
}
return true ;
}
case ppu_itype : : BCCTR :
case ppu_itype : : BCLR :
{
if ( op . opcode & 0xe000 )
{
// Garbage filter
return false ;
}
return true ;
}
default :
{
continue ;
}
}
}
return true ;
} ;
2016-07-07 20:42:39 +02:00
// Register new function
2025-01-16 11:30:50 +01:00
auto add_func = [ & ] ( u32 addr , u32 toc , u32 caller ) - > ppu_function_ext &
2016-07-07 20:42:39 +02:00
{
2025-02-23 18:04:14 +01:00
if ( addr < start | | addr > = end | | g_ppu_itype . decode ( * get_ptr < u32 > ( addr ) ) = = ppu_itype : : UNK )
2025-01-18 17:51:53 +01:00
{
if ( ! fmap . contains ( addr ) )
{
ppu_log . error ( " Potentially invalid function has been added: 0x%x " , addr ) ;
}
}
2025-01-16 11:30:50 +01:00
ppu_function_ext & func = fmap [ addr ] ;
2016-07-07 20:42:39 +02:00
2017-04-08 22:58:00 +02:00
if ( caller )
{
// Register caller
func . callers . emplace ( caller ) ;
}
2016-07-07 20:42:39 +02:00
if ( func . addr )
{
2020-02-19 18:03:59 +01:00
if ( toc & & func . toc & & func . toc ! = umax & & func . toc ! = toc )
2016-07-07 20:42:39 +02:00
{
2017-04-08 22:58:00 +02:00
func . toc = - 1 ;
2016-07-07 20:42:39 +02:00
}
2017-04-08 22:58:00 +02:00
else if ( toc & & func . toc = = 0 )
2016-07-07 20:42:39 +02:00
{
2017-04-08 22:58:00 +02:00
// Must then update TOC recursively
func . toc = toc ;
func_queue . emplace_back ( func ) ;
2016-07-07 20:42:39 +02:00
}
return func ;
}
func_queue . emplace_back ( func ) ;
func . addr = addr ;
func . toc = toc ;
2020-02-01 09:31:27 +01:00
ppu_log . trace ( " Function 0x%x added (toc=0x%x) " , addr , toc ) ;
2016-07-07 20:42:39 +02:00
return func ;
} ;
2023-06-25 14:53:42 +02:00
static const auto advance = [ ] ( auto & _ptr , auto & ptr , u32 count )
{
const auto old_ptr = ptr ;
_ptr + = count ;
ptr + = count ;
return old_ptr ;
} ;
2016-07-07 20:42:39 +02:00
// Register new TOC and find basic set of functions
auto add_toc = [ & ] ( u32 toc )
{
2020-02-19 18:03:59 +01:00
if ( ! toc | | toc = = umax | | ! TOCs . emplace ( toc ) . second )
2016-07-07 20:42:39 +02:00
{
return ;
}
// Grope for OPD section (TODO: optimization, better constraints)
for ( const auto & seg : segs )
{
2025-04-05 21:50:45 +02:00
if ( seg . size < 8 )
continue ;
2023-06-25 14:53:42 +02:00
const vm : : cptr < void > seg_end = vm : : cast ( seg . addr + seg . size - 8 ) ;
vm : : cptr < u32 > _ptr = vm : : cast ( seg . addr ) ;
auto ptr = get_ptr < u32 > ( _ptr ) ;
2020-12-14 07:03:49 +01:00
2023-06-25 14:53:42 +02:00
for ( ; _ptr < = seg_end ; )
2016-07-07 20:42:39 +02:00
{
2025-01-18 13:47:12 +01:00
if ( ptr [ 1 ] = = toc & & FN ( x > = start & & x < end & & x % 4 = = 0 ) ( ptr [ 0 ] ) & & verify_ref ( _ptr . addr ( ) ) )
2016-07-07 20:42:39 +02:00
{
// New function
2023-06-25 14:53:42 +02:00
ppu_log . trace ( " OPD*: [0x%x] 0x%x (TOC=0x%x) " , _ptr , ptr [ 0 ] , ptr [ 1 ] ) ;
add_func ( * ptr , addr_heap . count ( _ptr . addr ( ) ) ? toc : 0 , 0 ) ;
advance ( _ptr , ptr , 2 ) ;
}
else
{
advance ( _ptr , ptr , 1 ) ;
2016-07-07 20:42:39 +02:00
}
}
}
} ;
2016-07-13 01:54:34 +02:00
// Get next reliable function address
2016-07-07 20:42:39 +02:00
auto get_limit = [ & ] ( u32 addr ) - > u32
{
2018-07-07 01:14:15 +02:00
auto it = known_functions . lower_bound ( addr ) ;
return it = = known_functions . end ( ) ? end : * it ;
2016-07-07 20:42:39 +02:00
} ;
2017-04-08 22:58:00 +02:00
// Find references indiscriminately
2025-01-31 13:38:19 +01:00
// For seg0, must be valid code
2017-04-08 22:58:00 +02:00
for ( const auto & seg : segs )
{
2025-04-05 21:50:45 +02:00
if ( seg . size < 4 )
continue ;
2020-12-14 07:03:49 +01:00
2023-06-25 14:53:42 +02:00
vm : : cptr < u32 > _ptr = vm : : cast ( seg . addr ) ;
const vm : : cptr < void > seg_end = vm : : cast ( seg . addr + seg . size - 4 ) ;
auto ptr = get_ptr < u32 > ( _ptr ) ;
2024-04-18 14:42:11 +02:00
for ( ; _ptr < = seg_end ; advance ( _ptr , ptr , 1 ) )
2017-04-08 22:58:00 +02:00
{
const u32 value = * ptr ;
2025-01-18 13:47:12 +01:00
if ( value % 4 | | ! verify_ref ( _ptr . addr ( ) ) )
2017-04-08 22:58:00 +02:00
{
continue ;
}
for ( const auto & _seg : segs )
{
2025-04-05 21:50:45 +02:00
if ( ! _seg . size )
continue ;
2020-12-14 07:03:49 +01:00
2021-01-27 09:50:51 +01:00
if ( value > = start & & value < end )
2017-04-08 22:58:00 +02:00
{
2025-04-05 21:50:45 +02:00
if ( is_valid_code ( { ptr , ptr + ( end - value ) } , ! is_relocatable , _ptr . addr ( ) ) )
2025-01-31 13:38:19 +01:00
{
continue ;
}
2025-01-31 16:15:51 +01:00
addr_heap . emplace ( value , _ptr . addr ( ) ) ;
2017-04-08 22:58:00 +02:00
break ;
}
}
}
}
2016-07-07 20:42:39 +02:00
// Find OPD section
for ( const auto & sec : secs )
{
2023-08-23 07:19:49 +02:00
if ( sec . size % 8 )
{
continue ;
}
2017-07-01 01:08:51 +02:00
vm : : cptr < void > sec_end = vm : : cast ( sec . addr + sec . size ) ;
2016-07-07 20:42:39 +02:00
2016-07-13 23:56:30 +02:00
// Probe
2023-06-25 14:53:42 +02:00
for ( vm : : cptr < u32 > _ptr = vm : : cast ( sec . addr ) ; _ptr < sec_end ; _ptr + = 2 )
2016-07-07 20:42:39 +02:00
{
2023-06-25 14:53:42 +02:00
auto ptr = get_ptr < u32 > ( _ptr ) ;
if ( _ptr + 6 < = sec_end & & ! ptr [ 0 ] & & ! ptr [ 2 ] & & ptr [ 1 ] = = ptr [ 4 ] & & ptr [ 3 ] = = ptr [ 5 ] )
2016-07-15 11:52:37 +02:00
{
// Special OPD format case (some homebrews)
2023-06-25 14:53:42 +02:00
advance ( _ptr , ptr , 4 ) ;
2016-07-15 11:52:37 +02:00
}
2023-06-25 14:53:42 +02:00
if ( _ptr + 2 > sec_end )
2016-07-15 11:52:37 +02:00
{
sec_end . set ( 0 ) ;
break ;
}
2016-07-13 23:56:30 +02:00
const u32 addr = ptr [ 0 ] ;
const u32 _toc = ptr [ 1 ] ;
2016-07-27 23:43:22 +02:00
// Rough Table of Contents borders
2023-08-26 11:10:25 +02:00
const u32 toc_begin = _toc - 0x8000 ;
2025-04-05 21:50:45 +02:00
// const u32 toc_end = _toc + 0x7ffc;
2016-07-27 23:43:22 +02:00
2016-07-13 23:56:30 +02:00
// TODO: improve TOC constraints
2023-08-26 11:10:25 +02:00
if ( toc_begin % 4 | | ! get_ptr < u8 > ( toc_begin ) | | toc_begin > = 0x40000000 | | ( toc_begin > = start & & toc_begin < end ) )
2016-07-07 20:42:39 +02:00
{
2016-07-15 11:52:37 +02:00
sec_end . set ( 0 ) ;
2016-07-13 23:56:30 +02:00
break ;
}
2025-01-18 13:47:12 +01:00
if ( addr % 4 | | addr < start | | addr > = end | | ! verify_ref ( _ptr . addr ( ) ) )
2016-07-13 23:56:30 +02:00
{
2016-07-15 11:52:37 +02:00
sec_end . set ( 0 ) ;
2016-07-13 23:56:30 +02:00
break ;
2016-07-07 20:42:39 +02:00
}
2016-07-13 23:56:30 +02:00
}
2016-07-07 20:42:39 +02:00
2025-04-05 21:50:45 +02:00
if ( sec_end )
ppu_log . notice ( " Reading OPD section at 0x%x... " , sec . addr ) ;
2016-07-13 23:56:30 +02:00
// Mine
2023-06-25 14:53:42 +02:00
for ( vm : : cptr < u32 > _ptr = vm : : cast ( sec . addr ) ; _ptr < sec_end ; _ptr + = 2 )
2016-07-13 23:56:30 +02:00
{
2023-06-25 14:53:42 +02:00
auto ptr = get_ptr < u32 > ( _ptr ) ;
2016-07-15 11:52:37 +02:00
// Special case: see "Probe"
2025-04-05 21:50:45 +02:00
if ( ! ptr [ 0 ] )
advance ( _ptr , ptr , 4 ) ;
2016-07-15 11:52:37 +02:00
2016-07-13 23:56:30 +02:00
// Add function and TOC
const u32 addr = ptr [ 0 ] ;
const u32 toc = ptr [ 1 ] ;
2023-06-25 14:53:42 +02:00
ppu_log . trace ( " OPD: [0x%x] 0x%x (TOC=0x%x) " , _ptr , addr , toc ) ;
2016-07-13 23:56:30 +02:00
TOCs . emplace ( toc ) ;
2025-02-02 16:07:19 +01:00
add_func ( addr , addr_heap . count ( _ptr . addr ( ) ) ? toc : 0 , 0 ) ;
2018-07-07 01:14:15 +02:00
known_functions . emplace ( addr ) ;
2016-07-07 20:42:39 +02:00
}
}
2017-04-08 14:09:01 +02:00
// Register TOC from entry point
if ( entry & & ! lib_toc )
{
2023-07-22 11:03:45 +02:00
lib_toc = get_ref < u32 > ( entry ) ? get_ref < u32 > ( entry + 4 ) : get_ref < u32 > ( entry + 20 ) ;
2017-04-08 14:09:01 +02:00
}
2017-04-06 15:57:32 +02:00
// Secondary attempt
if ( TOCs . empty ( ) & & lib_toc )
2016-07-13 23:56:30 +02:00
{
add_toc ( lib_toc ) ;
}
2016-07-07 20:42:39 +02:00
2017-04-08 22:58:00 +02:00
// Clean TOCs
2017-07-01 01:08:51 +02:00
for ( auto & & pair : fmap )
2017-04-08 22:58:00 +02:00
{
2020-02-19 18:03:59 +01:00
if ( pair . second . toc = = umax )
2017-04-08 22:58:00 +02:00
{
pair . second . toc = 0 ;
}
}
2016-07-09 00:36:42 +02:00
// Find .eh_frame section
2016-07-07 20:42:39 +02:00
for ( const auto & sec : secs )
{
2023-08-23 07:19:49 +02:00
if ( sec . size % 4 )
{
continue ;
}
2017-07-01 01:08:51 +02:00
vm : : cptr < void > sec_end = vm : : cast ( sec . addr + sec . size ) ;
2016-07-07 20:42:39 +02:00
2016-07-09 00:36:42 +02:00
// Probe
2023-06-25 14:53:42 +02:00
for ( vm : : cptr < u32 > _ptr = vm : : cast ( sec . addr ) ; _ptr < sec_end ; )
2016-07-07 20:42:39 +02:00
{
2023-06-25 14:53:42 +02:00
if ( ! _ptr . aligned ( ) | | _ptr . addr ( ) < sec . addr | | _ptr > = sec_end )
2016-07-09 00:36:42 +02:00
{
2016-07-15 11:52:37 +02:00
sec_end . set ( 0 ) ;
2016-07-09 00:36:42 +02:00
break ;
}
2023-06-25 14:53:42 +02:00
const auto ptr = get_ptr < u32 > ( _ptr ) ;
2016-07-09 00:36:42 +02:00
const u32 size = ptr [ 0 ] + 4 ;
2023-06-25 14:53:42 +02:00
if ( size = = 4 & & _ptr + 1 = = sec_end )
2016-07-09 00:36:42 +02:00
{
// Null terminator
break ;
}
2023-06-25 14:53:42 +02:00
if ( size % 4 | | size < 0x10 | | _ptr + size / 4 > sec_end )
2016-07-09 00:36:42 +02:00
{
2016-07-15 11:52:37 +02:00
sec_end . set ( 0 ) ;
2016-07-09 00:36:42 +02:00
break ;
}
if ( ptr [ 1 ] )
{
2023-06-25 14:53:42 +02:00
const u32 cie_off = _ptr . addr ( ) - ptr [ 1 ] + 4 ;
2016-07-09 00:36:42 +02:00
2017-07-01 01:08:51 +02:00
if ( cie_off % 4 | | cie_off < sec . addr | | cie_off > = sec_end . addr ( ) )
2016-07-09 00:36:42 +02:00
{
2016-07-15 11:52:37 +02:00
sec_end . set ( 0 ) ;
2016-07-09 00:36:42 +02:00
break ;
}
}
2023-06-25 14:53:42 +02:00
_ptr = vm : : cast ( _ptr . addr ( ) + size ) ;
2016-07-07 20:42:39 +02:00
}
2025-04-05 21:50:45 +02:00
if ( sec_end & & sec . size > 4 )
ppu_log . notice ( " Reading .eh_frame section at 0x%x... " , sec . addr ) ;
2016-07-13 23:56:30 +02:00
2016-07-09 00:36:42 +02:00
// Mine
2023-06-25 14:53:42 +02:00
for ( vm : : cptr < u32 > _ptr = vm : : cast ( sec . addr ) ; _ptr < sec_end ; _ptr = vm : : cast ( _ptr . addr ( ) + * get_ptr < u32 > ( _ptr ) + 4 ) )
2016-07-07 20:42:39 +02:00
{
2023-06-25 14:53:42 +02:00
const auto ptr = get_ptr < u32 > ( _ptr ) ;
2020-02-19 16:26:41 +01:00
if ( ptr [ 0 ] = = 0u )
2016-07-09 00:36:42 +02:00
{
// Null terminator
break ;
}
2020-02-19 16:26:41 +01:00
if ( ptr [ 1 ] = = 0u )
2016-07-09 00:36:42 +02:00
{
// CIE
2020-02-01 09:31:27 +01:00
ppu_log . trace ( " .eh_frame: [0x%x] CIE 0x%x " , ptr , ptr [ 0 ] ) ;
2016-07-09 00:36:42 +02:00
}
else
2016-07-07 20:42:39 +02:00
{
2016-07-09 00:36:42 +02:00
// Get associated CIE (currently unused)
2023-06-25 14:53:42 +02:00
const vm : : cptr < u32 > cie = vm : : cast ( _ptr . addr ( ) - ptr [ 1 ] + 4 ) ;
2016-07-09 00:36:42 +02:00
u32 addr = 0 ;
u32 size = 0 ;
2016-07-13 23:56:30 +02:00
// TODO: 64 bit or 32 bit values (approximation)
2020-02-19 16:26:41 +01:00
if ( ptr [ 2 ] = = 0u & & ptr [ 3 ] = = 0u )
2016-07-09 00:36:42 +02:00
{
size = ptr [ 5 ] ;
}
2020-02-19 16:26:41 +01:00
else if ( ( ptr [ 2 ] + 1 = = 0u | | ptr [ 2 ] = = 0u ) & & ptr [ 4 ] = = 0u & & ptr [ 5 ] )
2016-07-09 00:36:42 +02:00
{
2016-07-13 23:56:30 +02:00
addr = ptr [ 3 ] ;
2016-07-13 01:54:34 +02:00
size = ptr [ 5 ] ;
2016-07-09 00:36:42 +02:00
}
2020-02-19 16:26:41 +01:00
else if ( ptr [ 2 ] + 1 & & ptr [ 3 ] )
2016-07-09 00:36:42 +02:00
{
addr = ptr [ 2 ] ;
size = ptr [ 3 ] ;
}
else
{
2020-02-01 09:31:27 +01:00
ppu_log . error ( " .eh_frame: [0x%x] 0x%x, 0x%x, 0x%x, 0x%x, 0x%x " , ptr , ptr [ 0 ] , ptr [ 1 ] , ptr [ 2 ] , ptr [ 3 ] , ptr [ 4 ] ) ;
2016-07-09 00:36:42 +02:00
continue ;
}
2016-07-07 20:42:39 +02:00
2016-07-13 23:56:30 +02:00
// TODO: absolute/relative offset (approximation)
if ( addr > 0xc0000000 )
{
2023-06-25 14:53:42 +02:00
addr + = _ptr . addr ( ) + 8 ;
2016-07-13 23:56:30 +02:00
}
2016-07-07 20:42:39 +02:00
2020-02-01 09:31:27 +01:00
ppu_log . trace ( " .eh_frame: [0x%x] FDE 0x%x (cie=*0x%x, addr=0x%x, size=0x%x) " , ptr , ptr [ 0 ] , cie , addr , size ) ;
2016-07-09 00:36:42 +02:00
2016-07-13 23:56:30 +02:00
// TODO: invalid offsets, zero offsets (removed functions?)
if ( addr % 4 | | size % 4 | | size > ( end - start ) | | addr < start | | addr + size > end )
2016-07-09 00:36:42 +02:00
{
2025-04-05 21:50:45 +02:00
if ( addr )
ppu_log . error ( " .eh_frame: Invalid function 0x%x " , addr ) ;
2016-07-09 00:36:42 +02:00
continue ;
}
2016-07-07 20:42:39 +02:00
2025-04-05 21:50:45 +02:00
// auto& func = add_func(addr, 0, 0);
// func.attr += ppu_attr::known_size;
// func.size = size;
// known_functions.emplace(func);
2016-07-07 20:42:39 +02:00
}
}
}
2024-05-30 08:28:14 +02:00
bool used_fallback = false ;
2024-06-07 12:03:52 +02:00
2024-06-08 11:44:23 +02:00
if ( func_queue . empty ( ) )
{
for ( u32 addr : exported_funcs )
{
const u32 faddr = get_ref < u32 > ( addr ) ;
if ( addr < start | | addr > = start + segs [ 0 ] . size )
{
// TODO: Reverse engineer how it works (maybe some flag in exports)
if ( faddr < start | | faddr > = start + segs [ 0 ] . size )
{
ppu_log . notice ( " Export not usable at 0x%x / 0x%x (0x%x...0x%x) " , addr , faddr , start , start + segs [ 0 ] . size ) ;
continue ;
}
addr = faddr ;
}
ppu_log . trace ( " Enqueued exported PPU function 0x%x for analysis " , addr ) ;
add_func ( addr , 0 , 0 ) ;
used_fallback = true ;
}
}
2024-04-18 14:42:11 +02:00
if ( func_queue . empty ( ) & & segs [ 0 ] . size > = 4u )
{
// Fallback, identify functions using callers (no jumptable detection, tail calls etc)
ppu_log . warning ( " Looking for PPU functions using callers. ('%s') " , name ) ;
vm : : cptr < u32 > _ptr = vm : : cast ( start ) ;
const vm : : cptr < void > seg_end = vm : : cast ( end - 4 ) ;
for ( auto ptr = get_ptr < u32 > ( _ptr ) ; _ptr < = seg_end ; advance ( _ptr , ptr , 1 ) )
{
const u32 iaddr = _ptr . addr ( ) ;
const ppu_opcode_t op { * ptr } ;
2025-02-23 18:04:14 +01:00
const ppu_itype : : type type = g_ppu_itype . decode ( op . opcode ) ;
2024-04-18 14:42:11 +02:00
2025-01-18 13:47:12 +01:00
if ( ( type = = ppu_itype : : B | | type = = ppu_itype : : BC ) & & op . lk & & ( ! op . aa | | verify_ref ( iaddr ) ) )
2024-04-18 14:42:11 +02:00
{
2024-06-07 12:03:52 +02:00
const u32 target = ( op . aa ? 0 : iaddr ) + ( type = = ppu_itype : : B ? + op . bt24 : + op . bt14 ) ;
2024-04-18 14:42:11 +02:00
if ( target > = start & & target < end & & target ! = iaddr & & target ! = iaddr + 4 )
{
2025-04-05 21:50:45 +02:00
if ( is_valid_code ( { get_ptr < u32 > ( target ) , get_ptr < u32 > ( end - 4 ) } , ! is_relocatable , target ) )
2024-04-18 14:42:11 +02:00
{
ppu_log . trace ( " Enqueued PPU function 0x%x using a caller at 0x%x " , target , iaddr ) ;
add_func ( target , 0 , 0 ) ;
2024-05-30 08:28:14 +02:00
used_fallback = true ;
2024-04-18 14:42:11 +02:00
}
}
}
}
}
2025-01-31 16:15:51 +01:00
// Register state (preallocated)
// Make sure to re-initialize this for every function!
std : : vector < reg_state_t > reg_state_storage ( 64 * ( is_relocatable ? 256 : 1024 ) ) ;
2025-02-08 16:00:07 +01:00
struct block_local_info_t
{
u32 addr = 0 ;
u32 size = 0 ;
u32 parent_block_idx = umax ;
2025-02-23 18:04:14 +01:00
ppua_reg_mask_t mapped_registers_mask { 0 } ;
ppua_reg_mask_t moved_registers_mask { 0 } ;
2025-02-08 16:00:07 +01:00
} ;
// Block analysis workload
std : : vector < block_local_info_t > block_queue_storage ;
2025-02-23 18:04:14 +01:00
bool is_function_caller_analysis = false ;
2016-07-07 20:42:39 +02:00
// Main loop (func_queue may grow)
2025-02-23 18:04:14 +01:00
for ( usz i = 0 ; i < = func_queue . size ( ) ; i + + )
2016-07-07 20:42:39 +02:00
{
2025-02-23 18:04:14 +01:00
if ( i = = func_queue . size ( ) )
{
if ( is_function_caller_analysis )
{
break ;
}
// Add callers of imported functions to be analyzed
std : : set < u32 > added ;
for ( const auto & [ stub_addr , _ ] : stub_addr_to_constant_state_of_registers )
{
auto it = fmap . upper_bound ( stub_addr ) ;
if ( it = = fmap . begin ( ) )
{
continue ;
}
auto stub_func = std : : prev ( it ) ;
for ( u32 caller : stub_func - > second . callers )
{
ppu_function_ext & func = : : at32 ( fmap , caller ) ;
if ( func . attr . none_of ( ppu_attr : : no_size ) & & ! func . blocks . empty ( ) & & ! added . contains ( caller ) )
{
added . emplace ( caller ) ;
func_queue . emplace_back ( : : at32 ( fmap , caller ) ) ;
}
}
}
if ( added . empty ( ) )
{
break ;
}
is_function_caller_analysis = true ;
}
2023-04-08 17:03:05 +02:00
if ( check_aborted & & check_aborted ( ) )
{
return false ;
}
2025-02-23 18:04:14 +01:00
ppu_function_ext & func = func_queue [ i ] ;
2016-07-07 20:42:39 +02:00
2017-04-08 22:58:00 +02:00
// Fixup TOCs
2025-02-23 18:04:14 +01:00
if ( ! is_function_caller_analysis & & func . toc & & func . toc ! = umax )
2017-04-08 22:58:00 +02:00
{
2025-01-16 11:08:55 +01:00
// Fixup callers
2017-04-08 22:58:00 +02:00
for ( u32 addr : func . callers )
{
2025-01-16 11:30:50 +01:00
ppu_function_ext & caller = fmap [ addr ] ;
2017-04-08 22:58:00 +02:00
if ( ! caller . toc )
{
add_func ( addr , func . toc - caller . trampoline , 0 ) ;
}
}
2025-01-16 11:08:55 +01:00
// Fixup callees
auto for_callee = [ & ] ( u32 addr )
2017-04-08 22:58:00 +02:00
{
2025-01-16 11:08:55 +01:00
if ( addr < func . addr | | addr > = func . addr + func . size )
{
return ;
}
const u32 iaddr = addr ;
const ppu_opcode_t op { get_ref < u32 > ( iaddr ) } ;
2025-02-23 18:04:14 +01:00
const ppu_itype : : type type = g_ppu_itype . decode ( op . opcode ) ;
2025-01-16 11:08:55 +01:00
if ( type = = ppu_itype : : B | | type = = ppu_itype : : BC )
{
const u32 target = ( op . aa ? 0 : iaddr ) + ( type = = ppu_itype : : B ? + op . bt24 : + op . bt14 ) ;
2025-01-18 13:47:12 +01:00
if ( target > = start & & target < end & & ( ! op . aa | | verify_ref ( iaddr ) ) )
2025-01-16 11:08:55 +01:00
{
if ( target < func . addr | | target > = func . addr + func . size )
{
2025-01-16 11:30:50 +01:00
ppu_function_ext & callee = fmap [ target ] ;
2025-01-16 11:08:55 +01:00
if ( ! callee . toc )
{
add_func ( target , func . toc + func . trampoline , 0 ) ;
}
}
}
}
} ;
for ( const auto & [ addr , size ] : func . blocks )
{
if ( size )
{
for_callee ( addr + size - 4 ) ;
}
}
if ( func . size )
{
for_callee ( func . addr + func . size - 4 ) ;
}
// For trampoline functions
if ( func . single_target )
{
2025-01-16 11:30:50 +01:00
ppu_function_ext & callee = fmap [ func . single_target ] ;
2017-04-08 22:58:00 +02:00
if ( ! callee . toc )
{
2025-01-16 11:08:55 +01:00
add_func ( func . single_target , func . toc + func . trampoline , 0 ) ;
2017-04-08 22:58:00 +02:00
}
}
}
2025-02-23 18:04:14 +01:00
if ( ! is_function_caller_analysis & & func . blocks . empty ( ) )
2016-07-07 20:42:39 +02:00
{
// Special function analysis
2023-06-25 14:53:42 +02:00
const vm : : cptr < u32 > _ptr = vm : : cast ( func . addr ) ;
2016-07-07 20:42:39 +02:00
const vm : : cptr < void > fend = vm : : cast ( end ) ;
2023-06-25 14:53:42 +02:00
const auto ptr = get_ptr < u32 > ( _ptr ) ;
2016-07-07 20:42:39 +02:00
using namespace ppu_instructions ;
2023-06-25 14:53:42 +02:00
if ( _ptr + 1 < = fend & & ( ptr [ 0 ] & 0xfc000001 ) = = B ( { } , { } ) )
2016-07-07 20:42:39 +02:00
{
2017-04-06 15:57:32 +02:00
// Simple trampoline
2023-06-25 14:53:42 +02:00
const u32 target = ( ptr [ 0 ] & 0x2 ? 0 : _ptr . addr ( ) ) + ppu_opcode_t { ptr [ 0 ] } . bt24 ;
2016-07-13 23:56:30 +02:00
if ( target = = func . addr )
{
// Special case
func . size = 0x4 ;
func . blocks . emplace ( func . addr , func . size ) ;
func . attr + = ppu_attr : : no_return ;
continue ;
}
2018-02-09 15:49:37 +01:00
2025-01-18 13:47:12 +01:00
if ( target > = start & & target < end & & ( ~ ptr [ 0 ] & 0x2 | | verify_ref ( _ptr . addr ( ) ) ) )
2016-07-09 00:36:42 +02:00
{
2017-04-08 22:58:00 +02:00
auto & new_func = add_func ( target , func . toc , func . addr ) ;
2016-07-13 23:56:30 +02:00
if ( new_func . blocks . empty ( ) )
{
func_queue . emplace_back ( func ) ;
continue ;
}
2016-07-09 00:36:42 +02:00
func . size = 0x4 ;
func . blocks . emplace ( func . addr , func . size ) ;
2016-07-13 23:56:30 +02:00
func . attr + = new_func . attr & ppu_attr : : no_return ;
2025-01-16 11:08:55 +01:00
func . single_target = target ;
2017-04-08 22:58:00 +02:00
func . trampoline = 0 ;
2016-07-09 00:36:42 +02:00
continue ;
}
2016-07-07 20:42:39 +02:00
}
2023-06-25 14:53:42 +02:00
if ( _ptr + 0x4 < = fend & &
2017-02-12 19:12:08 +01:00
( ptr [ 0 ] & 0xffff0000 ) = = LIS ( r11 , 0 ) & &
( ptr [ 1 ] & 0xffff0000 ) = = ADDI ( r11 , r11 , 0 ) & &
ptr [ 2 ] = = MTCTR ( r11 ) & &
ptr [ 3 ] = = BCTR ( ) )
{
2017-04-06 15:57:32 +02:00
// Simple trampoline
2017-02-12 19:12:08 +01:00
const u32 target = ( ptr [ 0 ] < < 16 ) + ppu_opcode_t { ptr [ 1 ] } . simm16 ;
2025-01-18 13:47:12 +01:00
if ( target > = start & & target < end & & verify_ref ( _ptr . addr ( ) ) )
2017-02-12 19:12:08 +01:00
{
2017-04-08 22:58:00 +02:00
auto & new_func = add_func ( target , func . toc , func . addr ) ;
2017-02-12 19:12:08 +01:00
if ( new_func . blocks . empty ( ) )
{
func_queue . emplace_back ( func ) ;
continue ;
}
func . size = 0x10 ;
func . blocks . emplace ( func . addr , func . size ) ;
func . attr + = new_func . attr & ppu_attr : : no_return ;
2025-01-16 11:08:55 +01:00
func . single_target = target ;
2017-04-08 22:58:00 +02:00
func . trampoline = 0 ;
2017-02-12 19:12:08 +01:00
continue ;
}
}
2023-06-25 14:53:42 +02:00
if ( _ptr + 0x7 < = fend & &
2017-12-31 15:38:02 +01:00
ptr [ 0 ] = = STD ( r2 , r1 , 0x28 ) & &
( ptr [ 1 ] & 0xffff0000 ) = = ADDIS ( r12 , r2 , { } ) & &
( ptr [ 2 ] & 0xffff0000 ) = = LWZ ( r11 , r12 , { } ) & &
( ptr [ 3 ] & 0xffff0000 ) = = ADDIS ( r2 , r2 , { } ) & &
( ptr [ 4 ] & 0xffff0000 ) = = ADDI ( r2 , r2 , { } ) & &
ptr [ 5 ] = = MTCTR ( r11 ) & &
ptr [ 6 ] = = BCTR ( ) )
{
func . toc = - 1 ;
func . size = 0x1C ;
func . blocks . emplace ( func . addr , func . size ) ;
func . attr + = ppu_attr : : known_size ;
2025-01-30 15:00:18 +01:00
known_functions . emplace ( func . addr ) ;
2017-12-31 15:38:02 +01:00
// Look for another imports to fill gaps (hack)
2023-06-25 14:53:42 +02:00
auto _p2 = _ptr + 7 ;
auto p2 = get_ptr < u32 > ( _p2 ) ;
2017-12-31 15:38:02 +01:00
2023-06-25 14:53:42 +02:00
while ( _p2 + 0x7 < = fend & &
2025-04-05 21:50:45 +02:00
p2 [ 0 ] = = STD ( r2 , r1 , 0x28 ) & &
( p2 [ 1 ] & 0xffff0000 ) = = ADDIS ( r12 , r2 , { } ) & &
( p2 [ 2 ] & 0xffff0000 ) = = LWZ ( r11 , r12 , { } ) & &
( p2 [ 3 ] & 0xffff0000 ) = = ADDIS ( r2 , r2 , { } ) & &
( p2 [ 4 ] & 0xffff0000 ) = = ADDI ( r2 , r2 , { } ) & &
p2 [ 5 ] = = MTCTR ( r11 ) & &
p2 [ 6 ] = = BCTR ( ) )
2017-12-31 15:38:02 +01:00
{
2023-06-25 14:53:42 +02:00
auto & next = add_func ( _p2 . addr ( ) , - 1 , func . addr ) ;
2017-12-31 15:38:02 +01:00
next . size = 0x1C ;
next . blocks . emplace ( next . addr , next . size ) ;
next . attr + = ppu_attr : : known_size ;
2025-01-30 15:00:18 +01:00
known_functions . emplace ( _p2 . addr ( ) ) ;
2023-06-25 14:53:42 +02:00
advance ( _p2 , p2 , 7 ) ;
2017-12-31 15:38:02 +01:00
}
continue ;
}
2023-06-25 14:53:42 +02:00
if ( _ptr + 0x7 < = fend & &
2019-08-15 13:05:34 +02:00
ptr [ 0 ] = = STD ( r2 , r1 , 0x28 ) & &
( ptr [ 1 ] & 0xffff0000 ) = = ADDIS ( r2 , r2 , { } ) & &
( ptr [ 2 ] & 0xffff0000 ) = = ADDI ( r2 , r2 , { } ) & &
( ptr [ 3 ] & 0xffff0000 ) = = LIS ( r11 , { } ) & &
( ptr [ 4 ] & 0xffff0000 ) = = ADDI ( r11 , r11 , { } ) & &
ptr [ 5 ] = = MTCTR ( r11 ) & &
ptr [ 6 ] = = BCTR ( ) )
{
// Trampoline with TOC
const u32 target = ( ptr [ 3 ] < < 16 ) + s16 ( ptr [ 4 ] ) ;
const u32 toc_add = ( ptr [ 1 ] < < 16 ) + s16 ( ptr [ 2 ] ) ;
2025-01-30 15:00:18 +01:00
if ( target > = start & & target < end & & verify_ref ( ( _ptr + 3 ) . addr ( ) ) )
2019-08-15 13:05:34 +02:00
{
auto & new_func = add_func ( target , 0 , func . addr ) ;
2020-02-19 18:03:59 +01:00
if ( func . toc & & func . toc ! = umax & & new_func . toc = = 0 )
2019-08-15 13:05:34 +02:00
{
const u32 toc = func . toc + toc_add ;
add_toc ( toc ) ;
add_func ( new_func . addr , toc , 0 ) ;
}
2020-02-19 18:03:59 +01:00
else if ( new_func . toc & & new_func . toc ! = umax & & func . toc = = 0 )
2019-08-15 13:05:34 +02:00
{
const u32 toc = new_func . toc - toc_add ;
add_toc ( toc ) ;
add_func ( func . addr , toc , 0 ) ;
}
2025-04-05 21:50:45 +02:00
// else if (new_func.toc - func.toc != toc_add)
2023-06-12 03:48:27 +02:00
//{
// func.toc = -1;
// new_func.toc = -1;
2025-04-05 21:50:45 +02:00
// }
2019-08-15 13:05:34 +02:00
if ( new_func . blocks . empty ( ) )
{
func_queue . emplace_back ( func ) ;
continue ;
}
func . size = 0x1C ;
func . blocks . emplace ( func . addr , func . size ) ;
func . attr + = new_func . attr & ppu_attr : : no_return ;
2025-01-16 11:08:55 +01:00
func . single_target = target ;
2019-08-15 13:05:34 +02:00
func . trampoline = toc_add ;
continue ;
}
}
2019-11-29 23:28:06 +01:00
2023-06-25 14:53:42 +02:00
if ( _ptr + 4 < = fend & &
2016-07-07 20:42:39 +02:00
ptr [ 0 ] = = STD ( r2 , r1 , 0x28 ) & &
( ptr [ 1 ] & 0xffff0000 ) = = ADDIS ( r2 , r2 , { } ) & &
( ptr [ 2 ] & 0xffff0000 ) = = ADDI ( r2 , r2 , { } ) & &
( ptr [ 3 ] & 0xfc000001 ) = = B ( { } , { } ) )
{
2017-04-06 15:57:32 +02:00
// Trampoline with TOC
2017-04-08 22:58:00 +02:00
const u32 toc_add = ( ptr [ 1 ] < < 16 ) + s16 ( ptr [ 2 ] ) ;
2023-06-25 14:53:42 +02:00
const u32 target = ( ptr [ 3 ] & 0x2 ? 0 : ( _ptr + 3 ) . addr ( ) ) + ppu_opcode_t { ptr [ 3 ] } . bt24 ;
2016-07-09 00:36:42 +02:00
2025-01-18 13:47:12 +01:00
if ( target > = start & & target < end & & ( ~ ptr [ 3 ] & 0x2 | | verify_ref ( ( _ptr + 3 ) . addr ( ) ) ) )
2016-07-09 00:36:42 +02:00
{
2017-04-06 15:57:32 +02:00
auto & new_func = add_func ( target , 0 , func . addr ) ;
2016-07-13 23:56:30 +02:00
2020-02-19 18:03:59 +01:00
if ( func . toc & & func . toc ! = umax & & new_func . toc = = 0 )
2017-04-08 22:58:00 +02:00
{
const u32 toc = func . toc + toc_add ;
add_toc ( toc ) ;
add_func ( new_func . addr , toc , 0 ) ;
}
2020-02-19 18:03:59 +01:00
else if ( new_func . toc & & new_func . toc ! = umax & & func . toc = = 0 )
2017-04-08 22:58:00 +02:00
{
const u32 toc = new_func . toc - toc_add ;
add_toc ( toc ) ;
add_func ( func . addr , toc , 0 ) ;
}
2025-04-05 21:50:45 +02:00
// else if (new_func.toc - func.toc != toc_add)
2023-06-12 03:48:27 +02:00
//{
// func.toc = -1;
// new_func.toc = -1;
2025-04-05 21:50:45 +02:00
// }
2017-04-08 22:58:00 +02:00
2016-07-13 23:56:30 +02:00
if ( new_func . blocks . empty ( ) )
{
func_queue . emplace_back ( func ) ;
continue ;
}
2016-07-09 00:36:42 +02:00
func . size = 0x10 ;
func . blocks . emplace ( func . addr , func . size ) ;
2016-07-24 19:54:15 +02:00
func . attr + = new_func . attr & ppu_attr : : no_return ;
2025-01-16 11:08:55 +01:00
func . single_target = target ;
2017-04-08 22:58:00 +02:00
func . trampoline = toc_add ;
2016-07-09 00:36:42 +02:00
continue ;
}
2016-07-07 20:42:39 +02:00
}
2023-06-25 14:53:42 +02:00
if ( _ptr + 8 < = fend & &
2016-07-07 20:42:39 +02:00
( ptr [ 0 ] & 0xffff0000 ) = = LI ( r12 , 0 ) & &
( ptr [ 1 ] & 0xffff0000 ) = = ORIS ( r12 , r12 , 0 ) & &
( ptr [ 2 ] & 0xffff0000 ) = = LWZ ( r12 , r12 , 0 ) & &
ptr [ 3 ] = = STD ( r2 , r1 , 0x28 ) & &
ptr [ 4 ] = = LWZ ( r0 , r12 , 0 ) & &
ptr [ 5 ] = = LWZ ( r2 , r12 , 4 ) & &
ptr [ 6 ] = = MTCTR ( r0 ) & &
2017-03-22 21:23:47 +01:00
ptr [ 7 ] = = BCTR ( ) )
2016-07-07 20:42:39 +02:00
{
// The most used simple import stub
2017-04-06 15:57:32 +02:00
func . toc = - 1 ;
2016-07-07 20:42:39 +02:00
func . size = 0x20 ;
func . blocks . emplace ( func . addr , func . size ) ;
2018-07-07 01:14:15 +02:00
known_functions . emplace ( func . addr ) ;
2016-07-13 01:54:34 +02:00
func . attr + = ppu_attr : : known_size ;
2017-02-12 19:12:08 +01:00
// Look for another imports to fill gaps (hack)
2023-06-25 14:53:42 +02:00
auto _p2 = _ptr + 8 ;
auto p2 = get_ptr < u32 > ( _p2 ) ;
2017-02-12 19:12:08 +01:00
2023-06-25 14:53:42 +02:00
while ( _p2 + 8 < = fend & &
2025-04-05 21:50:45 +02:00
( p2 [ 0 ] & 0xffff0000 ) = = LI ( r12 , 0 ) & &
( p2 [ 1 ] & 0xffff0000 ) = = ORIS ( r12 , r12 , 0 ) & &
( p2 [ 2 ] & 0xffff0000 ) = = LWZ ( r12 , r12 , 0 ) & &
p2 [ 3 ] = = STD ( r2 , r1 , 0x28 ) & &
p2 [ 4 ] = = LWZ ( r0 , r12 , 0 ) & &
p2 [ 5 ] = = LWZ ( r2 , r12 , 4 ) & &
p2 [ 6 ] = = MTCTR ( r0 ) & &
p2 [ 7 ] = = BCTR ( ) )
2017-02-12 19:12:08 +01:00
{
2023-06-25 14:53:42 +02:00
auto & next = add_func ( _p2 . addr ( ) , - 1 , func . addr ) ;
2017-02-12 19:12:08 +01:00
next . size = 0x20 ;
next . blocks . emplace ( next . addr , next . size ) ;
next . attr + = ppu_attr : : known_size ;
2023-06-25 14:53:42 +02:00
advance ( _p2 , p2 , 8 ) ;
2018-07-07 01:14:15 +02:00
known_functions . emplace ( next . addr ) ;
2017-02-12 19:12:08 +01:00
}
continue ;
}
2023-06-25 14:53:42 +02:00
if ( _ptr + 3 < = fend & &
2020-02-19 16:26:41 +01:00
ptr [ 0 ] = = 0x7c0004acu & &
ptr [ 1 ] = = 0x00000000u & &
2017-02-12 19:12:08 +01:00
ptr [ 2 ] = = BLR ( ) )
{
// Weird function (illegal instruction)
func . size = 0xc ;
func . blocks . emplace ( func . addr , func . size ) ;
2025-04-05 21:50:45 +02:00
// func.attr += ppu_attr::no_return;
2016-07-07 20:42:39 +02:00
continue ;
}
2023-06-25 14:53:42 +02:00
if ( const u32 len = ppu_test ( ptr , get_ptr < void > ( fend ) , ppu_patterns : : abort ) )
2016-07-09 00:36:42 +02:00
{
2016-07-13 23:56:30 +02:00
// Function "abort"
2020-02-01 09:31:27 +01:00
ppu_log . notice ( " Function [0x%x]: 'abort' " , func . addr ) ;
2016-07-09 00:36:42 +02:00
func . attr + = ppu_attr : : no_return ;
func . attr + = ppu_attr : : known_size ;
func . size = len ;
}
2016-07-07 20:42:39 +02:00
// TODO: detect no_return, scribe more TODOs
// Acknowledge completion
func . blocks . emplace ( vm : : cast ( func . addr ) , 0 ) ;
}
2016-07-13 01:54:34 +02:00
// Get function limit
2018-09-02 19:22:35 +02:00
const u32 func_end = std : : min < u32 > ( get_limit ( func . addr + 1 ) , func . attr & ppu_attr : : known_size ? func . addr + func . size : end ) ;
2016-07-13 01:54:34 +02:00
2025-02-08 16:00:07 +01:00
auto & block_queue = block_queue_storage ;
block_queue . clear ( ) ;
2025-01-31 16:15:51 +01:00
u32 reg_tag_allocator = 1 ;
auto make_unknown_reg_state = [ & ] ( )
{
reg_state_t s { } ;
s . tag = reg_tag_allocator + + ;
return s ;
} ;
2016-07-07 20:42:39 +02:00
// Add new block for analysis
2025-01-31 16:15:51 +01:00
auto add_block = [ & ] ( u32 addr , u32 parent_block ) - > u32
2016-07-07 20:42:39 +02:00
{
2016-07-13 01:54:34 +02:00
if ( addr < func . addr | | addr > = func_end )
{
2025-01-31 16:15:51 +01:00
return umax ;
2016-07-13 01:54:34 +02:00
}
2016-07-07 20:42:39 +02:00
const auto _pair = func . blocks . emplace ( addr , 0 ) ;
if ( _pair . second )
{
2025-02-08 16:00:07 +01:00
auto & block = block_queue . emplace_back ( block_local_info_t { addr } ) ;
block . parent_block_idx = parent_block ;
2025-04-05 21:50:45 +02:00
2025-01-31 16:15:51 +01:00
if ( parent_block ! = umax )
{
2025-02-08 16:00:07 +01:00
// Inherit loaded registers mask (lazily)
2025-02-23 18:04:14 +01:00
block . mapped_registers_mask . mask = : : at32 ( block_queue , parent_block ) . mapped_registers_mask . mask ;
2025-01-31 16:15:51 +01:00
}
return static_cast < u32 > ( block_queue . size ( ) - 1 ) ;
2016-07-07 20:42:39 +02:00
}
2025-01-31 16:15:51 +01:00
return umax ;
2016-07-07 20:42:39 +02:00
} ;
2025-02-23 18:04:14 +01:00
std : : map < u32 , u32 > preserve_blocks ;
if ( is_function_caller_analysis )
{
preserve_blocks = std : : move ( func . blocks ) ;
func . blocks . clear ( ) ;
func . blocks . emplace ( preserve_blocks . begin ( ) - > first , 0 ) ;
}
2016-07-07 20:42:39 +02:00
for ( auto & block : func . blocks )
{
2016-07-13 01:54:34 +02:00
if ( ! block . second & & block . first < func_end )
2016-07-07 20:42:39 +02:00
{
2025-01-31 16:15:51 +01:00
block_queue . emplace_back ( block_local_info_t { block . first } ) ;
2016-07-07 20:42:39 +02:00
}
}
// TODO: lower priority?
2018-09-02 19:22:35 +02:00
if ( func . attr & ppu_attr : : no_size )
2016-07-07 20:42:39 +02:00
{
2016-07-13 01:54:34 +02:00
// Get next function
2017-07-01 01:08:51 +02:00
const auto _next = fmap . lower_bound ( func . blocks . crbegin ( ) - > first + 1 ) ;
2016-07-13 01:54:34 +02:00
// Get limit
2017-07-01 01:08:51 +02:00
const u32 func_end2 = _next = = fmap . end ( ) ? func_end : std : : min < u32 > ( _next - > first , func_end ) ;
2016-07-07 20:42:39 +02:00
2017-04-08 22:58:00 +02:00
// Set more block entries
2025-04-05 21:50:45 +02:00
std : : for_each ( addr_heap . lower_bound ( func . addr ) , addr_heap . lower_bound ( func_end2 ) , [ & ] ( auto a )
{
add_block ( a . first , umax ) ;
} ) ;
2016-07-07 20:42:39 +02:00
}
2025-02-08 16:00:07 +01:00
bool postpone_analysis = false ;
2016-07-07 20:42:39 +02:00
// Block loop (block_queue may grow, may be aborted via clearing)
2025-02-08 16:00:07 +01:00
for ( u32 j = 0 ; ! postpone_analysis & & j < block_queue . size ( ) ; [ & ] ( )
2025-01-31 16:15:51 +01:00
{
2025-04-05 21:50:45 +02:00
if ( u32 size = block_queue [ j ] . size )
{
// Update size
func . blocks [ block_queue [ j ] . addr ] = size ;
}
2016-07-07 20:42:39 +02:00
2025-04-05 21:50:45 +02:00
j + + ;
} ( ) )
2025-01-31 16:15:51 +01:00
{
block_local_info_t * block = & block_queue [ j ] ;
const u32 block_addr = block - > addr ;
vm : : cptr < u32 > _ptr = vm : : cast ( block_addr ) ;
2023-06-25 14:53:42 +02:00
auto ptr = ensure ( get_ptr < u32 > ( _ptr ) ) ;
2025-01-31 16:15:51 +01:00
auto is_reg_mapped = [ & ] ( u32 index )
{
2025-02-23 18:04:14 +01:00
return ! ! ( block_queue [ j ] . mapped_registers_mask . mask & ( u64 { 1 } < < index ) ) ;
2025-01-31 16:15:51 +01:00
} ;
reg_state_t dummy_state { } ;
const auto get_reg = [ & ] ( usz index ) - > reg_state_t &
{
block_local_info_t * block = & block_queue [ j ] ;
const usz reg_mask = u64 { 1 } < < index ;
2025-02-23 18:04:14 +01:00
if ( ~ block - > moved_registers_mask . mask & reg_mask )
2025-01-31 16:15:51 +01:00
{
if ( ( j + 1 ) * 64 > = reg_state_storage . size ( ) )
{
dummy_state . value_range = 0 ;
dummy_state . tag = reg_tag_allocator + + ;
return dummy_state ;
}
usz begin_block = umax ;
// Try searching for register origin
2025-02-23 18:04:14 +01:00
if ( block - > mapped_registers_mask . mask & reg_mask )
2025-04-05 21:50:45 +02:00
{
for ( u32 i = block - > parent_block_idx ; i ! = umax ; i = block_queue [ i ] . parent_block_idx )
2025-01-31 16:15:51 +01:00
{
2025-02-23 18:04:14 +01:00
if ( ~ block_queue [ i ] . moved_registers_mask . mask & reg_mask )
2025-01-31 16:15:51 +01:00
{
continue ;
}
begin_block = i ;
break ;
}
}
// Initialize register or copy from origin
if ( begin_block ! = umax )
{
reg_state_storage [ 64 * j + index ] = reg_state_storage [ 64 * begin_block + index ] ;
}
else
{
reg_state_storage [ 64 * j + index ] = make_unknown_reg_state ( ) ;
}
2025-02-23 18:04:14 +01:00
block - > mapped_registers_mask . mask | = reg_mask ;
block - > moved_registers_mask . mask | = reg_mask ;
2025-01-31 16:15:51 +01:00
}
return reg_state_storage [ 64 * j + index ] ;
} ;
const auto store_block_reg = [ & ] ( u32 block_index , u32 index , const reg_state_t & rhs )
{
if ( ( block_index + 1 ) * 64 > = reg_state_storage . size ( ) )
{
return ;
}
reg_state_storage [ 64 * block_index + index ] = rhs ;
const usz reg_mask = u64 { 1 } < < index ;
2025-02-23 18:04:14 +01:00
block_queue [ block_index ] . mapped_registers_mask . mask | = reg_mask ;
block_queue [ block_index ] . moved_registers_mask . mask | = reg_mask ;
2025-01-31 16:15:51 +01:00
} ;
const auto unmap_reg = [ & ] ( u32 index )
{
block_local_info_t * block = & block_queue [ j ] ;
const usz reg_mask = u64 { 1 } < < index ;
2025-02-23 18:04:14 +01:00
block - > mapped_registers_mask . mask & = ~ reg_mask ;
block - > moved_registers_mask . mask & = ~ reg_mask ;
2025-01-31 16:15:51 +01:00
} ;
enum : u32
{
c_reg_lr = 32 ,
c_reg_ctr = 33 ,
c_reg_vrsave = 34 ,
c_reg_xer = 35 ,
c_reg_cr0_arg_rhs = 36 ,
c_reg_cr7_arg_rhs = c_reg_cr0_arg_rhs + 7 ,
c_reg_cr0_arg_lhs = 44 ,
c_reg_cr7_arg_lhs = c_reg_cr0_arg_lhs + 7 ,
} ;
2023-06-25 14:53:42 +02:00
for ( ; _ptr . addr ( ) < func_end ; )
2016-07-07 20:42:39 +02:00
{
const u32 iaddr = _ptr . addr ( ) ;
2023-06-25 14:53:42 +02:00
const ppu_opcode_t op { * advance ( _ptr , ptr , 1 ) } ;
2025-02-23 18:04:14 +01:00
const ppu_itype : : type type = g_ppu_itype . decode ( op . opcode ) ;
2016-07-07 20:42:39 +02:00
2025-01-31 16:15:51 +01:00
switch ( type )
{
case ppu_itype : : UNK :
2016-07-07 20:42:39 +02:00
{
// Invalid blocks will remain empty
break ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : B :
case ppu_itype : : BC :
2016-07-07 20:42:39 +02:00
{
2016-07-13 23:56:30 +02:00
const u32 target = ( op . aa ? 0 : iaddr ) + ( type = = ppu_itype : : B ? + op . bt24 : + op . bt14 ) ;
2016-07-09 00:36:42 +02:00
if ( target < start | | target > = end )
{
2020-02-01 09:31:27 +01:00
ppu_log . warning ( " [0x%x] Invalid branch at 0x%x -> 0x%x " , func . addr , iaddr , target ) ;
2016-07-09 00:36:42 +02:00
continue ;
}
2024-06-07 12:03:52 +02:00
if ( ! op . aa & & target = = _ptr . addr ( ) & & _ptr . addr ( ) < func_end )
{
2025-02-04 17:31:00 +01:00
( ! ! op . lk ? ppu_log . notice : ppu_log . trace ) ( " [0x%x] Branch to next at 0x%x -> 0x%x " , func . addr , iaddr , target ) ;
2024-06-07 12:03:52 +02:00
}
const bool is_call = op . lk & & target ! = iaddr & & target ! = _ptr . addr ( ) & & _ptr . addr ( ) < func_end ;
2017-04-08 22:58:00 +02:00
const auto pfunc = is_call ? & add_func ( target , 0 , 0 ) : nullptr ;
2016-07-07 20:42:39 +02:00
2025-02-23 18:04:14 +01:00
if ( pfunc & & pfunc - > blocks . empty ( ) & & ! is_function_caller_analysis )
2016-07-07 20:42:39 +02:00
{
// Postpone analysis (no info)
2025-02-08 16:00:07 +01:00
postpone_analysis = true ;
2016-07-07 20:42:39 +02:00
break ;
}
2018-02-09 15:49:37 +01:00
2025-02-23 18:04:14 +01:00
if ( is_function_caller_analysis & & is_call & & ! ( pfunc - > attr & ppu_attr : : no_return ) )
{
while ( is_function_caller_analysis )
{
// Verify that it is the call to the imported function (may be more than one)
const auto it = stub_addr_to_constant_state_of_registers . lower_bound ( target ) ;
if ( it = = stub_addr_to_constant_state_of_registers . end ( ) )
{
break ;
}
const auto next_func = fmap . upper_bound ( it - > first ) ;
if ( next_func = = fmap . begin ( ) )
{
break ;
}
const auto stub_func = std : : prev ( next_func ) ;
if ( stub_func - > first = = target )
{
// It is
// Now, mine register state
// Currently only of R3
if ( is_reg_mapped ( 3 ) )
{
const reg_state_t & value = get_reg ( 3 ) ;
if ( value ( is_const ) )
{
2025-04-05 21:50:45 +02:00
it - > second . emplace_back ( ppua_reg_mask_t { 1u < < 3 } , value ( minv ) ) ;
2025-02-23 18:04:14 +01:00
}
}
}
break ;
}
}
2016-07-07 20:42:39 +02:00
// Add next block if necessary
2018-09-02 19:22:35 +02:00
if ( ( is_call & & ! ( pfunc - > attr & ppu_attr : : no_return ) ) | | ( type = = ppu_itype : : BC & & ( op . bo & 0x14 ) ! = 0x14 ) )
2016-07-07 20:42:39 +02:00
{
2025-01-31 16:15:51 +01:00
const u32 next_idx = add_block ( _ptr . addr ( ) , j ) ;
if ( next_idx ! = umax & & target ! = iaddr + 4 & & type = = ppu_itype : : BC & & ( op . bo & 0b10100 ) = = 0b00100 )
{
bool lt { } ;
bool gt { } ;
bool eq { } ;
bool so { } ;
bool nso { } ;
// Because this is the following instruction, reverse the flags
if ( ( op . bo & 0b01000 ) ! = 0 )
{
switch ( op . bi % 4 )
{
2025-04-05 21:50:45 +02:00
case 0x0 :
gt = true ;
eq = true ;
break ;
case 0x1 :
lt = true ;
eq = true ;
break ;
case 0x2 :
gt = true ;
lt = true ;
break ;
2025-01-31 16:15:51 +01:00
case 0x3 : nso = true ; break ;
default : fmt : : throw_exception ( " Unreachable " ) ;
}
}
else
{
switch ( op . bi % 4 )
{
case 0x0 : lt = true ; break ;
case 0x1 : gt = true ; break ;
case 0x2 : eq = true ; break ;
case 0x3 : so = true ; break ;
default : fmt : : throw_exception ( " Unreachable " ) ;
}
}
const u32 rhs_cr_state = c_reg_cr0_arg_rhs + op . bi / 4 ;
const u32 lhs_cr_state = c_reg_cr0_arg_lhs + op . bi / 4 ;
// Complete the block information based on the data that the jump is NOT taken
// This information would be inherited to next block
auto lhs_state = get_reg ( lhs_cr_state ) ;
auto rhs_state = get_reg ( rhs_cr_state ) ;
lhs_state . declare_ordering ( rhs_state , lt , eq , gt , so , nso ) ;
store_block_reg ( next_idx , lhs_cr_state , lhs_state ) ;
store_block_reg ( next_idx , rhs_cr_state , rhs_state ) ;
2025-02-23 18:04:14 +01:00
const u64 reg_mask = block_queue [ j ] . mapped_registers_mask . mask ;
2025-01-31 16:15:51 +01:00
for ( u32 bit = std : : countr_zero ( reg_mask ) ; bit < 64 & & reg_mask & ( u64 { 1 } < < bit ) ;
bit + = 1 , bit = std : : countr_zero ( reg_mask > > ( bit % 64 ) ) + bit )
{
if ( bit = = lhs_cr_state | | bit = = rhs_cr_state )
{
continue ;
}
auto & reg_state = get_reg ( bit ) ;
if ( reg_state . is_equals ( lhs_state ) )
{
store_block_reg ( next_idx , bit , lhs_state ) ;
}
else if ( reg_state . is_equals ( rhs_state ) )
{
store_block_reg ( next_idx , bit , rhs_state ) ;
}
}
}
2016-07-07 20:42:39 +02:00
}
2018-09-02 19:22:35 +02:00
if ( is_call & & pfunc - > attr & ppu_attr : : no_return )
2016-07-07 20:42:39 +02:00
{
// Nothing
}
2016-07-13 01:54:34 +02:00
else if ( is_call | | target < func . addr | | target > = func_end )
2016-07-07 20:42:39 +02:00
{
// Add function call (including obvious tail call)
2025-02-23 18:04:14 +01:00
add_func ( target , 0 , func . addr ) ;
2016-07-07 20:42:39 +02:00
}
else
{
// Add block
2025-01-31 16:15:51 +01:00
add_block ( target , umax ) ;
2016-07-07 20:42:39 +02:00
}
2025-01-31 16:15:51 +01:00
block_queue [ j ] . size = _ptr . addr ( ) - block_addr ;
2016-07-07 20:42:39 +02:00
break ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : BCLR :
2016-07-07 20:42:39 +02:00
{
if ( op . lk | | ( op . bo & 0x14 ) ! = 0x14 )
{
2025-01-31 16:15:51 +01:00
add_block ( _ptr . addr ( ) , j ) ;
2016-07-07 20:42:39 +02:00
}
2025-01-31 16:15:51 +01:00
block_queue [ j ] . size = _ptr . addr ( ) - block_addr ;
2016-07-07 20:42:39 +02:00
break ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : BCCTR :
2016-07-07 20:42:39 +02:00
{
if ( op . lk | | ( op . bo & 0x10 ) ! = 0x10 )
{
2025-01-31 16:15:51 +01:00
add_block ( _ptr . addr ( ) , j ) ;
}
else if ( get_reg ( c_reg_ctr ) ( is_const ) )
{
ppu_log . todo ( " [0x%x] BCTR to constant destination at 0x%x: 0x%x " , func . addr , iaddr , get_reg ( c_reg_ctr ) ( minv ) ) ;
2016-07-07 20:42:39 +02:00
}
else
{
// Analyse jumptable (TODO)
2025-01-31 16:15:51 +01:00
u32 jt_addr = _ptr . addr ( ) ;
u32 jt_end = func_end ;
const auto code_end = get_ptr < u32 > ( func_end ) ;
2016-07-07 20:42:39 +02:00
2025-01-31 16:15:51 +01:00
auto get_jumptable_end = [ & ] ( vm : : cptr < u32 > & _ptr , be_t < u32 > * & ptr , bool is_relative )
2016-07-07 20:42:39 +02:00
{
2025-01-31 16:15:51 +01:00
for ( ; _ptr . addr ( ) < jt_end ; advance ( _ptr , ptr , 1 ) )
2016-07-07 20:42:39 +02:00
{
2025-01-31 16:15:51 +01:00
const u32 addr = ( is_relative ? jt_addr : 0 ) + * ptr ;
if ( addr = = jt_addr )
{
// TODO (cannot branch to jumptable itself)
return ;
}
2025-04-05 21:50:45 +02:00
if ( addr % 4 | | addr < func . addr | | addr > = func_end | | ! is_valid_code ( { get_ptr < u32 > ( addr ) , code_end } , ! is_relocatable , addr ) )
2025-01-31 16:15:51 +01:00
{
return ;
}
add_block ( addr , j ) ;
2016-07-07 20:42:39 +02:00
}
2025-01-31 16:15:51 +01:00
} ;
get_jumptable_end ( _ptr , ptr , true ) ;
bool found_jt = jt_addr = = jt_end | | _ptr . addr ( ) ! = jt_addr ;
if ( ! found_jt & & is_reg_mapped ( c_reg_ctr ) )
{
// Fallback: try to extract jumptable address from registers
const reg_state_t ctr = get_reg ( c_reg_ctr ) ;
2016-07-07 20:42:39 +02:00
2025-01-31 16:15:51 +01:00
if ( ctr . is_loaded )
2016-07-07 20:42:39 +02:00
{
2025-01-31 16:15:51 +01:00
vm : : cptr < u32 > jumpatble_off = vm : : cast ( static_cast < u32 > ( ctr ( load_addrv ) ) ) ;
if ( be_t < u32 > * jumpatble_ptr_begin = get_ptr < u32 > ( jumpatble_off ) )
{
be_t < u32 > * jumpatble_ptr = jumpatble_ptr_begin ;
jt_addr = jumpatble_off . addr ( ) ;
2025-02-08 16:00:07 +01:00
jt_end = segs_end ;
2025-01-31 16:15:51 +01:00
for ( const auto & seg : segs )
{
if ( seg . size )
{
if ( jt_addr < seg . addr + seg . size & & jt_addr > = seg . addr )
{
jt_end = seg . addr + seg . size ;
break ;
}
}
}
2025-10-05 18:28:03 +02:00
jt_end = rx : : alignUp < u32 > ( static_cast < u32 > ( std : : min < u64 > ( jt_end - 1 , ctr ( maxv ) - 1 ) + 1 ) , 4 ) ;
2025-01-31 16:15:51 +01:00
get_jumptable_end ( jumpatble_off , jumpatble_ptr , false ) ;
// If we do not have jump-table bounds, try manually extract reference address (last resort: may be inaccurate in theory)
if ( jumpatble_ptr = = jumpatble_ptr_begin & & addr_heap . contains ( _ptr . addr ( ) ) )
{
// See who is referencing it
const u32 ref_addr = addr_heap . at ( _ptr . addr ( ) ) ;
if ( ref_addr > jt_addr & & ref_addr < jt_end )
{
ppu_log . todo ( " [0x%x] Jump table not found through GPR search, retrying using addr_heap. (iaddr=0x%x, ref_addr=0x%x) " , func . addr , iaddr , ref_addr ) ;
jumpatble_off = vm : : cast ( ref_addr ) ;
jumpatble_ptr_begin = get_ptr < u32 > ( jumpatble_off ) ;
jumpatble_ptr = jumpatble_ptr_begin ;
jt_addr = ref_addr ;
get_jumptable_end ( jumpatble_off , jumpatble_ptr , false ) ;
}
}
if ( jumpatble_ptr ! = jumpatble_ptr_begin )
{
found_jt = true ;
2025-04-05 21:50:45 +02:00
for ( be_t < u32 > addr : std : : span < const be_t < u32 > > { jumpatble_ptr_begin , jumpatble_ptr } )
2025-01-31 16:15:51 +01:00
{
if ( addr = = _ptr . addr ( ) )
{
ppu_log . success ( " [0x%x] Found address of next code in jump table at 0x%x! 0x%x-0x%x " , func . addr , iaddr , jt_addr , jt_addr + 4 * ( jumpatble_ptr - jumpatble_ptr_begin ) ) ;
break ;
}
}
}
}
2016-07-07 20:42:39 +02:00
}
}
2025-01-31 16:15:51 +01:00
if ( ! found_jt )
2016-07-07 20:42:39 +02:00
{
// Acknowledge jumptable detection failure
2018-09-02 19:22:35 +02:00
if ( ! ( func . attr & ppu_attr : : no_size ) )
2016-07-13 23:56:30 +02:00
{
2020-02-01 09:31:27 +01:00
ppu_log . warning ( " [0x%x] Jump table not found! 0x%x-0x%x " , func . addr , jt_addr , jt_end ) ;
2016-07-13 23:56:30 +02:00
}
2016-08-07 21:01:27 +02:00
func . attr + = ppu_attr : : no_size ;
2025-01-31 16:15:51 +01:00
add_block ( jt_addr , j ) ;
2025-02-08 16:00:07 +01:00
postpone_analysis = true ;
2016-07-07 20:42:39 +02:00
}
2016-07-13 23:56:30 +02:00
else
{
2020-02-01 09:31:27 +01:00
ppu_log . trace ( " [0x%x] Jump table found: 0x%x-0x%x " , func . addr , jt_addr , _ptr ) ;
2016-07-13 23:56:30 +02:00
}
2016-07-07 20:42:39 +02:00
}
2025-01-31 16:15:51 +01:00
block_queue [ j ] . size = _ptr . addr ( ) - block_addr ;
2016-07-07 20:42:39 +02:00
break ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : TD :
case ppu_itype : : TW :
case ppu_itype : : TDI :
case ppu_itype : : TWI :
2017-02-12 19:12:08 +01:00
{
2025-01-31 16:15:51 +01:00
if ( ! op . bo )
{
// No-op
continue ;
}
2023-09-04 18:31:27 +02:00
if ( can_trap_continue ( op , type ) )
2017-02-22 22:35:29 +01:00
{
2025-01-31 16:15:51 +01:00
add_block ( _ptr . addr ( ) , j ) ;
2017-02-22 22:35:29 +01:00
}
2025-01-31 16:15:51 +01:00
block_queue [ j ] . size = _ptr . addr ( ) - block_addr ;
2017-02-12 19:12:08 +01:00
break ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : SC :
2017-06-22 23:52:09 +02:00
{
2025-01-31 16:15:51 +01:00
add_block ( _ptr . addr ( ) , j ) ;
block_queue [ j ] . size = _ptr . addr ( ) - block_addr ;
2017-06-22 23:52:09 +02:00
break ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : STDU :
2017-06-22 23:52:09 +02:00
{
2025-01-31 16:15:51 +01:00
if ( func . attr & ppu_attr : : no_size & & ( op . opcode = = * ptr | | * ptr = = ppu_instructions : : BLR ( ) ) )
{
// Hack
ppu_log . success ( " [0x%x] Instruction repetition: 0x%08x " , iaddr , op . opcode ) ;
add_block ( _ptr . addr ( ) , j ) ;
block_queue [ j ] . size = _ptr . addr ( ) - block_addr ;
}
continue ;
}
case ppu_itype : : ADDI :
case ppu_itype : : ADDIC :
case ppu_itype : : ADDIS :
{
const s64 to_add = type = = ppu_itype : : ADDIS ? op . simm16 * 65536 : op . simm16 ;
const reg_state_t ra = type = = ppu_itype : : ADDIC | | op . ra ? get_reg ( op . ra ) : s_reg_const_0 ;
if ( ra ( is_const ) )
{
reg_state_t rd { } ;
rd . load_const ( ra ( minv ) + to_add ) ;
store_block_reg ( j , op . rd , rd ) ;
}
else
{
unmap_reg ( op . rd ) ;
}
continue ;
}
case ppu_itype : : ORI :
case ppu_itype : : ORIS :
{
const u64 to_or = type = = ppu_itype : : ORIS ? op . uimm16 * 65536 : op . uimm16 ;
const reg_state_t rs = get_reg ( op . rs ) ;
if ( ! to_or & & is_reg_mapped ( op . rs ) )
{
store_block_reg ( j , op . ra , get_reg ( op . rs ) ) ;
}
else if ( rs ( is_const ) )
{
reg_state_t ra { } ;
ra . load_const ( rs ( minv ) | to_or ) ;
store_block_reg ( j , op . ra , ra ) ;
}
else
{
unmap_reg ( op . ra ) ;
}
continue ;
}
case ppu_itype : : MTSPR :
{
switch ( const u32 n = ( op . spr > > 5 ) | ( ( op . spr & 0x1f ) < < 5 ) )
{
case 0x001 : // MTXER
{
break ;
}
case 0x008 : // MTLR
{
2025-04-05 21:50:45 +02:00
// store_block_reg(j, c_reg_lr, get_reg(op.rs));
2025-01-31 16:15:51 +01:00
break ;
}
case 0x009 : // MTCTR
{
store_block_reg ( j , c_reg_ctr , get_reg ( op . rs ) ) ;
break ;
}
case 0x100 :
{
// get_reg(c_reg_vrsave) = get_reg(op.rs);
// get_reg(c_reg_vrsave).value &= u32{umax};
break ;
}
default :
{
break ;
}
}
continue ;
}
2025-02-23 18:04:14 +01:00
case ppu_itype : : LWZ :
{
const bool is_load_from_toc = ( is_function_caller_analysis & & op . ra = = 2u & & func . toc & & func . toc ! = umax ) ;
if ( is_load_from_toc | | is_reg_mapped ( op . rd ) | | is_reg_mapped ( op . ra ) )
{
const reg_state_t ra = get_reg ( op . ra ) ;
auto & rd = get_reg ( op . rd ) ;
rd = { } ;
rd . tag = reg_tag_allocator + + ;
rd . is_loaded = true ;
reg_state_t const_offs { } ;
const_offs . load_const ( op . simm16 ) ;
reg_state_t toc_offset { } ;
toc_offset . load_const ( func . toc ) ;
const reg_state_t & off_ra = is_load_from_toc ? toc_offset : ra ;
rd . ge_than = const_offs ( minv ) ;
const bool is_negative = const_offs ( sign_bitv ) = = 1u ;
2025-04-05 21:50:45 +02:00
const bool is_offset_test_ok = is_negative ? ( 0 - const_offs ( minv ) < = off_ra ( minv ) & & off_ra ( minv ) + const_offs ( minv ) < segs_end ) : ( off_ra ( minv ) < segs_end & & const_offs ( minv ) < segs_end - off_ra ( minv ) ) ;
2025-02-23 18:04:14 +01:00
if ( off_ra ( minv ) < off_ra ( maxv ) & & is_offset_test_ok )
{
rd . ge_than + = off_ra ( minv ) ;
2025-04-05 21:50:45 +02:00
const bool is_range_end_test_ok = is_negative ? ( off_ra ( maxv ) + const_offs ( minv ) < = segs_end ) : ( off_ra ( maxv ) - 1 < segs_end - 1 & & const_offs ( minv ) < = segs_end - off_ra ( maxv ) ) ;
2025-02-23 18:04:14 +01:00
if ( is_range_end_test_ok )
{
rd . value_range = off_ra . value_range ;
}
}
if ( is_load_from_toc )
{
if ( rd . value_range = = 1 )
{
// Try to load a constant value from data segment
if ( auto val_ptr = get_ptr < u32 > ( static_cast < u32 > ( rd . ge_than ) ) )
{
rd = { } ;
rd . load_const ( * val_ptr ) ;
}
}
}
}
continue ;
}
2025-01-31 16:15:51 +01:00
case ppu_itype : : LWZX :
case ppu_itype : : LDX : // TODO: Confirm if LDX can appear in jumptable branching (probably in LV1 applications such as ps2_emu)
{
if ( is_reg_mapped ( op . ra ) | | is_reg_mapped ( op . rb ) )
{
const reg_state_t ra = get_reg ( op . ra ) ;
const reg_state_t rb = get_reg ( op . rb ) ;
const bool is_ra = ra ( is_const ) & & ( ra ( minv ) > = start & & ra ( minv ) < segs_end ) ;
const bool is_rb = rb ( is_const ) & & ( rb ( minv ) > = start & & rb ( minv ) < segs_end ) ;
if ( ra ( is_const ) = = rb ( is_const ) )
{
unmap_reg ( op . rd ) ;
}
else
{
// Register possible jumptable offset
auto & rd = get_reg ( op . rd ) ;
rd = { } ;
2025-02-23 18:04:14 +01:00
rd . tag = reg_tag_allocator + + ;
2025-01-31 16:15:51 +01:00
rd . is_loaded = true ;
const reg_state_t & const_reg = is_ra ? ra : rb ;
const reg_state_t & off_reg = is_ra ? rb : ra ;
rd . ge_than = const_reg ( minv ) ;
2025-02-08 16:00:07 +01:00
if ( off_reg . value_range ! = 0 & & off_reg ( minv ) < segs_end & & const_reg ( minv ) < segs_end - off_reg ( minv ) )
2025-01-31 16:15:51 +01:00
{
rd . ge_than + = off_reg ( minv ) ;
2025-02-08 16:00:07 +01:00
if ( off_reg ( maxv ) - 1 < segs_end - 1 & & const_reg ( minv ) < = segs_end - off_reg ( maxv ) )
{
rd . value_range = off_reg . value_range ;
}
2025-01-31 16:15:51 +01:00
}
}
}
continue ;
}
case ppu_itype : : RLWINM :
{
2025-04-05 21:50:45 +02:00
// const u64 mask = ppu_rotate_mask(32 + op.mb32, 32 + op.me32);
2025-01-31 16:15:51 +01:00
if ( ! is_reg_mapped ( op . rs ) & & ! is_reg_mapped ( op . ra ) )
{
continue ;
}
if ( op . mb32 < = op . me32 & & op . sh32 = = 31 - op . me32 )
{
// SLWI mnemonic (mb32 == 0) or generic form
reg_state_t rs = get_reg ( op . rs ) ;
if ( ! rs . shift_left ( op . sh32 , reg_tag_allocator ) | | ! rs . clear_higher_bits ( 32 + op . mb32 , reg_tag_allocator ) )
{
unmap_reg ( op . ra ) ;
}
else
{
store_block_reg ( j , op . ra , rs ) ;
}
}
else
{
unmap_reg ( op . ra ) ;
}
continue ;
}
case ppu_itype : : RLDICR :
{
const u32 sh = op . sh64 ;
const u32 me = op . mbe64 ;
2025-04-05 21:50:45 +02:00
// const u64 mask = ~0ull << (63 - me);
2025-01-31 16:15:51 +01:00
if ( sh = = 63 - me )
{
// SLDI mnemonic
reg_state_t rs = get_reg ( op . rs ) ;
if ( ! rs . shift_left ( op . sh32 , reg_tag_allocator ) )
{
unmap_reg ( op . ra ) ;
}
else
{
store_block_reg ( j , op . ra , rs ) ;
}
}
else
{
unmap_reg ( op . ra ) ;
}
continue ;
}
case ppu_itype : : CMPLI :
case ppu_itype : : CMPI :
case ppu_itype : : CMP :
case ppu_itype : : CMPL :
{
const bool is_wbits = op . l10 = = 0 ;
const bool is_signed = type = = ppu_itype : : CMPI | | type = = ppu_itype : : CMP ;
const bool is_rhs_register = type = = ppu_itype : : CMP | | type = = ppu_itype : : CMPL ;
reg_state_t rhs { } ;
reg_state_t lhs { } ;
lhs = get_reg ( op . ra ) ;
if ( is_rhs_register )
{
rhs = get_reg ( op . rb ) ;
}
else
{
const u64 compared = is_signed ? op . simm16 : op . uimm16 ;
rhs . load_const ( is_wbits ? ( compared & u32 { umax } ) : compared ) ;
}
if ( is_wbits )
{
lhs . undef_higher_bits ( 32 ) ;
rhs . undef_higher_bits ( 32 ) ;
}
if ( is_signed )
{
// Force the comparison to become unsigned
// const u64 sign_bit = u64{1} << (is_wbits ? 31 : 63);
// lhs.add(sign_bit);
// rhs.add(sign_bit);
}
const u32 cr_idx = op . crfd ;
store_block_reg ( j , c_reg_cr0_arg_rhs + cr_idx , rhs ) ;
store_block_reg ( j , c_reg_cr0_arg_lhs + cr_idx , lhs ) ;
continue ;
2017-06-22 23:52:09 +02:00
}
2025-01-31 16:15:51 +01:00
default :
{
if ( type & ppu_itype : : store )
{
continue ;
}
// TODO: Tell if instruction modified RD or RA
unmap_reg ( op . rd ) ;
unmap_reg ( op . ra ) ;
continue ;
}
}
break ;
2016-07-07 20:42:39 +02:00
}
}
2025-02-23 18:04:14 +01:00
if ( ! preserve_blocks . empty ( ) )
{
ensure ( func . blocks . size ( ) = = preserve_blocks . size ( ) ) ;
for ( auto fit = func . blocks . begin ( ) , pit = preserve_blocks . begin ( ) ; fit ! = func . blocks . end ( ) ; fit + + , pit + + )
{
// Ensure block addresses match
ensure ( fit - > first = = pit - > first ) ;
}
func . blocks = std : : move ( preserve_blocks ) ;
}
2025-02-08 16:00:07 +01:00
if ( postpone_analysis )
2016-07-07 20:42:39 +02:00
{
// Block aborted: abort function, postpone
func_queue . emplace_back ( func ) ;
continue ;
}
2018-02-09 15:49:37 +01:00
2016-07-07 20:42:39 +02:00
// Finalization: determine function size
2018-09-02 19:22:35 +02:00
if ( ! ( func . attr & ppu_attr : : known_size ) )
2016-07-07 20:42:39 +02:00
{
2016-07-13 23:56:30 +02:00
const auto last = func . blocks . crbegin ( ) ;
2016-07-07 20:42:39 +02:00
2016-07-13 23:56:30 +02:00
if ( last ! = func . blocks . crend ( ) )
2016-07-07 20:42:39 +02:00
{
2016-07-13 23:56:30 +02:00
func . size = last - > first + last - > second - func . addr ;
2016-07-07 20:42:39 +02:00
}
}
// Finalization: normalize blocks
for ( auto & block : func . blocks )
{
const auto next = func . blocks . upper_bound ( block . first ) ;
// Normalize block if necessary
2017-02-25 19:24:36 +01:00
if ( next ! = func . blocks . end ( ) & & block . second > next - > first - block . first )
2016-07-07 20:42:39 +02:00
{
block . second = next - > first - block . first ;
}
// Invalidate blocks out of the function
const u32 fend = func . addr + func . size ;
const u32 bend = block . first + block . second ;
if ( block . first > = fend )
{
block . second = 0 ;
}
else if ( bend > fend )
{
block . second - = bend - fend ;
}
}
// Finalization: process remaining tail calls
for ( const auto & block : func . blocks )
{
for ( vm : : cptr < u32 > _ptr = vm : : cast ( block . first ) ; _ptr . addr ( ) < block . first + block . second ; )
{
const u32 iaddr = _ptr . addr ( ) ;
2023-07-22 11:03:45 +02:00
const ppu_opcode_t op { get_ref < u32 > ( _ptr + + ) } ;
2025-02-23 18:04:14 +01:00
const ppu_itype : : type type = g_ppu_itype . decode ( op . opcode ) ;
2016-07-07 20:42:39 +02:00
if ( type = = ppu_itype : : B | | type = = ppu_itype : : BC )
{
2016-07-13 23:56:30 +02:00
const u32 target = ( op . aa ? 0 : iaddr ) + ( type = = ppu_itype : : B ? + op . bt24 : + op . bt14 ) ;
2016-07-07 20:42:39 +02:00
2025-01-18 13:47:12 +01:00
if ( target > = start & & target < end & & ( ! op . aa | | verify_ref ( iaddr ) ) )
2016-07-07 20:42:39 +02:00
{
2016-07-09 00:36:42 +02:00
if ( target < func . addr | | target > = func . addr + func . size )
{
2017-04-08 22:58:00 +02:00
add_func ( target , func . toc ? func . toc + func . trampoline : 0 , func . addr ) ;
2016-07-09 00:36:42 +02:00
}
2016-07-07 20:42:39 +02:00
}
}
else if ( type = = ppu_itype : : BCCTR & & ! op . lk )
{
// Jumptable (do not touch entries)
break ;
}
}
}
2016-07-15 11:52:37 +02:00
// Finalization: decrease known function size (TODO)
2018-09-02 19:22:35 +02:00
if ( func . attr & ppu_attr : : known_size )
2016-07-15 11:52:37 +02:00
{
const auto last = func . blocks . crbegin ( ) ;
if ( last ! = func . blocks . crend ( ) )
{
func . size = std : : min < u32 > ( func . size , last - > first + last - > second - func . addr ) ;
}
}
2016-07-07 20:42:39 +02:00
}
2016-07-13 01:54:34 +02:00
// Function shrinkage, disabled (TODO: it's potentially dangerous but improvable)
2017-07-01 01:08:51 +02:00
for ( auto & _pair : fmap )
2016-07-07 20:42:39 +02:00
{
auto & func = _pair . second ;
2016-07-13 01:54:34 +02:00
// Get next function addr
2017-07-01 01:08:51 +02:00
const auto _next = fmap . lower_bound ( _pair . first + 1 ) ;
2016-07-13 01:54:34 +02:00
2017-07-01 01:08:51 +02:00
const u32 next = _next = = fmap . end ( ) ? end : _next - > first ;
2016-07-07 20:42:39 +02:00
// Just ensure that functions don't overlap
if ( func . addr + func . size > next )
{
2021-01-19 18:40:15 +01:00
ppu_log . trace ( " Function overlap: [0x%x] 0x%x -> 0x%x " , func . addr , func . size , next - func . addr ) ;
2025-04-05 21:50:45 +02:00
continue ; // func.size = next - func.addr;
2016-07-07 20:42:39 +02:00
// Also invalidate blocks
for ( auto & block : func . blocks )
{
if ( block . first + block . second > next )
{
block . second = block . first > = next ? 0 : next - block . first ;
}
}
}
2017-02-12 19:12:08 +01:00
// Suspicious block start
u32 start = func . addr + func . size ;
if ( next = = end )
{
continue ;
}
// Analyse gaps between functions
for ( vm : : cptr < u32 > _ptr = vm : : cast ( start ) ; _ptr . addr ( ) < next ; )
{
const u32 addr = _ptr . addr ( ) ;
2023-07-22 11:03:45 +02:00
const ppu_opcode_t op { get_ref < u32 > ( _ptr + + ) } ;
2025-02-23 18:04:14 +01:00
const ppu_itype : : type type = g_ppu_itype . decode ( op . opcode ) ;
2017-02-12 19:12:08 +01:00
2022-08-17 15:53:05 +02:00
if ( type = = ppu_itype : : UNK )
2017-02-12 19:12:08 +01:00
{
break ;
}
2023-06-12 03:48:27 +02:00
if ( addr = = start & & op . opcode = = ppu_instructions : : NOP ( ) )
2017-02-12 19:12:08 +01:00
{
if ( start = = func . addr + func . size )
{
// Extend function with tail NOPs (hack)
func . size + = 4 ;
}
start + = 4 ;
continue ;
}
2023-06-12 03:48:27 +02:00
if ( type = = ppu_itype : : SC & & op . opcode ! = ppu_instructions : : SC ( 0 ) )
2017-02-12 19:12:08 +01:00
{
break ;
}
2023-06-12 03:48:27 +02:00
if ( addr = = start & & op . opcode = = ppu_instructions : : BLR ( ) )
2017-02-12 19:12:08 +01:00
{
start + = 4 ;
continue ;
}
2023-06-12 03:48:27 +02:00
if ( type = = ppu_itype : : B | | type = = ppu_itype : : BC )
2017-02-12 19:12:08 +01:00
{
const u32 target = ( op . aa ? 0 : addr ) + ( type = = ppu_itype : : B ? + op . bt24 : + op . bt14 ) ;
if ( target = = addr )
{
break ;
}
_ptr . set ( next ) ;
}
else if ( type = = ppu_itype : : BCLR | | type = = ppu_itype : : BCCTR )
{
_ptr . set ( next ) ;
}
2018-02-09 15:49:37 +01:00
2017-02-12 19:12:08 +01:00
if ( _ptr . addr ( ) > = next )
{
2021-01-19 18:40:15 +01:00
ppu_log . trace ( " Function gap: [0x%x] 0x%x bytes at 0x%x " , func . addr , next - start , start ) ;
2017-02-12 19:12:08 +01:00
break ;
}
}
2016-07-07 20:42:39 +02:00
}
2017-04-08 22:58:00 +02:00
// Fill TOCs for trivial case
if ( TOCs . size ( ) = = 1 )
{
lib_toc = * TOCs . begin ( ) ;
2017-07-01 01:08:51 +02:00
for ( auto & & pair : fmap )
2017-04-08 22:58:00 +02:00
{
if ( pair . second . toc = = 0 )
{
pair . second . toc = lib_toc ;
}
}
}
2018-02-09 15:49:37 +01:00
2024-04-18 14:42:11 +02:00
( fmap . empty ( ) ? ppu_log . error : ppu_log . notice ) ( " Function analysis: %zu functions (%zu enqueued) " , fmap . size ( ) , func_queue . size ( ) ) ;
2021-01-19 18:40:15 +01:00
// Decompose functions to basic blocks
2021-02-02 17:54:43 +01:00
if ( ! entry & & ! sec_end )
{
// Regenerate end from blocks
end = 0 ;
}
2023-08-24 22:03:51 +02:00
u32 per_instruction_bytes = 0 ;
2025-01-30 15:00:18 +01:00
// Iterate by address (fmap may grow)
2025-02-04 17:31:00 +01:00
for ( u32 addr_next = start ; addr_next ! = umax ; )
2021-01-19 18:40:15 +01:00
{
2025-01-30 15:00:18 +01:00
// Get next iterator
const auto it = fmap . lower_bound ( addr_next ) ;
if ( it = = fmap . end ( ) )
{
break ;
}
// Save next function address as is as of this moment (ignoring added functions)
const auto it_next = std : : next ( it ) ;
2025-02-04 17:31:00 +01:00
addr_next = it_next = = fmap . end ( ) ? u32 { umax } : it_next - > first ;
2025-01-30 15:00:18 +01:00
const ppu_function_ext & func = it - > second ;
2025-01-31 16:15:51 +01:00
if ( func . attr & ppu_attr : : no_size & & ! is_relocatable )
2021-01-27 09:50:51 +01:00
{
// Disabled for PRX for now
const u32 lim = get_limit ( func . addr ) ;
ppu_log . warning ( " Function 0x%x will be compiled on per-instruction basis (next=0x%x) " , func . addr , lim ) ;
for ( u32 addr = func . addr ; addr < lim ; addr + = 4 )
{
auto & block = fmap [ addr ] ;
block . addr = addr ;
block . size = 4 ;
2025-04-05 21:50:45 +02:00
block . toc = func . toc ;
2021-01-27 09:50:51 +01:00
block . attr = ppu_attr : : no_size ;
}
2025-10-05 18:28:03 +02:00
per_instruction_bytes + = rx : : sub_saturate < u32 > ( lim , func . addr ) ;
2025-01-30 15:00:18 +01:00
addr_next = std : : max < u32 > ( addr_next , lim ) ;
2021-01-27 09:50:51 +01:00
continue ;
}
2023-06-12 03:48:27 +02:00
for ( const auto & [ addr , size ] : func . blocks )
2021-01-19 18:40:15 +01:00
{
if ( ! size )
{
continue ;
}
auto & block = fmap [ addr ] ;
if ( block . addr | | block . size )
{
ppu_log . trace ( " Block __0x%x exists (size=0x%x) " , block . addr , block . size ) ;
continue ;
}
block . addr = addr ;
block . size = size ;
2025-04-05 21:50:45 +02:00
block . toc = func . toc ;
2025-01-30 15:00:18 +01:00
ppu_log . trace ( " Block __0x%x added (func=0x%x, size=0x%x, toc=0x%x) " , block . addr , it - > first , block . size , block . toc ) ;
2021-01-28 19:32:26 +01:00
2021-02-02 17:54:43 +01:00
if ( ! entry & & ! sec_end )
2021-01-28 19:32:26 +01:00
{
// Workaround for SPRX: update end to the last found function
2021-02-02 17:54:43 +01:00
end = std : : max < u32 > ( end , block . addr + block . size ) ;
2021-01-28 19:32:26 +01:00
}
2021-01-19 18:40:15 +01:00
}
}
// Simple callable block analysis
std : : vector < std : : pair < u32 , u32 > > block_queue ;
block_queue . reserve ( 128000 ) ;
std : : unordered_set < u32 > block_set ;
2021-01-25 14:34:54 +01:00
// Check relocations which may involve block addresses (usually it's type 1)
for ( auto & rel : this - > relocs )
{
// Disabled (TODO)
2025-04-05 21:50:45 +02:00
// if (!vm::check_addr<4>(rel.addr))
2021-01-25 14:34:54 +01:00
{
continue ;
}
2023-07-22 11:03:45 +02:00
const u32 target = get_ref < u32 > ( rel . addr ) ;
2021-01-25 14:34:54 +01:00
if ( target % 4 | | target < start | | target > = end )
{
continue ;
}
switch ( rel . type )
{
case 1 :
case 24 :
case 26 :
case 27 :
case 28 :
case 107 :
case 108 :
case 109 :
case 110 :
{
2021-01-27 09:50:51 +01:00
ppu_log . trace ( " Added block from reloc: 0x%x (0x%x, %u) (heap=%d) " , target , rel . addr , rel . type , addr_heap . count ( target ) ) ;
2021-01-25 14:34:54 +01:00
block_queue . emplace_back ( target , 0 ) ;
block_set . emplace ( target ) ;
continue ;
}
default :
{
continue ;
}
}
}
2021-01-19 18:40:15 +01:00
u32 exp = start ;
u32 lim = end ;
2023-08-24 22:03:51 +02:00
// Start with full scan
block_queue . emplace_back ( exp , lim ) ;
2021-01-19 18:40:15 +01:00
2021-02-02 19:14:35 +01:00
// Add entries from patches (on per-instruction basis)
for ( u32 addr : applied )
{
if ( addr % 4 = = 0 & & addr > = start & & addr < segs [ 0 ] . addr + segs [ 0 ] . size & & ! block_set . count ( addr ) )
{
block_queue . emplace_back ( addr , addr + 4 ) ;
block_set . emplace ( addr ) ;
}
}
2021-01-19 18:40:15 +01:00
// block_queue may grow
for ( usz i = 0 ; i < block_queue . size ( ) ; i + + )
{
std : : tie ( exp , lim ) = block_queue [ i ] ;
if ( lim = = 0 )
{
// Find next function
const auto found = fmap . upper_bound ( exp ) ;
if ( found ! = fmap . cend ( ) )
{
lim = found - > first ;
}
ppu_log . trace ( " Block rescan: addr=0x%x, lim=0x%x " , exp , lim ) ;
}
while ( exp < lim )
{
u32 i_pos = exp ;
2023-08-24 22:03:51 +02:00
u32 block_edges [ 16 ] ;
u32 edge_count = 0 ;
2021-01-19 18:40:15 +01:00
bool is_good = true ;
2021-02-06 17:33:17 +01:00
bool is_fallback = true ;
2021-01-19 18:40:15 +01:00
for ( ; i_pos < lim ; i_pos + = 4 )
{
2023-08-24 22:03:51 +02:00
const ppu_opcode_t op { get_ref < u32 > ( i_pos ) } ;
2021-01-19 18:40:15 +01:00
2025-02-23 18:04:14 +01:00
switch ( auto type = g_ppu_itype . decode ( op . opcode ) )
2021-01-19 18:40:15 +01:00
{
case ppu_itype : : UNK :
case ppu_itype : : ECIWX :
case ppu_itype : : ECOWX :
{
// Seemingly bad instruction, skip this block
is_good = false ;
break ;
}
case ppu_itype : : TDI :
case ppu_itype : : TWI :
2023-08-24 22:03:51 +02:00
{
2023-09-04 18:31:27 +02:00
if ( op . bo & & ( op . ra = = 1u | | op . ra = = 13u | | op . ra = = 2u ) )
2023-08-24 22:03:51 +02:00
{
// Non-user registers, checking them against a constant value makes no sense
is_good = false ;
break ;
}
[[fallthrough]] ;
}
case ppu_itype : : TD :
case ppu_itype : : TW :
2023-09-04 18:31:27 +02:00
{
if ( ! op . bo )
{
continue ;
}
if ( ! can_trap_continue ( op , type ) )
{
is_fallback = false ;
}
[[fallthrough]] ;
}
2021-01-19 18:40:15 +01:00
case ppu_itype : : B :
case ppu_itype : : BC :
{
2023-09-04 18:31:27 +02:00
if ( type = = ppu_itype : : B | | ( type = = ppu_itype : : BC & & ( op . bo & 0x14 ) = = 0x14 ) )
2021-02-06 17:33:17 +01:00
{
is_fallback = false ;
}
2021-01-19 18:40:15 +01:00
if ( type = = ppu_itype : : B | | type = = ppu_itype : : BC )
{
2023-09-17 19:15:26 +02:00
if ( type = = ppu_itype : : BC & & ( op . bo & 0x14 ) = = 0x14 & & op . bo & 0xB )
2023-09-04 18:31:27 +02:00
{
// Invalid form
is_good = false ;
break ;
}
2023-08-24 22:03:51 +02:00
if ( entry = = 0 & & op . aa )
2021-01-19 18:40:15 +01:00
{
// Ignore absolute branches in PIC (PRX)
is_good = false ;
break ;
}
2023-08-24 22:03:51 +02:00
const u32 target = ( op . aa ? 0 : i_pos ) + ( type = = ppu_itype : : B ? + op . bt24 : + op . bt14 ) ;
2021-01-19 18:40:15 +01:00
2021-02-02 19:14:35 +01:00
if ( target < segs [ 0 ] . addr | | target > = segs [ 0 ] . addr + segs [ 0 ] . size )
2021-01-19 18:40:15 +01:00
{
// Sanity check
is_good = false ;
break ;
}
2023-08-24 22:03:51 +02:00
const ppu_opcode_t test_op { get_ref < u32 > ( target ) } ;
2025-02-23 18:04:14 +01:00
const auto type0 = g_ppu_itype . decode ( test_op . opcode ) ;
2023-08-24 22:03:51 +02:00
if ( type0 = = ppu_itype : : UNK )
{
is_good = false ;
break ;
}
// Test another instruction just in case (testing more is unlikely to improve results by much)
2023-09-04 18:31:27 +02:00
if ( type0 = = ppu_itype : : SC | | ( type0 & ppu_itype : : trap & & ! can_trap_continue ( test_op , type0 ) ) )
{
// May not be followed by a valid instruction
}
else if ( ! ( type0 & ppu_itype : : branch ) )
2023-08-24 22:03:51 +02:00
{
if ( target + 4 > = segs [ 0 ] . addr + segs [ 0 ] . size )
{
is_good = false ;
break ;
}
2025-02-23 18:04:14 +01:00
const auto type1 = g_ppu_itype . decode ( get_ref < u32 > ( target + 4 ) ) ;
2023-08-24 22:03:51 +02:00
if ( type1 = = ppu_itype : : UNK )
{
is_good = false ;
break ;
}
}
2023-09-04 18:31:27 +02:00
else if ( u32 target0 = ( test_op . aa ? 0 : target ) + ( type0 = = ppu_itype : : B ? + test_op . bt24 : + test_op . bt14 ) ;
2023-09-09 13:21:42 +02:00
( type0 = = ppu_itype : : B | | type0 = = ppu_itype : : BC ) & & ( target0 < segs [ 0 ] . addr | | target0 > = segs [ 0 ] . addr + segs [ 0 ] . size ) )
2023-08-24 22:03:51 +02:00
{
// Sanity check
is_good = false ;
break ;
}
2023-06-12 03:48:27 +02:00
if ( target ! = i_pos & & ! fmap . contains ( target ) )
2021-01-19 18:40:15 +01:00
{
2023-09-04 18:31:27 +02:00
if ( block_set . count ( target ) = = 0 )
2021-01-19 18:40:15 +01:00
{
ppu_log . trace ( " Block target found: 0x%x (i_pos=0x%x) " , target , i_pos ) ;
block_queue . emplace_back ( target , 0 ) ;
block_set . emplace ( target ) ;
}
}
}
[[fallthrough]] ;
}
case ppu_itype : : BCCTR :
case ppu_itype : : BCLR :
case ppu_itype : : SC :
{
2023-09-04 18:31:27 +02:00
if ( type = = ppu_itype : : SC & & op . opcode ! = ppu_instructions : : SC ( 0 ) & & op . opcode ! = ppu_instructions : : SC ( 1 ) )
2021-01-19 18:40:15 +01:00
{
// Strict garbage filter
is_good = false ;
break ;
}
2023-08-24 22:03:51 +02:00
if ( type = = ppu_itype : : BCCTR & & op . opcode & 0xe000 )
2021-01-19 18:40:15 +01:00
{
// Garbage filter
is_good = false ;
break ;
}
2023-08-24 22:03:51 +02:00
if ( type = = ppu_itype : : BCLR & & op . opcode & 0xe000 )
2021-01-19 18:40:15 +01:00
{
// Garbage filter
is_good = false ;
break ;
}
2023-09-04 18:31:27 +02:00
if ( type = = ppu_itype : : BCCTR | | type = = ppu_itype : : BCLR )
{
is_fallback = false ;
}
if ( ( type & ppu_itype : : branch & & op . lk ) | | is_fallback )
2023-08-24 22:03:51 +02:00
{
// if farther instructions are valid: register all blocks
// Otherwise, register none (all or nothing)
2023-09-04 18:31:27 +02:00
if ( edge_count < std : : size ( block_edges ) & & i_pos + 4 < lim )
2023-08-24 22:03:51 +02:00
{
block_edges [ edge_count + + ] = i_pos + 4 ;
2023-09-04 18:31:27 +02:00
is_fallback = true ; // Reset value
2023-08-24 22:03:51 +02:00
continue ;
}
}
2021-01-19 18:40:15 +01:00
// Good block terminator found, add single block
break ;
}
default :
{
// Normal instruction: keep scanning
continue ;
}
}
break ;
}
2025-02-07 16:23:51 +01:00
if ( is_good )
{
// Special opting-out: range of "instructions" is likely to be embedded floats
// Test this by testing if the lower 23 bits are 0
vm : : cptr < u32 > _ptr = vm : : cast ( exp ) ;
auto ptr = get_ptr < const u32 > ( _ptr ) ;
bool ok = false ;
for ( u32 it = exp ; it < std : : min ( i_pos + 4 , lim ) ; it + = 4 , advance ( _ptr , ptr , 1 ) )
{
const u32 value_of_supposed_opcode = * ptr ;
if ( value_of_supposed_opcode = = ppu_instructions : : NOP ( ) | | ( value_of_supposed_opcode < < ( 32 - 23 ) ) )
{
ok = true ;
break ;
}
}
if ( ! ok )
{
is_good = false ;
}
}
2021-01-19 18:40:15 +01:00
if ( i_pos < lim )
{
i_pos + = 4 ;
}
2021-02-06 17:33:17 +01:00
else if ( is_good & & is_fallback & & lim < end )
{
// Register fallback target
2023-06-12 03:48:27 +02:00
if ( ! fmap . contains ( lim ) & & ! block_set . contains ( lim ) )
2021-02-06 17:33:17 +01:00
{
ppu_log . trace ( " Block target found: 0x%x (i_pos=0x%x) " , lim , i_pos ) ;
block_queue . emplace_back ( lim , 0 ) ;
block_set . emplace ( lim ) ;
}
}
2021-01-19 18:40:15 +01:00
if ( is_good )
{
2023-08-24 22:03:51 +02:00
for ( u32 it = 0 , prev_addr = exp ; it < = edge_count ; it + + )
2021-01-19 18:40:15 +01:00
{
2023-08-24 22:03:51 +02:00
const u32 block_end = it < edge_count ? block_edges [ it ] : i_pos ;
const u32 block_begin = std : : exchange ( prev_addr , block_end ) ;
auto & block = fmap [ block_begin ] ;
2021-01-27 09:50:51 +01:00
2023-08-24 22:03:51 +02:00
if ( ! block . addr )
2021-01-27 09:50:51 +01:00
{
2023-08-24 22:03:51 +02:00
block . addr = block_begin ;
block . size = block_end - block_begin ;
ppu_log . trace ( " Block __0x%x added (size=0x%x) " , block . addr , block . size ) ;
if ( get_limit ( block_begin ) = = end )
{
block . attr + = ppu_attr : : no_size ;
}
2021-01-27 09:50:51 +01:00
}
2021-01-19 18:40:15 +01:00
}
}
exp = i_pos ;
}
}
// Remove overlaps in blocks
for ( auto it = fmap . begin ( ) , end = fmap . end ( ) ; it ! = fmap . end ( ) ; it + + )
{
const auto next = std : : next ( it ) ;
if ( next ! = end & & next - > first < it - > first + it - > second . size )
{
it - > second . size = next - > first - it - > first ;
}
}
2016-07-07 20:42:39 +02:00
// Convert map to vector (destructive)
2025-01-16 08:44:06 +01:00
for ( auto it = fmap . begin ( ) ; it ! = fmap . end ( ) ; it = fmap . begin ( ) )
2016-07-07 20:42:39 +02:00
{
2025-01-16 11:30:50 +01:00
ppu_function_ext block = std : : move ( fmap . extract ( it ) . mapped ( ) ) ;
2025-01-16 08:44:06 +01:00
2024-06-07 12:03:52 +02:00
if ( block . attr & ppu_attr : : no_size & & block . size > 4 & & ! used_fallback )
2021-01-27 09:50:51 +01:00
{
ppu_log . warning ( " Block 0x%x will be compiled on per-instruction basis (size=0x%x) " , block . addr , block . size ) ;
for ( u32 addr = block . addr ; addr < block . addr + block . size ; addr + = 4 )
{
auto & i = funcs . emplace_back ( ) ;
i . addr = addr ;
i . size = 4 ;
2025-04-05 21:50:45 +02:00
i . toc = block . toc ;
2021-01-27 09:50:51 +01:00
}
2023-08-24 22:03:51 +02:00
per_instruction_bytes + = block . size ;
2021-01-27 09:50:51 +01:00
continue ;
}
funcs . emplace_back ( std : : move ( block ) ) ;
2016-07-07 20:42:39 +02:00
}
2023-08-24 22:03:51 +02:00
if ( per_instruction_bytes )
{
2025-10-05 18:28:03 +02:00
const bool error = per_instruction_bytes > = 200 & & per_instruction_bytes / 4 > = rx : : aligned_div < u32 > ( : : size32 ( funcs ) , 128 ) ;
2023-08-24 22:03:51 +02:00
( error ? ppu_log . error : ppu_log . notice ) ( " %d instructions will be compiled on per-instruction basis in total " , per_instruction_bytes / 4 ) ;
}
2021-01-19 18:40:15 +01:00
ppu_log . notice ( " Block analysis: %zu blocks (%zu enqueued) " , funcs . size ( ) , block_queue . size ( ) ) ;
2023-04-08 17:03:05 +02:00
return true ;
2016-07-07 20:42:39 +02:00
}