Files
Odin/core/rexcode/ir/spirv/module.odin
Brendan Punsky c2a5091336 rexcode/ir/spirv: round-trip opaque types (image/sampler/event/...) verbatim
Type-defining instructions the codec doesn't model structurally -- OpTypeImage,
OpTypeSampler, OpTypeSampledImage, OpTypeMatrix, OpTypeOpaque, OpTypeEvent,
OpTypeDeviceEvent, OpTypeReserveId, OpTypeQueue, OpTypePipe -- decode to
Type{.OPAQUE} plus an Opaque_Info{opcode, words} side entry (the operand words
after the result <id>) and re-emit verbatim. One generic fallback covers every
opaque SPIR-V type byte-exact, with no per-type fields.

Test: image_type (OpTypeImage, 7 operands) round-trips -> 9 passed.
2026-06-26 12:11:25 -04:00

169 lines
6.1 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_spirv
import "core:rexcode/ir"
// =============================================================================
// SECTION: Module (the SPIR-V module -- ir core + SPIR-V's own sections)
// =============================================================================
//
// `spirv.Module` is the `ir.Module` SSA core (types / globals / functions /
// symbols, dataflow = .SSA) plus the SPIR-V module-level sections the shared core
// has no slot for. A concrete IR carries those "alongside the core" per
// docs/ir_design.md §3; here that is literal -- `using base: ir.Module` embeds
// the core so `m.types`, `m.functions`, ... read straight through.
//
// SPIR-V is a *flat* stream of OpXxx, each defining an <id>. `decode` lowers it
// into this structure -- OpTypeXxx -> base.types, OpVariable(global) ->
// base.globals, OpFunction -> base.functions, OpConstant* -> the constant pool,
// and the preamble / debug / annotations -> the sections below -- and `encode`
// flattens it back in the spec's required section order.
Module :: struct {
using base: ir.Module, // types, globals, functions, symbols, dataflow, target
// --- Header ---
version: u32, // see version(); a default is supplied on encode if 0
generator: u32, // GENERATOR if 0
bound: u32, // exclusive upper bound on every <id>; recomputed on encode
// --- Preamble (in spec section order) ---
capabilities: []Capability,
extensions: []string,
ext_imports: []Ext_Import,
addressing: Addressing_Model,
memory: Memory_Model,
entry_points: []Entry_Point,
exec_modes: []Exec_Mode,
// --- Constant pool ---
// SPIR-V constants are module-level value definitions, each a result <id>
// referenced like any other value (an op_value / .CONSTANT ref).
constants: []Constant,
// --- Debug + annotations ---
debug: Debug,
decorations: []Decoration_Inst,
// --- <id> side tables ---
// SPIR-V has one flat <id> space (types, constants, globals, functions, and
// SSA results all draw from it), but ir.Type/Global/Function carry no id of
// their own. These parallel the ir core arrays and hold each entity's wire
// <id>, so decode->encode preserves them. (Results carry their own id in
// Result.id / Constant.result.id; only these three need a side table.)
type_ids: []Id, // parallel to base.types
global_ids: []Id, // parallel to base.globals
function_ids: []Id, // parallel to base.functions
// --- Definition order ---
// The interleaving of types / constants / globals as they appear in the stream.
// SPIR-V has one "types, constants, global variables" section that must be in
// dependency order (an array's length constant before the array type, a
// constant's result type before the constant, ...). decode records the order;
// encode replays it for byte-exact, spec-valid output. Empty => encode falls
// back to all-types, then all-constants, then all-globals.
defs: []Def,
// --- Opaque type detail ---
// Verbatim operand words for type-defining instructions the codec does not
// model structurally (OpTypeImage / Sampler / SampledImage / Matrix / Event /
// Pipe / ...). Parallel to base.types; the entry is meaningful only where
// types[i].kind == OPAQUE. Lets any such type round-trip byte-exact without a
// dedicated field per SPIR-V opaque kind.
opaque_info: []Opaque_Info,
}
// A node in Module.defs: which of the three definition arrays, and its index.
Def_Kind :: enum u8 { TYPE, CONSTANT, GLOBAL }
Def :: struct {
kind: Def_Kind,
index: u32,
}
// An unmodeled type-defining instruction, captured verbatim (the operand words
// after its result <id>) so it re-emits exactly.
Opaque_Info :: struct {
opcode: Opcode,
words: []u32,
}
// Member index sentinel: a whole-target decoration / name (OpDecorate / OpName)
// rather than a struct member one (OpMemberDecorate / OpMemberName).
MEMBER_NONE :: u32(0xFFFF_FFFF)
// -----------------------------------------------------------------------------
// Section element types
// -----------------------------------------------------------------------------
// OpExtInstImport: an extended instruction set, e.g. "GLSL.std.450".
Ext_Import :: struct {
result: Id,
name: string,
}
// OpEntryPoint: an execution entry into the module.
Entry_Point :: struct {
model: Execution_Model,
function: Id,
name: string,
interface: []Id, // the OpVariable <id>s forming the entry's interface
}
// OpExecutionMode / OpExecutionModeId: a mode set on an entry point.
Exec_Mode :: struct {
entry: Id,
mode: Execution_Mode,
operands: []u32, // literal operands (e.g. LocalSize x, y, z)
is_id: bool, // OpExecutionModeId: `operands` are <id>s, not literals
}
// A module-level constant: OpConstant / OpConstant{True,False,Null} / OpConstantComposite.
Constant :: struct {
result: Result, // <id> + type
opcode: Opcode, // which OpConstant* form produced it
value: u64, // scalar bit pattern (OpConstant); width comes from the type
elements: []Id, // OpConstantComposite member <id>s
}
// OpDecorate / OpMemberDecorate: an annotation on an <id> (or one of its members).
Decoration_Inst :: struct {
target: Id,
decoration: Decoration,
member: u32, // OpMemberDecorate member index; MEMBER_NONE for OpDecorate
operands: []u32, // decoration literal operands (Location = N, Binding = N, ...)
}
// The debug section.
Debug :: struct {
source_language: u32,
source_version: u32,
source_file: Id, // an OpString <id>, or ID_NONE
names: []Name, // OpName / OpMemberName
strings: []Str, // OpString
}
// OpName / OpMemberName.
Name :: struct {
target: Id,
member: u32, // OpMemberName member index; MEMBER_NONE for OpName
text: string,
}
// OpString.
Str :: struct {
result: Id,
text: string,
}
// SPIR-V is always SSA; a freshly-made module declares it so the shared verbs
// and printer pick the SSA path.
@(require_results)
make_module :: proc "contextless" () -> Module {
m: Module
m.base.dataflow = .SSA
m.version = VERSION_1_5
m.addressing = .Logical
m.memory = .GLSL450
return m
}