mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-19 16:42:33 +00:00
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.
100 lines
3.7 KiB
Odin
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
|
|
}
|