Skip to content

Recompilation status

Pablo Souza edited this page Oct 24, 2026 · 13 revisions

Recompilation status

Last updated: 2026-10-20 (R5 N5: the frame drawn through Metal, experimental, in bloodborne-runtime). What the recompilation has done so far, and what is next.

Milestones

Goal Status
R0 Groundwork: function table, library labels, CPU profile, record and replay, native functions in the game ✅ Done
R1 Recompiler prototype: generated C for the recorded functions, checked by replay ✅ Done
R2 Coverage: every function generated; the game runs recompiled, the translator as fallback ✅ Done
R3 Speed: integer, flags and vector instructions in C; memory ordering relaxed where safe 🟡 In progress
R4 Annotations: names, signatures and structs make the generated C readable ⬜
R5 The engine's graphics on each platform's own GPU API: native renderer (bloodborne-runtime) 🟡 N1 closed; N5 (Metal backend): the frame draws through Metal behind an experimental renderer option

Progress

2026-10-24. The game's executable has 241,821 functions, about 11.3 million instructions.

Progress
Functions recompiled, running in the game ████████████████████ 99.6% 240,973 of 241,821
Instructions recompiled ███████████████████░ 95.3% 10,743,599 of 11,268,132
Instructions in plain C (of those recompiled) ████████████████████ 99.1% 10,641,693 of 10,743,599
CPU time recompiled, against the translator ████████████████████ 98% 420% of one core against 428%
Functions checked call by call (recordings) ░░░░░░░░░░░░░░░░░░░░ 0.4% 933 of 241,821
Functions labelled by library (2026-10-24) █████████████░░░░░░░ 62.7% 151,562 of 241,821

Every function is generated; the ones left to the translator are the 660 import stubs, the 130 functions that call setjmp, and 58 that the game's patches (56) or the runtime's live switches (2) change. Instructions recompiled count what control flow reaches; the rest is padding and code nothing reaches. Every recompiled function can run in the game; the call-by-call checks cover the busiest, and the fuzzing covers every instruction form the generator handles.

Done so far

  • Function table: 241,821 functions: 162,959 from the unwind tables, the rest found through calls, jumps and code pointers (many small leaf functions have no unwind entry). Every piece of code the game ran on a test route lies inside a known function.
  • Imports named: the game's 660 calls into the PS4 system libraries, by name.
  • Library labels: 62.7% of the functions, 83.8% of the code (below).
  • Profiles: CPU time per function from the running game.
  • Record and replay: calls of up to 16 functions recorded at once, with no visible slowdown; the originals replay exactly.
  • Native functions in the game: a library of native functions is loaded at start and called straight from translated code, at no measurable cost; the recompiler's output uses this path.

The recompiler today

  • Generated C for every function: 241,031 functions, 10.74 million instructions, 99.1% of them in plain C. The whole game builds in 11.5 minutes into a 404 MB library; a change rebuilds only the files it touches.
  • In the game: 240,973 functions run recompiled; standing in the Hunter's Dream the game plays at 57–59 FPS (59.1 with the translator alone; the frame rate is bound by rendering).
  • CPU time: the library takes the same CPU time as the translator, at the same frame rate (420% of one core against 428% in the Hunter's Dream, 2026-10-24). It took 28% more before the lookup of recompiled functions became one flat table, and 14% more before the instructions the generated C left to the interpreter (11.8 million a second there) got C of their own.
  • Imports: calls of the PS4 libraries the runtime provides go from recompiled code straight to them, 2.4 million a second; 1.45 million a second still enter the translator (the PS4 libc's own code, and functions that hand their rest back).
  • Correct: recordings of 40 calls each for the 933 busiest functions: 911 match every call; the other 22 also fail when the original runs (they read the time stamp counter, or memory other threads change).
  • Fuzzed: every distinct instruction form in the game the generator handles (4,892 of them, at most 30 per mnemonic) runs from 200 random states through the interpreter and through its generated C, with no differences.
  • Safe with patches: a function whose code a game patch changed stays with the translator.

Found along the way

  • A lock bug in the interpreter: after a failed lock cmpxchg it reported a separately loaded value, so a failed exchange could look successful and two threads took the same lock. It was the occasional heap panic during save load; fixed for everyone (main).
  • A gap in the recorder: vmaskmovdqu writes to an address it doesn't name (rdi).
  • Speed: recompiled code first ran at 51 FPS. A shared counter every thread added to, calls between recompiled functions going through the runtime, and instructions left to the interpreter cost the difference.
  • setjmp and longjmp: recompiled import stubs left the runtime's own return address where setjmp saves it, and a later longjmp (the game's PNG reader) came back into a frame that was gone. Import stubs and the 40 functions that call setjmp now stay with the translator, and each call the runtime makes into the translator has its own return address.

The game's code

Library Functions Code
Game (SPRJ) ~37,100 7.8 MiB
Dantelion2 (FromSoftware core library) ~35,800 7.4 MiB
Havok 2014.1 ~28,500 9.7 MiB
Scaleform GFx (menus, HUD) ~20,700 4.2 MiB
FD4 (FromSoftware engine) ~16,400 4.1 MiB
FMOD Ex 4.44.50 ~7,200 1.4 MiB
Lua, YEBIS, zlib, other source paths ~5,900 1.9 MiB
Not labelled yet ~90,300 7.1 MiB
Total 241,821 ~44 MiB

The labels (2026-10-24) start from source paths and messages and spread to the functions between two of one library, then through vtables, callers and callees. Hiding a tenth of them and spreading again gives back 96.4% the same (tools/recomp/label.py --check). The 90,259 left are small (median 18 bytes).

In 20 s of play in the Hunter's Dream, 933 functions ran on the main thread and the busiest 145 took half its time: no small set of functions dominates, so every function gets recompiled.

Next

  1. Speed (R3), cheaper than the translator: the runtime between recompiled functions (rc_call, the registers copied around every call), the wide-character libc calls in native code, jump tables as C switches (fewer hand-backs to the translator), hot functions together in the library; then measure CPU time, not only frames.
  2. Longer and more varied routes (other areas, bosses, travel, cutscenes) to run more of the recompiled code in the game.
  3. Known library code (zlib, Lua) matched against its sources: real names (R4).
  4. Recompile at install time from the player's own copy, in the launcher.

Updating this page

Update the date, the milestone table, the progress bars (20 blocks, one per 5%) and the numbers: checks come from tools/recomp/replay.sh, calls a second from the game's log.

Clone this wiki locally