Deterministic C source generation from validated #206/#264 IR. Generates self-contained, compilable C source with fixed-width types and well-defined arithmetic — no UB, no host-width assumptions. Supports GPR arithmetic (Phase 3A), memory access (Phase 3B), comparisons (Phase 3C), and explicit control flow (Phase 3C).
Plain C (C11, <stdint.h>). Rationale: simplest portable standard; maps
directly to gcc/g++ native toolchain; uint32_t guarantees 32-bit guest
values without host-width assumptions; no class hierarchies needed for
Phase 3A scope.
struct RecompilerState;
typedef int32_t (*recompiler_host_transfer_fn)(struct RecompilerState*);
typedef struct RecompilerState {
uint32_t gpr[32];
uint32_t hi;
uint32_t lo;
uint32_t pc;
int32_t termination_reason;
uint32_t next_pc;
void* core;
recompiler_host_transfer_fn host_transfer;
} RecompilerState;gpr[0]is always 0 on entry (caller must ensure).hi,lo,pcare present for ABI stability; initialized to 0 in usage.termination_reason: written by the generated block on exit; 0 = Success; nonzero =RecompilerIrTerminationReasonbyte value cast toint32_t.next_pc: set on Success exit; on Branch, sets the taken or fallthrough target; on Jump/Call, sets the target address.core: opaque pointer passed to memory helper functions. The runtime provides the implementation; the codegen never dereferences it.host_transfer: optional host-owned control-transfer hook. When the dispatcher reaches a PC that no generated block owns, it offers the current state to this callback before classifying the PC as unsupported. A return value of 0 means the host claimed the transfer and has settermination_reason/next_pc; nonzero means it was not claimed. A null hook preserves the normal unsupported-PC behavior. This is used by the runtime boundary for transfers such as BIOS A0/B0/C0 trampoline vectors without embedding BIOS-specific knowledge in generated code.
static int32_t recompiler_block_0x<entryPc>(RecompilerState* state);- Takes a pointer to
RecompilerState. - On every exit, writes
state->termination_reason(0 on Success, the reason code otherwise). - Returns 0 on Success (with
state->next_pcset), or the termination reason code as a non-zeroint32_t.
int32_t recompiler_dispatch(RecompilerState* state, uint32_t budget);A budgeted sequential dispatcher. It selects the block function whose entry PC
matches state->pc, executes it, stops on a non-Success termination, and
refuses to retire more than budget instructions (reporting
RECOMPILER_REASON_EXECUTION_BUDGET_EXCEEDED). When a PC matches no generated
block, the dispatcher first calls the optional state->host_transfer hook. If
the host claims the PC, execution follows the termination/continuation state set
by that hook. If no host claims it, a PC reached after at least one step means
the straight-line program fell off the end (normal completion); an unclaimed PC
on the first step is reported as RECOMPILER_REASON_UNSUPPORTED_IR.
The generated RECOMPILER_REASON_* constants used by the dispatcher are emitted
from the live RecompilerIrTerminationReason enum values rather than duplicated
numeric literals, keeping dispatcher and per-block termination reporting on the
same contract.
- Entry: caller initializes
gpr[0] = 0; the dispatcher setsstate->pctostate->next_pcafter each retired block via the sequential program counter. - Exit: returns termination reason and writes it to
state->termination_reason; on Success, also setsstate->next_pc.
- Read:
state->gpr[i] - Write:
state->gpr[i] = value $zeroinvariant preserved: generator never emitsWriteGprtogpr[0].
Block functions call extern memory helpers for guest memory access. Address translation, alignment, endianness, and bounds checking are the runtime's responsibility.
extern uint8_t recompiler_read_mem8(void* core, uint32_t address);
extern uint16_t recompiler_read_mem16(void* core, uint32_t address);
extern uint32_t recompiler_read_mem32(void* core, uint32_t address);
extern void recompiler_write_mem8(void* core, uint32_t address, uint8_t value);
extern void recompiler_write_mem16(void* core, uint32_t address, uint16_t value);
extern void recompiler_write_mem32(void* core, uint32_t address, uint32_t value);- Narrow loads zero-extend to
uint32_t. - Narrow stores write only the specified width (little-endian).
- The
corepointer isstate->core.
CompareEqual:uint32_t v = (a == b) ? 1u : 0u;CompareNotEqual:uint32_t v = (a != b) ? 1u : 0u;
The exit of each block carries an explicit flow transition:
- Sequential:
state->next_pc = <nextPc>;(same as Phase 3A). - Branch:
if (cond != 0u) { state->next_pc = <taken>; } else { state->next_pc = <fallthrough>; } - Jump:
state->next_pc = <target>; - Call:
state->next_pc = <callee_target>;(the return address is an architectural GPR write the lowering emits).
- All guest values:
uint32_t/int32_tvia<stdint.h>. - Never use host
long,int, or pointer-width arithmetic for guest values. - Immediate constants 0-9 are rendered as plain decimal literals; larger
values are rendered as
(valueu).
uint32_t r = (uint32_t)a + (uint32_t)b; // well-defined modular wrap
uint32_t r = (uint32_t)a - (uint32_t)b; // well-defined modular wrapSigned overflow is UB; all guest arithmetic uses unsigned types.
uint32_t r = (uint32_t)a << (s & 31u); // well-defined for uint32_t
uint32_t r = (uint32_t)a >> (s & 31u); // well-defined for uint32_tShift amount masked to 5 bits; >> on uint32_t is always logical.
static uint32_t recompiler_sra32(uint32_t a, uint32_t s) {
uint32_t sh = s & 31u;
uint32_t result = a >> sh;
if ((a & 0x80000000u) != 0u && sh != 0u) {
result |= (0xFFFFFFFFu << (32u - sh));
}
return result;
}This is a well-defined, 64-bit-free formulation that does not depend on the
implementation-defined behavior of >> on signed values.
uint32_t r = ~(a | b); // well-defined on uint32_t| Parameter | Value |
|---|---|
| Compiler | gcc (primary) |
| Standard | -std=c11 |
| Optimization | -O0 (semantic debugging priority) |
| Warnings | -Wall -Wextra |
| Includes | <stdint.h> only (self-contained) |
| Output | Generated to temp dir in tests; never committed |
Same IR + same config → byte-equivalent source.
- Fixed identifier naming:
v0,v1, ... (byresultValueId). - Fixed indentation: 2 spaces.
- Fixed block ordering: by
EntryPc(enforced byRecompilerIrProgram). - Fixed operation ordering: by position within block.
- No timestamps, GUIDs, random names, paths, or environment-dependent values.
- Helper function emitted in fixed order before block functions.
Generator rejects (returns Success=false with machine-readable diagnostic):
- IR that fails
RecompilerIrValidator.Validate(). - Undefined
RecompilerIrOperationKindvalues. - Undefined
RecompilerIrTerminationReasonvalues. - Empty programs (
UNSUPPORTED_EMPTY_PROGRAM). - Duplicate result value ids (
DUPLICATE_RESULT_VALUE_ID). - Operation kinds outside the Phase 3A–3C subset
(
UNSUPPORTED_OPERATION_KIND). - Exit flow kinds other than
Sequential,Branch,Jump, andCall(UNSUPPORTED_FLOW_KIND). TheReturnflow kind is additionally rejected by the IR validator inRecompilerIrValidator, since it cannot carry a register-held target as a static address.
Generator never silently produces partial source for invalid IR.