← all projects

ps2-recomp

RustLLVMMIPS

Static recompilation of a PlayStation 2 game to a native executable

A from-scratch static recompiler and runtime that lifts PlayStation 2 machine code — EE (R5900 MIPS), IOP (R3000A), and VU microcode — to LLVM IR and compiles it to a native cross-platform executable. No JIT, no interpreter on the hot path.

~50k LOC

Rust

8,497

Functions mapped

macOS · Linux · Windows

Targets

From MIPS to LLVM IR to native

Every guest function is decoded, lifted instruction-by-instruction into LLVM IR against a guest-context struct, verified, and compiled straight to a native object. Scroll to drive the pipeline on a demo function:

$ ps2recomp emit-ll --elf demo.elf --map demo_funcs.csv --addr 0x100000

func_00100000 → native object ✓
demo.s — nvim
1# sum a score table, publish result
2.globl sum_entity_scores
3sum_entity_scores:
4 addiu $sp, $sp, -32
5 sw $s0, 0x1c($sp)
6 lw $t0, 0x10($a0)
7 addu $t1, $zero, $zero
8 addiu $t2, $a0, 0x20
9.loop:
10 lw $t3, 0($t2)
11 addu $t1, $t1, $t3
12 addiu $t2, $t2, 4
13 addiu $t0, $t0, -1
14 bne $t0, $zero, .loop
15 nop
16 sll $t1, $t1, 1
17 lui $t4, 0x35
18 sw $t1, 0x1c40($t4)
19 addu $v0, $zero, $t1
20 lw $s0, 0x1c($sp)
21 addiu $sp, $sp, 32
22 jr $ra
23 nop
~
~
NORMALdemo.s mips utf-823:1
sum_entity_scores.ll — nvim
define void @func_00100000(ptr %0) {
%pc = load i32, ptr %sf, align 4
switch i32 %pc, label %resume_default [ ... ]
bb_00100000:
%addiu = add i32 %g32, -32
store i64 %sext, ptr %gpr1, align 4
bb_00100004:
%addr = add i32 %g323, 28
br i1 %isio, label %st_io, label %st_mem
bb_00100008:
%addr11 = add i32 %g3210, 16
%ldv = phi i32 [ %iotr, %ld_io ], [ %ld, %ea_cont24 ]
%sx = sext i32 %ldv to i64
store i64 %sx, ptr %gpr36, align 4
bb_0010000c:
store i64 0, ptr %gpr37, align 4
bb_00100010:
%addiu40 = add i32 %g3239, 32
store i64 %sext41, ptr %gpr42, align 4
bb_00100014:
%ldv76 = phi i32 [ %iotr57, %ld_io53 ], [ %ld75, %ea_cont63 ]
bb_00100018:
%addu = add i32 %g3280, %g3282
store i64 %sext83, ptr %gpr84, align 4
bb_0010001c:
%addiu87 = add i32 %g3286, 4
bb_00100020:
%addiu92 = add i32 %g3291, -1
bb_00100024:
call void @rt_advance_count(ptr %0, i32 2)
%g6496 = load i64, ptr %gpr95, align 4
%bne = icmp ne i64 %g6496, 0
br i1 %bne, label %bb_00100014, label %bb_0010002c
bb_0010002c:
%sll = shl i32 %g3298, 1
bb_00100030:
store i64 3473408, ptr %gpr101, align 4
bb_00100034:
%addr104 = add i32 %g32103, 7232
store i32 %tn107, ptr %eaptr135, align 4
bb_00100038:
store i64 %sext139, ptr %gpr140, align 4
bb_0010003c:
%sx175 = sext i32 %ldv174 to i64
store i64 %sx175, ptr %gpr176, align 4
bb_00100040:
%addiu179 = add i32 %g32178, 32
bb_00100044:
store i32 %g32183, ptr %sf184, align 4
ret void
NORMALsum_entity_scores.ll llvmlifting…
↓ scroll to compile
view the full emitted module (425 lines, verbatim recompiler output)
; ModuleID = 'dbg'
source_filename = "dbg"

%Context = type { [32 x <2 x i64>], <2 x i64>, <2 x i64>, [32 x i32], i32, i32, i32, i32, i32, ptr, [32 x <2 x i64>], [32 x i32], <2 x i64>, [32 x i32], [256 x <2 x i64>], ptr, i64, [32 x <2 x i64>], <2 x i64>, [1024 x <2 x i64>], [16 x i32], i32, i32, i32, i32, i32, i32, i32, i32, i32, [16384 x i8] }

define void @func_00100000(ptr %0) {
entry:
  %sf = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 7
  %pc = load i32, ptr %sf, align 4
  switch i32 %pc, label %resume_default [
    i32 1048576, label %bb_00100000
    i32 1048580, label %bb_00100004
    i32 1048584, label %bb_00100008
    i32 1048588, label %bb_0010000c
    i32 1048592, label %bb_00100010
    i32 1048596, label %bb_00100014
    i32 1048600, label %bb_00100018
    i32 1048604, label %bb_0010001c
    i32 1048608, label %bb_00100020
    i32 1048612, label %bb_00100024
    i32 1048616, label %bb_00100028
    i32 1048620, label %bb_0010002c
    i32 1048624, label %bb_00100030
    i32 1048628, label %bb_00100034
    i32 1048632, label %bb_00100038
    i32 1048636, label %bb_0010003c
    i32 1048640, label %bb_00100040
    i32 1048644, label %bb_00100044
    i32 1048648, label %bb_00100048
  ]

resume_default:                                   ; preds = %entry
  ret void

bb_00100000:                                      ; preds = %entry
  %gpr = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 29
  %g32 = load i32, ptr %gpr, align 4
  %addiu = add i32 %g32, -32
  %sext = sext i32 %addiu to i64
  %gpr1 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 29
  store i64 %sext, ptr %gpr1, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100004

bb_00100004:                                      ; preds = %bb_00100000, %entry
  %gpr2 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 29
  %g323 = load i32, ptr %gpr2, align 4
  %addr = add i32 %g323, 28
  %gpr4 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 16
  %g64 = load i64, ptr %gpr4, align 4
  %tn = trunc i64 %g64 to i32
  %sprwin = and i32 %addr, -16384
  %isspr = icmp eq i32 %sprwin, 1879048192
  %phys = and i32 %addr, 536870911
  %physhi = lshr i32 %phys, 28
  %inio = icmp eq i32 %physhi, 1
  %notspr = xor i1 %isspr, true
  %isio = and i1 %inio, %notspr
  br i1 %isio, label %st_io, label %st_mem

bb_00100008:                                      ; preds = %st_cont, %entry
  %gpr9 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 4
  %g3210 = load i32, ptr %gpr9, align 4
  %addr11 = add i32 %g3210, 16
  %sprwin12 = and i32 %addr11, -16384
  %isspr13 = icmp eq i32 %sprwin12, 1879048192
  %phys14 = and i32 %addr11, 536870911
  %physhi15 = lshr i32 %phys14, 28
  %inio16 = icmp eq i32 %physhi15, 1
  %notspr17 = xor i1 %isspr13, true
  %isio18 = and i1 %inio16, %notspr17
  br i1 %isio18, label %ld_io, label %ld_mem

bb_0010000c:                                      ; preds = %ld_cont, %entry
  %gpr37 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  store i64 0, ptr %gpr37, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100010

bb_00100010:                                      ; preds = %bb_0010000c, %entry
  %gpr38 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 4
  %g3239 = load i32, ptr %gpr38, align 4
  %addiu40 = add i32 %g3239, 32
  %sext41 = sext i32 %addiu40 to i64
  %gpr42 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 10
  store i64 %sext41, ptr %gpr42, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100014

bb_00100014:                                      ; preds = %bb_00100024, %bb_00100010, %entry
  %gpr43 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 10
  %g3244 = load i32, ptr %gpr43, align 4
  %addr45 = add i32 %g3244, 0
  %sprwin46 = and i32 %addr45, -16384
  %isspr47 = icmp eq i32 %sprwin46, 1879048192
  %phys48 = and i32 %addr45, 536870911
  %physhi49 = lshr i32 %phys48, 28
  %inio50 = icmp eq i32 %physhi49, 1
  %notspr51 = xor i1 %isspr47, true
  %isio52 = and i1 %inio50, %notspr51
  br i1 %isio52, label %ld_io53, label %ld_mem54

bb_00100018:                                      ; preds = %ld_cont55, %entry
  %gpr79 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  %g3280 = load i32, ptr %gpr79, align 4
  %gpr81 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 11
  %g3282 = load i32, ptr %gpr81, align 4
  %addu = add i32 %g3280, %g3282
  %sext83 = sext i32 %addu to i64
  %gpr84 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  store i64 %sext83, ptr %gpr84, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_0010001c

bb_0010001c:                                      ; preds = %bb_00100018, %entry
  %gpr85 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 10
  %g3286 = load i32, ptr %gpr85, align 4
  %addiu87 = add i32 %g3286, 4
  %sext88 = sext i32 %addiu87 to i64
  %gpr89 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 10
  store i64 %sext88, ptr %gpr89, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100020

bb_00100020:                                      ; preds = %bb_0010001c, %entry
  %gpr90 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 8
  %g3291 = load i32, ptr %gpr90, align 4
  %addiu92 = add i32 %g3291, -1
  %sext93 = sext i32 %addiu92 to i64
  %gpr94 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 8
  store i64 %sext93, ptr %gpr94, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100024

bb_00100024:                                      ; preds = %bb_00100020, %entry
  call void @rt_advance_count(ptr %0, i32 2)
  %gpr95 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 8
  %g6496 = load i64, ptr %gpr95, align 4
  %bne = icmp ne i64 %g6496, 0
  br i1 %bne, label %bb_00100014, label %bb_0010002c

bb_00100028:                                      ; preds = %entry
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_0010002c

bb_0010002c:                                      ; preds = %bb_00100028, %bb_00100024, %entry
  %gpr97 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  %g3298 = load i32, ptr %gpr97, align 4
  %sll = shl i32 %g3298, 1
  %sext99 = sext i32 %sll to i64
  %gpr100 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  store i64 %sext99, ptr %gpr100, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100030

bb_00100030:                                      ; preds = %bb_0010002c, %entry
  %gpr101 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 12
  store i64 3473408, ptr %gpr101, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100034

bb_00100034:                                      ; preds = %bb_00100030, %entry
  %gpr102 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 12
  %g32103 = load i32, ptr %gpr102, align 4
  %addr104 = add i32 %g32103, 7232
  %gpr105 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  %g64106 = load i64, ptr %gpr105, align 4
  %tn107 = trunc i64 %g64106 to i32
  %sprwin108 = and i32 %addr104, -16384
  %isspr109 = icmp eq i32 %sprwin108, 1879048192
  %phys110 = and i32 %addr104, 536870911
  %physhi111 = lshr i32 %phys110, 28
  %inio112 = icmp eq i32 %physhi111, 1
  %notspr113 = xor i1 %isspr109, true
  %isio114 = and i1 %inio112, %notspr113
  br i1 %isio114, label %st_io115, label %st_mem116

bb_00100038:                                      ; preds = %st_cont117, %entry
  %gpr136 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 9
  %g32137 = load i32, ptr %gpr136, align 4
  %addu138 = add i32 0, %g32137
  %sext139 = sext i32 %addu138 to i64
  %gpr140 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 2
  store i64 %sext139, ptr %gpr140, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_0010003c

bb_0010003c:                                      ; preds = %bb_00100038, %entry
  %gpr141 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 29
  %g32142 = load i32, ptr %gpr141, align 4
  %addr143 = add i32 %g32142, 28
  %sprwin144 = and i32 %addr143, -16384
  %isspr145 = icmp eq i32 %sprwin144, 1879048192
  %phys146 = and i32 %addr143, 536870911
  %physhi147 = lshr i32 %phys146, 28
  %inio148 = icmp eq i32 %physhi147, 1
  %notspr149 = xor i1 %isspr145, true
  %isio150 = and i1 %inio148, %notspr149
  br i1 %isio150, label %ld_io151, label %ld_mem152

bb_00100040:                                      ; preds = %ld_cont153, %entry
  %gpr177 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 29
  %g32178 = load i32, ptr %gpr177, align 4
  %addiu179 = add i32 %g32178, 32
  %sext180 = sext i32 %addiu179 to i64
  %gpr181 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 29
  store i64 %sext180, ptr %gpr181, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100044

bb_00100044:                                      ; preds = %bb_00100040, %entry
  call void @rt_advance_count(ptr %0, i32 2)
  %gpr182 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 31
  %g32183 = load i32, ptr %gpr182, align 4
  %sf184 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 7
  store i32 %g32183, ptr %sf184, align 4
  ret void

bb_00100048:                                      ; preds = %entry
  call void @rt_advance_count(ptr %0, i32 1)
  br label %tail_0010004c

st_io:                                            ; preds = %bb_00100004
  %stz = zext i32 %tn to i64
  call void @rt_store32(ptr %0, i32 %addr, i64 %stz)
  br label %st_cont

st_mem:                                           ; preds = %bb_00100004
  %eaa = and i32 %addr, -1
  %sprwin5 = and i32 %addr, -16384
  %isspr6 = icmp eq i32 %sprwin5, 1879048192
  br i1 %isspr6, label %ea_spr, label %ea_ram

st_cont:                                          ; preds = %ea_cont, %st_io
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100008

ea_spr:                                           ; preds = %st_mem
  %sproff = and i32 %eaa, 16383
  %sproffz = zext i32 %sproff to i64
  %sf7 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 15
  %spr = load ptr, ptr %sf7, align 8
  %sprmem = getelementptr inbounds i8, ptr %spr, i64 %sproffz
  br label %ea_cont

ea_ram:                                           ; preds = %st_mem
  %ramoff = and i32 %eaa, 33554431
  %ramoffz = zext i32 %ramoff to i64
  %sf8 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 9
  %ram = load ptr, ptr %sf8, align 8
  %rammem = getelementptr inbounds i8, ptr %ram, i64 %ramoffz
  br label %ea_cont

ea_cont:                                          ; preds = %ea_ram, %ea_spr
  %eaptr = phi ptr [ %sprmem, %ea_spr ], [ %rammem, %ea_ram ]
  store i32 %tn, ptr %eaptr, align 4
  br label %st_cont

ld_io:                                            ; preds = %bb_00100008
  %ioload = call i64 @rt_load32(ptr %0, i32 %addr11)
  %iotr = trunc i64 %ioload to i32
  br label %ld_cont

ld_mem:                                           ; preds = %bb_00100008
  %eaa19 = and i32 %addr11, -1
  %sprwin20 = and i32 %addr11, -16384
  %isspr21 = icmp eq i32 %sprwin20, 1879048192
  br i1 %isspr21, label %ea_spr22, label %ea_ram23

ld_cont:                                          ; preds = %ea_cont24, %ld_io
  %ldv = phi i32 [ %iotr, %ld_io ], [ %ld, %ea_cont24 ]
  %sx = sext i32 %ldv to i64
  %gpr36 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 8
  store i64 %sx, ptr %gpr36, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_0010000c

ea_spr22:                                         ; preds = %ld_mem
  %sproff25 = and i32 %eaa19, 16383
  %sproffz26 = zext i32 %sproff25 to i64
  %sf27 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 15
  %spr28 = load ptr, ptr %sf27, align 8
  %sprmem29 = getelementptr inbounds i8, ptr %spr28, i64 %sproffz26
  br label %ea_cont24

ea_ram23:                                         ; preds = %ld_mem
  %ramoff30 = and i32 %eaa19, 33554431
  %ramoffz31 = zext i32 %ramoff30 to i64
  %sf32 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 9
  %ram33 = load ptr, ptr %sf32, align 8
  %rammem34 = getelementptr inbounds i8, ptr %ram33, i64 %ramoffz31
  br label %ea_cont24

ea_cont24:                                        ; preds = %ea_ram23, %ea_spr22
  %eaptr35 = phi ptr [ %sprmem29, %ea_spr22 ], [ %rammem34, %ea_ram23 ]
  %ld = load i32, ptr %eaptr35, align 4
  br label %ld_cont

ld_io53:                                          ; preds = %bb_00100014
  %ioload56 = call i64 @rt_load32(ptr %0, i32 %addr45)
  %iotr57 = trunc i64 %ioload56 to i32
  br label %ld_cont55

ld_mem54:                                         ; preds = %bb_00100014
  %eaa58 = and i32 %addr45, -1
  %sprwin59 = and i32 %addr45, -16384
  %isspr60 = icmp eq i32 %sprwin59, 1879048192
  br i1 %isspr60, label %ea_spr61, label %ea_ram62

ld_cont55:                                        ; preds = %ea_cont63, %ld_io53
  %ldv76 = phi i32 [ %iotr57, %ld_io53 ], [ %ld75, %ea_cont63 ]
  %sx77 = sext i32 %ldv76 to i64
  %gpr78 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 11
  store i64 %sx77, ptr %gpr78, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100018

ea_spr61:                                         ; preds = %ld_mem54
  %sproff64 = and i32 %eaa58, 16383
  %sproffz65 = zext i32 %sproff64 to i64
  %sf66 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 15
  %spr67 = load ptr, ptr %sf66, align 8
  %sprmem68 = getelementptr inbounds i8, ptr %spr67, i64 %sproffz65
  br label %ea_cont63

ea_ram62:                                         ; preds = %ld_mem54
  %ramoff69 = and i32 %eaa58, 33554431
  %ramoffz70 = zext i32 %ramoff69 to i64
  %sf71 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 9
  %ram72 = load ptr, ptr %sf71, align 8
  %rammem73 = getelementptr inbounds i8, ptr %ram72, i64 %ramoffz70
  br label %ea_cont63

ea_cont63:                                        ; preds = %ea_ram62, %ea_spr61
  %eaptr74 = phi ptr [ %sprmem68, %ea_spr61 ], [ %rammem73, %ea_ram62 ]
  %ld75 = load i32, ptr %eaptr74, align 4
  br label %ld_cont55

st_io115:                                         ; preds = %bb_00100034
  %stz118 = zext i32 %tn107 to i64
  call void @rt_store32(ptr %0, i32 %addr104, i64 %stz118)
  br label %st_cont117

st_mem116:                                        ; preds = %bb_00100034
  %eaa119 = and i32 %addr104, -1
  %sprwin120 = and i32 %addr104, -16384
  %isspr121 = icmp eq i32 %sprwin120, 1879048192
  br i1 %isspr121, label %ea_spr122, label %ea_ram123

st_cont117:                                       ; preds = %ea_cont124, %st_io115
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100038

ea_spr122:                                        ; preds = %st_mem116
  %sproff125 = and i32 %eaa119, 16383
  %sproffz126 = zext i32 %sproff125 to i64
  %sf127 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 15
  %spr128 = load ptr, ptr %sf127, align 8
  %sprmem129 = getelementptr inbounds i8, ptr %spr128, i64 %sproffz126
  br label %ea_cont124

ea_ram123:                                        ; preds = %st_mem116
  %ramoff130 = and i32 %eaa119, 33554431
  %ramoffz131 = zext i32 %ramoff130 to i64
  %sf132 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 9
  %ram133 = load ptr, ptr %sf132, align 8
  %rammem134 = getelementptr inbounds i8, ptr %ram133, i64 %ramoffz131
  br label %ea_cont124

ea_cont124:                                       ; preds = %ea_ram123, %ea_spr122
  %eaptr135 = phi ptr [ %sprmem129, %ea_spr122 ], [ %rammem134, %ea_ram123 ]
  store i32 %tn107, ptr %eaptr135, align 4
  br label %st_cont117

ld_io151:                                         ; preds = %bb_0010003c
  %ioload154 = call i64 @rt_load32(ptr %0, i32 %addr143)
  %iotr155 = trunc i64 %ioload154 to i32
  br label %ld_cont153

ld_mem152:                                        ; preds = %bb_0010003c
  %eaa156 = and i32 %addr143, -1
  %sprwin157 = and i32 %addr143, -16384
  %isspr158 = icmp eq i32 %sprwin157, 1879048192
  br i1 %isspr158, label %ea_spr159, label %ea_ram160

ld_cont153:                                       ; preds = %ea_cont161, %ld_io151
  %ldv174 = phi i32 [ %iotr155, %ld_io151 ], [ %ld173, %ea_cont161 ]
  %sx175 = sext i32 %ldv174 to i64
  %gpr176 = getelementptr inbounds %Context, ptr %0, i32 0, i32 0, i32 16
  store i64 %sx175, ptr %gpr176, align 4
  call void @rt_advance_count(ptr %0, i32 1)
  br label %bb_00100040

ea_spr159:                                        ; preds = %ld_mem152
  %sproff162 = and i32 %eaa156, 16383
  %sproffz163 = zext i32 %sproff162 to i64
  %sf164 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 15
  %spr165 = load ptr, ptr %sf164, align 8
  %sprmem166 = getelementptr inbounds i8, ptr %spr165, i64 %sproffz163
  br label %ea_cont161

ea_ram160:                                        ; preds = %ld_mem152
  %ramoff167 = and i32 %eaa156, 33554431
  %ramoffz168 = zext i32 %ramoff167 to i64
  %sf169 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 9
  %ram170 = load ptr, ptr %sf169, align 8
  %rammem171 = getelementptr inbounds i8, ptr %ram170, i64 %ramoffz168
  br label %ea_cont161

ea_cont161:                                       ; preds = %ea_ram160, %ea_spr159
  %eaptr172 = phi ptr [ %sprmem166, %ea_spr159 ], [ %rammem171, %ea_ram160 ]
  %ld173 = load i32, ptr %eaptr172, align 4
  br label %ld_cont153

tail_0010004c:                                    ; preds = %bb_00100048
  %sf185 = getelementptr inbounds nuw %Context, ptr %0, i32 0, i32 7
  store i32 1048652, ptr %sf185, align 4
  ret void
}

declare void @rt_advance_count(ptr, i32)

declare void @rt_store32(ptr, i32, i64)

declare i64 @rt_load32(ptr, i32)

The assembly is a hand-written demo function — not game code. The LLVM IR shown is excerpted verbatim from the recompiler's emit-ll output for those exact instruction words (full module above); the binary stage is illustrative.

Highlights

  • 01Lifts every guest PS2 function to LLVM IR in-process via inkwell (LLVM 21), verifies each module, and emits PC native objects directly.
  • 02CPU-parameterized lifter shared between the R5900 EE core and R3000A IOP, including 128-bit MMI SIMD, exact PS2 non-IEEE float semantics, and VU microcode.
  • 03Floating-point and SIMD behavior validated bit-exact against a real PS2 running a custom oracle daemon over LAN.
  • 04Graphics Synthesizer reimplemented as a true hardware renderer on the SDL3 GPU API (Metal, Vulkan, D3D12) rather than a software rasterizer.
  • 05~50,000 lines of Rust across a 10-crate workspace: recompiler, generic LLVM backend, runtime, and host layers.
Options menu over the running game
Native options menu — controls, audio, and video settings rendered over the recompiled game.
full write-up in progress — detailed notes on this project are coming soon.