# Architecture How a PS5 executable becomes a native Linux or Windows program. Nothing is emulated: the converted executable runs as a normal process and calls native implementations of the system libraries. ## Overview ```mermaid flowchart LR subgraph input["PS5 game"] elf["input.elf"] mods["sce_module/*"] end subgraph relinker["relinker (core/relinker)"] direction TB intel["--to-intel:
lower AMD-only instructions
(codegen)"] pipeline["RelinkerPipeline:
read imports by NID,
check syscalls, filter unused NIDs,
build SysV dynamic section"] guest["GuestModuleBuilder:
convert bundled modules"] patcher["LinuxElfPatcher / WindowsPePatcher"] intel -.-> pipeline --> guest --> patcher end subgraph build["build (core/libs)"] direction TB prx["core/libs/prx/*
shared libraries"] nid["nid_patcher:
rename exports to their NIDs"] prx --> nid end subgraph out["Native program"] app["app.elf / app.exe"] app0["app0/sce_module/*"] libs["libs/*.prx"] end elf --> intel mods --> guest patcher --> app guest --> app0 nid --> libs app -- "OS loader binds imports by NID" --> libs app0 -- "OS loader binds imports by NID" --> libs ``` - [`core/relinker/main.cpp`](../../core/relinker/main.cpp) runs the steps in this order. `--to-intel` is optional, see [USAGE.md](../user/USAGE.md). - Each library in [`core/libs/prx`](../../core/libs/prx) builds as a shared library. After the build, `nid_patcher` ([`core/libs/nid`](../../core/libs/nid)) renames every export to its NID, computed from the function name. `APS5_EXPORT("", func)` sets the NID directly when the name is unknown. ## Graphics ```mermaid flowchart LR game["Game:
command buffers"] --> submit["libSceAgcDriver/Submit
DCB / ACB"] submit --> pm4["Execution/Pm4:
state, draws,
dispatches"] pm4 -- "shader + state" --> cache{"Compiled variant
in memory or
on disk?"} cache -- yes --> vk cache -- no --> dec subgraph recompiler["core/shader/recompiler"] dec["RdnaDecoder"] --> cf["ControlFlow:
graph + structurize"] cf --> tr["Translation:
RDNA to IR"] tr --> opt["Optimization:
SSA, resources,
bindings"] opt --> spv["SpirvBackend:
emit SPIR-V"] end spv --> vk["libSceAgcDriver/Graphics:
Vulkan pipeline"] ``` - [`Recompiler.cpp`](../../core/shader/recompiler/Recompiler.cpp) runs the stages in this order. With `ANYPS5_ENABLE_SPIRV_TOOLS`, the SPIR-V is also validated and optimized with SPIRV-Tools. - `ShaderRecompiler::Recompile` keeps compiled variants in memory, and `ShaderDiskCache` stores them on disk so later runs reuse them. - `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` detached threads (at least 2, 64 MiB stacks). - The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead. ### Shader MODE register access `GuestContext::floatMode` carries the initial shader mode through `Recompile` and `TranslateOptions` into each block's `TranslationContext`. `s_getreg_b32` reads confined to MODE bits 0–3 return the selected initial rounding bits in the destination's low bits. Bits 0–1 select f32 rounding; bits 2–3 select f16/f64 rounding. Missing mode metadata retains the legacy round-to-nearest-even assumption. Reading other MODE fields or hardware registers throws. An immediate MODE write confined to the rounding fields is accepted only when its selected replacement bits equal the initial state. Source bits above the selected width are ignored. `s_round_mode` similarly compares its low four immediate bits with the initial rounding fields. A state-changing write or an SGPR-sourced MODE write throws with its instruction and program counter. Runtime transitions require control-flow-aware mode tracking and matching arithmetic lowering. Writes to other hardware registers retain their existing behavior and remain technical debt. The register layout and instruction contract follow [AMD RDNA2 ISA sections 5.8 and 6.4, Table 24](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/rdna2-shader-instruction-set-architecture.pdf), cross-checked against LLVM 20.1.8's [hardware-register encoding](https://github.com/llvm/llvm-project/blob/87f0227cb60147a26a1eeb4fb06e3b505e9c7261/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.h) and [MODE state analysis](https://github.com/llvm/llvm-project/blob/87f0227cb60147a26a1eeb4fb06e3b505e9c7261/llvm/lib/Target/AMDGPU/SIModeRegister.cpp). This contract does not establish arithmetic support for every initial mode or console-specific behavior. `agc_shader_mode_register` decodes instruction words, builds the control-flow graph and checks production translation without a Vulkan device. It covers rounding subfield reads, masked constant writes, branches, unsupported runtime changes and diagnostics, plus mode propagation through full recompilation. The requirements remain active in Release; CTest bounds execution to 20 seconds.