package core:rexcode/ir

⌘K
Ctrl+K
or
/

    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.odinError / Error_Code (shape-identical to isa.Error). * refs.odinId / Ref / Ref_Space / Symbol_Table (the label analog). * types.odinType / Type_Ref / Type_Kind (the type table). * module.odinModule / 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

    Types

    Block ¶

    Block :: struct {
    	ops:    []Operation,
    	params: []Result,
    	id:     Id,
    }
     

    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
    }

    Function ¶

    Function :: struct {
    	blocks:    []Block,
    	name:      string,
    	signature: Type_Ref,
    	// a FUNCTION type in Module.types
    	id:        Id,
    }

    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
    Related Procedures With Returns

    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.

    Operation_Flags ¶

    Operation_Flags :: distinct bit_field u8 {
    	terminator: bool | 1,
    	control:    bool | 1,
    	memory:     bool | 1,
    	_:          u8 | 5,
    }
    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
    Print_Result :: struct {
    	text:   string,
    	tokens: []Token,
    }

    Ref ¶

    Ref :: struct #packed {
    	id:    Id,
    	space: Ref_Space,
    	_:     [3]u8,
    }
     

    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 ¶

    Result :: struct #packed {
    	id:   Id,
    	// ID_NONE if the op defines no value
    	type: Type_Ref,
    }
     

    What an operation produces.

    Symbol_Table ¶

    Symbol_Table :: struct {
    	names: map[string]Id,
    	space: Ref_Space,
    }
    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"}

    ID_NONE ¶

    ID_NONE :: Id(0xFFFFFFFF)

    TYPE_NONE ¶

    TYPE_NONE :: Type_Ref(0xFFFFFFFF)

    Variables

    This section is empty.

    Procedures

    op_block ¶

    op_block :: proc "contextless" (id: Id) -> Operand {…}

    op_float ¶

    op_float :: proc "contextless" (bits: u64, w: u16) -> Operand {…}

    op_int ¶

    op_int :: proc "contextless" (v: i64) -> Operand {…}

    op_ref ¶

    op_ref :: proc "contextless" (space: Ref_Space, id: Id) -> Operand {…}

    op_type ¶

    op_type :: proc "contextless" (t: Type_Ref) -> Operand {…}

    op_value ¶

    op_value :: proc "contextless" (id: Id) -> Operand {…}

    operand_id ¶

    operand_id :: proc "contextless" (o: Operand) -> Id {…}
     

    Reconstruct the Id / Type_Ref carried by an operand.

    operand_type ¶

    operand_type :: proc "contextless" (o: Operand) -> Type_Ref {…}
    print_decimal :: proc(sb: ^strings.Builder, value: u32) {…}
    print_hex :: proc(sb: ^strings.Builder, value: u64, options: ^Print_Options) {…}
    print_hex_digits :: proc(sb: ^strings.Builder, value: u64, options: ^Print_Options) {…}

    ref ¶

    ref :: proc "contextless" (space: Ref_Space, id: Id) -> Ref {…}

    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_array ¶

    type_array :: proc "contextless" (elem: Type_Ref, len_ref: Id) -> Type {…}

    type_bool ¶

    type_bool :: proc "contextless" () -> Type {…}

    type_float ¶

    type_float :: proc "contextless" (bits: u16) -> Type {…}

    type_int ¶

    type_int :: proc "contextless" (bits: u16) -> Type {…}

    type_pointer ¶

    type_pointer :: proc "contextless" (elem: Type_Ref, address_space: u16 = 0) -> Type {…}

    type_vector ¶

    type_vector :: proc "contextless" (elem: Type_Ref, count: u32) -> 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