Intermediate Representation
The IR is the middle of the decompiler and the part that does the real work. The IL that comes out of lifting is correct but sloppy - it says exactly what the machine does, one lowered operation at a time. The IR is where that gets turned into something that reads like source: dead code disappears, constants collapse, control flow is rebuilt into loops and conditionals, and virtual functions are recovered.
Everything here operates on two types:
- ir_stat (a statement)
- ir_stat::ir_expr (an expression)
Everything downstream, every pass and every emitter, is written using them. This section is the reference for that model and the pipeline that runs over it.
API
A whole IR run is a single call:
ir_stat::space lift(std::shared_ptr<luramas::closures::closure> &closure,
const passes::environment_flags &env_flags = passes::environment_flags());
lift takes a closure (the tree the front-end produced from an ilang buffer) plus a set of environment_flags that describe what the target actually supports,
and returns an optimized ir_stat::space ready to hand to an emitter. The closure came from the IL; nothing in this call knows which target produced it.
What happens inside
lift runs the same sequence for every target:
- Build - the
ilangclosure is lifted into anir_stat::space, the raw statement stream. - Reconstruct - control flow is recovered into a cfg, dominators are computed, and the code is renamed into SSA so every value has exactly one definition. See Control Flow & SSA.
- Optimize - the pass_manager runs a fixed schedule of passes to fixpoint, reshaping the code and recomputing analysis between phases. See Passes.
- Finalize - definitions and types are inferred so the emitter has variables and signatures to print.
The result is still ir_stat - the emitter is a separate stage. See Code Generation.
Environment Flags
The IR is target-independent, but targets differ - some have pages (native code), some have real memory, some treat comparison results as integers.
Those differences are passed in as environment_flags, and passes gate their behavior on them.
A native x86 run sets fhas_pages, fhas_memory, fhas_types, feliminate_flags, and more; a Lua run leaves most of them off. The full set, and how a real run configures them, is in Customization.
The rest of this section
- Data Model - the
ir_statandir_exprtypes, their kinds, and the emit API. - Control Flow & SSA - how the CFG, dominators, and SSA are built.
- Passes - the pass_manager, the commit model, and the schedule.
- Writing a Pass - developing your own transform.
- Helper Functions - the
tools::toolkit passes are built from. - Customization - environment flags, virtual-function tables, and output formatting.
Do not add or change primitive expression kinds. Intrinsic handling across the whole pipeline depends on the existing
expr_kindsset. Compose new ideas from existing kinds.