PCSX2 Documentation/PCSX2 EE Recompiler

WORK IN PROGRESS I think it is about time that I start to contribute to this project ;)

It is a summary of my understanding of the EE recompiler. The idea is to collect the key information on the recompiler. It is interesting as a general info and it would be useful to port/improve it one day.

External Reference

Global Overview of the EE recompiler

Others useful documentation

  1. Introduction to Dynamic Recompilation
  2. Recompilers: All 'dems buzzwords?


The 3 recompiler phases

  1. The recompilation phase:
    The purpose is to compile an EE instruction list into an X86 instruction list (also know as an instruction block). Instructions are stored in a buffer called x86Ptr. It can be seen as an instruction cache.
  2. The execution phase:
    The x86 instruction block will be executed.
  3. The pause phase:
    The purpose is to emulate the others HW block (VU, GIF, DMA etc..) In particular EE interrupts are handled here.

Internal detail of the EE recompiler

An important part of the recompiler is the management of various blocks (x86/EE etc...). You can see below a nice schematic with the links between them.

 

PS2 Virtual space

The PS2 virtual space is composed of EE instructions. An instruction is always 4B. An instruction can be anywhere in the 4GB address space of the PS2 (yes 4GB). Of course those 4GB are mapped physically into a 32MB RAM or 4MB ROM. The address of the current instruction is stored inside an HW register. It is named PC (which stand for Program Counter).


Recompiler lookup table

The recLUT (recompiler lookup table) will allow to get the related BASEBLOCK related to the PC. The LUT maps the virtual address space as 64kB page. There is an easy macro to retrieve the current BASEBLOCK from a PC.

  • C++ macro: PC_GETBLOCK(PC)
  • C++ array: __aligned16 uptr recLUT[_64kb]

The hwLUT (hardware lookup table) will allow to get the physical PS2 address from a PS2 virtual address. Again a nice macro is available.

  • C++ macro: HWADDR(mem_address)
  • C++ array: __aligned16 uptr hwLUT[_64kb]

Important note, those LUTs are based on the TLB mapping at the boot of the EE kernel. They don't take TLB updates into consideration. It won't work if your game/program (linux/home-brew) rely on TLB management. In this situation your best bet is to use the interpreter.

Base Block

The recompiler will map the EE memory into 3 differents BASEBLOCK array.

  • C++ array: BASEBLOCK *recRAM
  • C++ array: BASEBLOCK *recROM
  • C++ array: BASEBLOCK *recROM1


The BASEBLOCK is barely a function pointer. It could be either

  • a pointer to the X86 instruction buffer
  • a pointer to a JITCompile dispatcher. The function will do
  1. Call recRecompile(cpuRegs.pc) to recompile the current block
  2. Jump to the recompiled block PC_GETBLOCK(cpuRegs.pc)->m_pFnptr()
  • a pointer to a JITCompileInBlock dispatcher.






[code] static __aligned16 uptr recLUT[_64kb]; [/code] Basically the RAM memory is splitted in 32MB zone. It is more complex for ROM, but boot ROM isn't really important to emulate.

You can get current BASEBLOCK of the PC with the macro [code] PC_GETBLOCK(pc) [/code]

There is also an hardware lut. It cheaply converts a virtual address to the physical address (static TLB)

All those blocks are managed by the BaseBlocks class. [code] static BaseBlocks recBlocks; [/code]


_DynGen_* functions generate dispatcher functions and return a function pointer to the function. Full initialization is done in _DynGen_Dispatchers.


  • JITCompile (generated by _DynGen_JITCompile) will

1/ Call recRecompile(cpuRegs.pc) to recompile the current block 2/ Jump to the recompiled block PC_GETBLOCK(cpuRegs.pc)->m_pFnptr()

Basically all BASEBLOCK will contains JITCompile as init address.


  • JITCompileInBlock (generated by _DynGen_JITCompileInBlock)

1/ Jump to JITCompile

Basically after the compilation of BLOCK of size N. First BASEBLOCK will contains the x86 address. The remaining N-1 BLOCK will contain JITCompileInBlock.


  • DispatcherReg (generated by _DynGen_DispatcherReg) will

1/ Jump to the current block (Note. Stack won't be realigned)


  • EnterRecompiledCode (generated by _DynGen_EnterRecompiledCode) will

1/ Setup the base frame pointer 2/ Align the stack pointer 3/ Save edi/esi/ebx on the stack 4/ Simulate a function call? (potentially to help debugger to unwind the stack) 5/ Simulate the stack frame preparation "push ebp, mov ebp, esp" 6/ Save esp, ebp into static variable (for debug check). Code can surely be removed. 7/ Jump to DispatcherReg 8/ Handle the return of DispatcherReg (Leave and restore edi/esi/ebx) 9/ Handle the return of current function (leave and ret)

  • ExitRecompiledCode is the return address of DispatcherReg (end of EnterRecompiledCode)
  • DispatchBlockDiscard (generated by _DynGen_DispatchBlockDiscard) is a wrapper to the C++ function dyna_block_discard
  • DispatchPageReset (generated by _DynGen_DispatchPageReset) is a wrapper to the C++ function dyna_page_reset

[hr]


The details of the "recompilation" stage Input: the PC (first instruction address) Output: x86 code in a buffer that is ready to be executed.