// 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 . `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 ; 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 // referenced like any other value (an op_value / .CONSTANT ref). constants: []Constant, // --- Debug + annotations --- debug: Debug, decorations: []Decoration_Inst, // --- side tables --- // SPIR-V has one flat 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 // , 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 ) 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 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 s, not literals } // A module-level constant: OpConstant / OpConstant{True,False,Null} / OpConstantComposite. Constant :: struct { result: Result, // + type opcode: Opcode, // which OpConstant* form produced it value: u64, // scalar bit pattern (OpConstant); width comes from the type elements: []Id, // OpConstantComposite member s } // OpDecorate / OpMemberDecorate: an annotation on an (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 , 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 }