Files
Odin/core/rexcode/ir/spirv/builder.odin
Brendan Punsky 6665d5b552 rexcode/ir/spirv: generate the typed builders (both layers) from the grammar
Extend tablegen to emit builders_gen.odin -- 723 opcodes get a low-level
inst_<OpName>(buf, ...) constructor and a high-level Builder method, mapping each
grammar operand to a typed param (IdResultType->Type_Ref, IdResult->auto-allocated
Id, IdRef->Id, LiteralInteger->i64, ValueEnum/BitEnum->the typed enum, trailing
IdRef* -> []Id). builder.odin keeps just the hand-written Builder infrastructure.

Skipped (stay hand-writable, like an ISA's can_generate_builder): operands that
aren't simple typed params yet (optional, Pair* composites, LiteralString),
type-declaration opcodes (those are ir.Type), and verbs colliding with Odin
keywords/builtins (return_, switch_, size_of, ...).

Validated: a function body built via i_add / return_ encodes + round-trips
byte-exact (builder_made test) -> 10 passed.
2026-06-26 13:32:07 -04:00

66 lines
2.3 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_spirv
import "base:runtime"
// =============================================================================
// SECTION: Builder (typed instruction construction -- infrastructure)
// =============================================================================
//
// The SPIR-V analog of an ISA's mnemonic builders comes in two layers, both
// GENERATED per opcode in builders_gen.odin (by tablegen/gen.odin):
//
// * Low level -- `inst_<OpName>(buf, ...)`: a stateless, allocation-free typed
// constructor returning an Operation. The caller owns `buf`, the operand
// backing store (SPIR-V operands are a slice, so unlike an ISA's inline
// [4]Operand they cannot be owned by the returned value).
//
// * High level -- methods on the `Builder` below that own operand storage and
// allocate result <id>s; `i_add(b, ty, a, c)` appends to the current block
// and returns the new <id>. The ergonomic SSA-construction API.
//
// This file holds only the hand-written Builder infrastructure the generated
// high-level methods build on.
// Accumulates operations into the current block. `next_id` hands out fresh result
// <id>s; operand backing for each op is allocated from `alloc` (stable, unlike a
// shared growing pool). Build a function by emitting ops, then take_block into a
// Block; set Module.bound from `next_id`.
Builder :: struct {
alloc: runtime.Allocator,
next_id: u32,
ops: [dynamic]Operation, // current block
}
@(require_results)
builder_make :: proc(first_id: u32 = 1, allocator := context.allocator) -> Builder {
b: Builder
b.alloc = allocator
b.next_id = first_id
b.ops.allocator = allocator
return b
}
// Allocate a fresh result <id>.
@(require_results)
alloc_id :: proc(b: ^Builder) -> Id {
id := Id(b.next_id)
b.next_id += 1
return id
}
// Detach the accumulated operations as a block body (and reset for the next block).
@(require_results)
take_block :: proc(b: ^Builder, label: Id) -> Block {
blk := Block{id = label, ops = b.ops[:]}
b.ops = nil
b.ops.allocator = b.alloc
return blk
}
// Stable per-operation operand backing; used by the generated high-level methods.
opbuf :: #force_inline proc(b: ^Builder, n: int) -> []Operand {
return make([]Operand, n, b.alloc)
}