Files
Odin/core/rexcode/ir/refs.odin
Brendan Punsky daa5b7cb79 rexcode: add core:rexcode/ir — the IR API layer (no concrete IR yet)
A sibling to core:rexcode/isa for the intermediate representations (WASM,
SPIR-V, LLVM bitcode + the LLVM dialects AIR/DXIL). Holds the shared
vocabulary every IR package builds on, implements no specific IR.

Design stance (see docs/ir_design.md): keep the ISA layer's spirit, but
where IRs are structurally MORE uniform than ISAs (SSA + a type system
regularize the operand/module shape), the shared core is richer. ir/ owns:

  status.odin  Error/Error_Code (shape-identical to isa.Error)
  refs.odin    Id/Ref/Ref_Space/Symbol_Table (the label analog: structural
               id references, not PC-relative byte offsets)
  types.odin   Type/Type_Ref/Type_Kind (the type table -- no ISA analog)
  module.odin  Module/Function/Block/Operation/Operand/Result/Dataflow
               (the structured model; Operation = isa.Instruction + an
               optional typed Result, opcode a u16 like Mnemonic)
  print.odin   token kinds + options + num-fmt (parallels isa.print)

Three honest concessions vs the ISA API, made explicit not inert: a
structured Module replaces the flat []Instruction; a first-class type
system; id-based entity refs replace labels. The encode/decode verbs take
a Module and drop label_defs/resolve/base_address. Dataflow hosts both the
WASM value stack and SSA; the codec is pluggable (table for WASM/SPIR-V,
bitstream for the LLVM family -- AIR/DXIL are LLVM dialects, not peers).

Package compiles; a hand-built SSA module round-trips through the types.
2026-06-18 19:03:27 -04:00

100 lines
3.7 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_ir
// =============================================================================
// REFERENCES (the IR analog of isa.labels)
// =============================================================================
//
// This is the first place the IR API genuinely diverges from the ISA API.
//
// An ISA resolves control flow as *PC-relative labels*: `Label_Definition`
// maps a label id to an instruction index and `encode()` rewrites it to a byte
// offset (isa.labels.rewrite_label_defs_to_offsets). That model is wrong for an
// IR: IR operands reference *entities by id* -- SSA results, blocks, functions,
// globals, types -- which are stable indices into the module's entity tables,
// not byte offsets, and resolve *structurally* (no PC-relative pass).
//
// So the label machinery is replaced, not re-exported. What survives in spirit:
// * a small distinct-u32 id type with an "undefined" sentinel (forward refs),
// * a name<->id table for the externally-visible symbols (the Label_Map analog).
//
// Object-file *symbol* fixups (a linker patching a function/global index) are
// still real and still produce Relocations -- but that is a codec concern,
// defined per-IR (parallel to each arch's reloc.odin), not here.
// A stable id into one of the module's entity spaces (see Ref_Space).
Id :: distinct u32
ID_NONE :: Id(0xFFFFFFFF)
// 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.
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
}
// 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.
Ref :: struct #packed {
id: Id,
space: Ref_Space,
_: [3]u8,
}
#assert(size_of(Ref) == 8)
@(require_results)
ref :: #force_inline proc "contextless" (space: Ref_Space, id: Id) -> Ref {
return Ref{id = id, space = space}
}
// -----------------------------------------------------------------------------
// Symbol table (the IR analog of isa.Label_Map: name <-> id for visible names)
// -----------------------------------------------------------------------------
Symbol_Table :: struct {
names: map[string]Id,
space: Ref_Space, // what these names address (usually FUNCTION/GLOBAL)
}
symbol_table_init :: #force_inline proc(st: ^Symbol_Table, space := Ref_Space.EXTERNAL, allocator := context.allocator) {
st.names = make(map[string]Id, allocator = allocator)
st.space = space
}
symbol_table_destroy :: #force_inline proc(st: ^Symbol_Table) {
delete(st.names)
}
// Bind a name to an id (e.g. when a definition is emitted).
symbol_define :: #force_inline proc(st: ^Symbol_Table, name: string, id: Id) {
st.names[name] = id
}
// Reserve a name for a forward reference; resolve later with symbol_define.
@(require_results)
symbol_reserve :: #force_inline proc(st: ^Symbol_Table, name: string) -> Id {
if existing, ok := st.names[name]; ok {
return existing
}
st.names[name] = ID_NONE
return ID_NONE
}
@(require_results)
symbol_lookup :: #force_inline proc(st: ^Symbol_Table, name: string) -> (id: Id, ok: bool) {
id, ok = st.names[name]
return
}