package core:rexcode/ir
Overview
# rexcode/ir — the IR API layer
core:rexcode/ir is to the intermediate representations (WASM, SPIR-V, LLVM
bitcode, and the LLVM dialects AIR / DXIL) what core:rexcode/isa is to the
machine ISAs: the shared core every concrete IR package builds on. It holds
the parts that are the same for every IR, and defines the contract each IR
package follows. It implements no specific IR — the concrete packages
(core:rexcode/ir/wasm, .../spirv, .../llvm, …) are added separately.
See docs/ir_design.md for the full design rationale and the ISA↔IR comparison.
Why a sibling, not a generalization of isa
The ISA API works because every arch follows one shape contract
(Mnemonic / Instruction / Operand / encode / decode / print) while
the shared isa package carries only the universal bookkeeping. The IR API
keeps that spirit, with three honest concessions where IRs truly diverge:
1. A structured module replaces the flat instruction stream. The unit of
work is a Module (Module → []Function → []Block → []Operation), not a
[]Instruction. So ir owns the structural model (module/function/block/
operation), where isa owns no Instruction.
2. A first-class type system. Operations and results reference a
module-level type table by Type_Ref. ISAs bake width into the mnemonic.
3. Entity references replace PC-relative labels. Operands reference SSA
values / blocks / functions / globals / types by Id, resolved
structurally — there is no instruction-index→byte-offset rewrite. (Object-
file symbol fixups still produce Relocations, defined per-IR.)
Everything else is deliberately ISA-shaped: the leaf Operation is
isa.Instruction + an optional typed Result, opcode is a u16 just like
isa.Mnemonic, Operand is one discriminated value, and the verbs are the same
three. Dataflow lets one model host both an implicit value stack (WASM) and
explicit SSA (SPIR-V/LLVM) without baking in either.
What this package provides (shared)
* status.odin — Error / Error_Code (shape-identical to isa.Error).
* refs.odin — Id / Ref / Ref_Space / Symbol_Table (the label analog).
* types.odin — Type / Type_Ref / Type_Kind (the type table).
* module.odin — Module / Function / Block / Operation / Operand /
Result / Dataflow (the structural model).
* print.odin — token kinds, print options, number-formatting helpers.
What a concrete IR package provides (the contract)
Each core:rexcode/ir/<name> package supplies, mirroring an arch package:
* Opcode — the IR's operation enum (u16, INVALID = 0), stored in
Operation.opcode. (Analogous to a Mnemonic.)
A *codec** — the wire format. Two strategies cover the field:
- table-driven (WASM byte/LEB, SPIR-V 32-bit words): a static
OPCODE → operand-layout table, exactly like an ISA ENCODING_TABLE.
- bitstream (LLVM bitcode, and thus AIR / DXIL): a block/record/
abbreviation engine; the operand layout is data-defined, so there is no
static table. The codec is pluggable behind the verbs below.
* The three verbs, on a Module (vs the ISA verbs' []Instruction):
encode :: proc(m: Module, code: []u8, relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (byte_count: u32, ok: bool)
decode :: proc(data: []u8, m: ^Module, errors: ^[dynamic]Error, allocator := context.allocator) -> (byte_count: u32, ok: bool)
print :: proc(m: Module, options := ir.DEFAULT_PRINT_OPTIONS) -> ir.Print_Result tprint :: proc(m: Module, options := ir.DEFAULT_PRINT_OPTIONS) -> string
(encode/decode deliberately drop the ISA verbs' label_defs /
resolve / base_address — there is no PC-relative resolution pass — and
take a Module rather than an instruction array. That is the whole point of
the divergence, made explicit rather than left inert.)
* Relocation / Relocation_Type — per-IR (the linker fixups for EXTERNAL
references), exactly as each arch owns its reloc.odin.
* Type lowering — how the IR's wire types map to/from ir.Type.
A dialect (AIR over LLVM, DXIL over LLVM) reuses its base IR's codec wholesale and adds only the intrinsic/metadata conventions and any container wrapper.
rexcode · Brendan Punsky (dotbmp@github), original author
rexcode · Brendan Punsky (dotbmp@github), original author
rexcode · Brendan Punsky (dotbmp@github), original author
rexcode · Brendan Punsky (dotbmp@github), original author
rexcode · Brendan Punsky (dotbmp@github), original author
Index
Constants (3)
Variables (0)
This section is empty.
Procedures (25)
Procedure Groups (0)
This section is empty.
Types
Block ¶
A basic block (SSA) or a structured region (stack IRs). params are block
arguments (SSA, phi-free form); empty for stack IRs. The terminator is the
final operation (Operation_Flags.terminator).
Dataflow ¶
Dataflow :: enum u8 { STACK, // WASM SSA, // SPIR-V / LLVM / AIR / DXIL }
Per-IR dataflow discipline. WASM = STACK; SPIR-V / LLVM / AIR / DXIL = SSA.
Error ¶
Error :: struct #packed { location: u32, code: Error_Code, _: [3]u8 `fmt:"-"`, }
location is the operation index on encode, or the byte offset on decode --
mirroring isa.Error.inst_idx.
Error_Code ¶
Error_Code :: enum u8 { NONE = 0, // Shared with the ISA side (encode/decode of the byte/word stream). INVALID_OPCODE, NO_MATCHING_ENCODING, OPERAND_MISMATCH, IMMEDIATE_OUT_OF_RANGE, BUFFER_OVERFLOW, BUFFER_TOO_SHORT, // IR-specific (no ISA analog -- these need a type system / SSA / a module). INVALID_TYPE, // malformed or out-of-range Type_Ref TYPE_MISMATCH, // an operand/result type disagrees with the op signature UNDEFINED_REF, // a Ref to an id/symbol that is never defined DUPLICATE_DEFINITION, // an id/symbol defined twice MALFORMED_MODULE, // structural violation (block without terminator, ...) UNSUPPORTED_FEATURE, // a capability/extension the codec does not implement }
Global ¶
Global :: struct { name: string, init: Id, // a CONSTANT ref, or ID_NONE type: Type_Ref, mutable: bool, }
A module-level mutable/immutable value.
Id ¶
Id :: distinct u32
A stable id into one of the module's entity spaces (see Ref_Space).
Related Procedures With Parameters
Related Procedures With Returns
Related Constants
Module ¶
Module :: struct { target: string, // triple / capability profile / version tag types: []Type, // the type table; Type_Ref indexes here globals: []Global, functions: []Function, symbols: Symbol_Table, // externally-visible names dataflow: Dataflow, }
The module -- the unit the IR verbs operate on (where the ISA verbs take a
flat []Instruction). Metadata, decorations, and dialect custom sections are
carried by the concrete IR alongside this core, the way each arch carries its
own reloc.odin.
Operand ¶
Operand :: struct #packed { imm: i64, kind: Operand_Kind, space: Ref_Space, // REF: which id space aux: u16, // LIT_FLOAT width / ATTRIBUTE tag / dialect bits flags: u32, }
16 bytes. The payload is one i64 (covers an Id, a Type_Ref, an int/float-bits
literal, or an attribute value); space/aux discriminate the entity space
or dialect tag. Large/aggregate constants are entities (a CONSTANT ref), not
inline operands -- the SSA way -- so no inline byte blob is needed here.
Related Procedures With Parameters
Operand_Kind ¶
Operand_Kind :: enum u8 { NONE, LIT_INT, // integer literal (value in `imm`) LIT_FLOAT, // float literal (IEEE bits in `imm`, width in `aux`) REF, // reference to an entity: `imm` is the Id, `space` the Ref_Space TYPE, // a Type_Ref (in the low 32 bits of `imm`) ATTRIBUTE, // a dialect enum / decoration / mask (value in `imm`, tag in `aux`) }
Operation ¶
Operation :: struct { operands: []Operand, result: Result, // SSA def; `.id == ID_NONE` for stack/void ops opcode: u16, flags: Operation_Flags, _: u8, }
opcode is the concrete IR's Opcode enum, stored as u16 (like isa.Mnemonic).
operands is variable-arity (calls, switch, phi) and caller-owned, like the
rest of the decoded module -- the fixed [4]Operand of the ISA Instruction is
the one shape that does not survive into IRs.
Print_Options ¶
Print_Options :: struct { uppercase: bool, hex_prefix: string, // default "0x" hex_lowercase: bool, value_prefix: string, // SSA value sigil, default "%" block_prefix: string, // block-label sigil, default "^" show_offsets: bool, indent: string, // default " " separator: string, }
Related Procedures With Parameters
Related Constants
Ref ¶
A typed reference: which space, plus the id within it. Carried by REF operands and by branch targets. 8 bytes, like isa.Label_Definition is u32-cheap.
Related Procedures With Returns
Ref_Space ¶
Ref_Space :: enum u8 { NONE, VALUE, // an SSA result (or a local/stack slot) BLOCK, // a basic block / structured-control label (branch target) FUNCTION, GLOBAL, TYPE, CONSTANT, // a constant-pool entry MEMORY, // a linear memory / address space METADATA, // a metadata/debug node EXTERNAL, // an imported/exported symbol -- relocatable across object files }
Which id space a reference addresses. Drives validation, printer annotation, and (for EXTERNAL) relocation-type selection. This is the union of the spaces the modelled IRs use; a concrete IR uses only the subset it needs -- a stack IR (WASM) never produces a VALUE ref, an untyped IR never produces a TYPE ref.
Related Procedures With Parameters
Result ¶
What an operation produces.
Symbol_Table ¶
Related Procedures With Parameters
Token ¶
Token :: struct { offset: u32, // byte offset in the output string length: u16, kind: Token_Kind, operation_index: u16, }
Token_Kind ¶
Token_Kind :: enum u8 { WHITESPACE, NEWLINE, OFFSET, // byte/word offset prefix KEYWORD, // `func` / `block` / `define` / `OpLabel` style keywords OPCODE, // the operation mnemonic TYPE, // a type reference / spelling (IR-only) VALUE_REF, // a use of an SSA value / local (IR-only) RESULT, // a value definition (`%3 =`) (IR-only) BLOCK_LABEL, // a basic-block / branch-target label (IR-only) GLOBAL_REF, // function / global / symbol reference (IR-only) IMMEDIATE, // literal constant ATTRIBUTE, // dialect attribute / decoration / flag (IR-only) PUNCTUATION, // `(`, `)`, `,`, `=`, `:` COMMENT, }
Related Procedures With Parameters
Type ¶
Type :: struct { fields: []Type_Ref, // STRUCT members, or FUNCTION params ++ result name: string, // OPAQUE / named struct elem: Type_Ref, // VECTOR / ARRAY / POINTER / typed REF element count: u32, // VECTOR length, literal ARRAY length, or FUNCTION param count len_ref: Id, // ARRAY length as a constant <id> (id-typed lengths, e.g. SPIR-V) bits: u16, // INT / FLOAT width aux: u16, // POINTER address space, packed kind flags, ... kind: Type_Kind, _: [3]u8, }
One node in a module's type table. fields (struct members / function
signature) is caller-owned, like the rest of the decoded module.
Related Procedures With Returns
Type_Kind ¶
Type_Kind :: enum u8 { VOID, BOOL, // a distinct boolean (SPIR-V OpTypeBool, LLVM i1) INT, // `bits` = width (1/8/16/32/64/...); signedness is op-level in most IRs FLOAT, // `bits` = width (16/32/64/128) VECTOR, // `elem` x `count` (fixed-width SIMD) ARRAY, // `elem` x `count` (literal length) or `elem` x `len_ref` (<id> length) POINTER, // `elem`, address space in `aux` STRUCT, // members in `fields` FUNCTION, // `fields` = params ++ [result]; `count` = param count OPAQUE, // named / forward-declared / abstract handle (images, tokens, ...) REF, // funcref / externref / typed GC reference (`elem` for typed refs) }
Type_Ref ¶
Type_Ref :: distinct u32
Related Procedures With Parameters
Related Procedures With Returns
Related Constants
Constants
DEFAULT_PRINT_OPTIONS ¶
DEFAULT_PRINT_OPTIONS :: Print_Options{uppercase = false, hex_prefix = "0x", hex_lowercase = true, value_prefix = "%", block_prefix = "^", show_offsets = false, indent = " ", separator = "\n"}
TYPE_NONE ¶
TYPE_NONE :: Type_Ref(0xFFFFFFFF)
Variables
This section is empty.
Procedures
operand_id ¶
Reconstruct the Id / Type_Ref carried by an operand.
print_hex ¶
print_hex :: proc(sb: ^strings.Builder, value: u64, options: ^Print_Options) {…}
print_hex_digits ¶
print_hex_digits :: proc(sb: ^strings.Builder, value: u64, options: ^Print_Options) {…}
symbol_define ¶
symbol_define :: proc(st: ^Symbol_Table, name: string, id: Id) {…}
Bind a name to an id (e.g. when a definition is emitted).
symbol_lookup ¶
symbol_lookup :: proc(st: ^Symbol_Table, name: string) -> (id: Id, ok: bool) {…}
symbol_reserve ¶
symbol_reserve :: proc(st: ^Symbol_Table, name: string) -> Id {…}
Reserve a name for a forward reference; resolve later with symbol_define.
symbol_table_destroy ¶
symbol_table_destroy :: proc(st: ^Symbol_Table) {…}
symbol_table_init ¶
symbol_table_init :: proc(st: ^Symbol_Table, space: Ref_Space = Ref_Space.EXTERNAL, allocator := context.allocator) {…}
token_kind_to_string ¶
token_kind_to_string :: proc(k: Token_Kind) -> string {…}
type_bool ¶
type_bool :: proc "contextless" () -> Type {…}
type_void ¶
type_void :: proc "contextless" () -> Type {…}
Procedure Groups
This section is empty.
Source Files
Generation Information
Generated with odin version dev-2026-07 (vendor "odin") Windows_amd64 @ 2026-07-20 21:55:30.308433000 +0000 UTC