diff --git a/core/rexcode/ir/spirv/decoder.odin b/core/rexcode/ir/spirv/decoder.odin index bb3e56b13..aab62afe7 100644 --- a/core/rexcode/ir/spirv/decoder.odin +++ b/core/rexcode/ir/spirv/decoder.odin @@ -50,6 +50,7 @@ Decoder :: struct { functions: [dynamic]Function, function_ids: [dynamic]Id, defs: [dynamic]Def, // type/constant/global definition order + opaque_info: [dynamic]Opaque_Info, // parallel to types; set for OPAQUE // in-flight function / block in_fn: bool, @@ -111,6 +112,17 @@ add_type :: proc(d: ^Decoder, id: Id, t: Type) { append(&d.defs, Def{.TYPE, u32(len(d.types))}) append(&d.types, t) append(&d.type_ids, id) + append(&d.opaque_info, Opaque_Info{}) // stay parallel; filled for OPAQUE +} + +// A type-defining instruction the codec doesn't model: store it as OPAQUE plus +// its verbatim operand words (after the result ) so it re-emits exactly. +@(private="file") +add_opaque :: proc(d: ^Decoder, opcode: Opcode, w: []u32) { + add_type(d, Id(w[0]), Type{kind = .OPAQUE}) + words := make([]u32, len(w) - 1) + copy(words, w[1:]) + d.opaque_info[len(d.opaque_info) - 1] = Opaque_Info{opcode = opcode, words = words} } @(private="file") @@ -238,6 +250,10 @@ lower :: proc(d: ^Decoder, opcode: Opcode, w: []u32) { for j in 0 ..< nparam { fields[j] = tref(d, w[2 + j]) } fields[nparam] = tref(d, w[1]) // return type last: fields = params ++ [result] add_type(d, Id(w[0]), Type{kind = .FUNCTION, fields = fields, count = u32(nparam)}) + case .OpTypeMatrix, .OpTypeImage, .OpTypeSampler, .OpTypeSampledImage, + .OpTypeOpaque, .OpTypeEvent, .OpTypeDeviceEvent, .OpTypeReserveId, + .OpTypeQueue, .OpTypePipe: + add_opaque(d, opcode, w) // captured verbatim -> OPAQUE case .OpConstant: c := Constant{result = {Id(w[1]), tref(d, w[0])}, opcode = opcode, value = u64(w[2])} @@ -348,6 +364,7 @@ decode :: proc(data: []u8, m: ^Module, errors: ^[dynamic]Error, allocator := con m.functions = d.functions[:] m.function_ids = d.function_ids[:] m.defs = d.defs[:] + m.opaque_info = d.opaque_info[:] return u32(nwords * 4), true } diff --git a/core/rexcode/ir/spirv/encoder.odin b/core/rexcode/ir/spirv/encoder.odin index fd1efbcd0..27a77d92f 100644 --- a/core/rexcode/ir/spirv/encoder.odin +++ b/core/rexcode/ir/spirv/encoder.odin @@ -194,7 +194,16 @@ emit_one_type :: proc "contextless" (w: ^Writer, m: ^Module, i: int) { w_id(w, tid(m, t.fields[t.count])) // return type for pi in 0 ..< int(t.count) { w_id(w, tid(m, t.fields[pi])) } op = .OpTypeFunction - case .OPAQUE, .REF: + case .OPAQUE: + // re-emit the captured instruction verbatim (image/sampler/event/...). + info := i < len(m.opaque_info) ? m.opaque_info[i] : Opaque_Info{} + if info.opcode == .OpNop { + w.pos = s // no recorded detail; cannot lower + return + } + for word in info.words { w_word(w, word) } + op = info.opcode + case .REF: w.pos = s // rewind the placeholder; not yet lowered return } diff --git a/core/rexcode/ir/spirv/module.odin b/core/rexcode/ir/spirv/module.odin index 2aeffac99..622123a1b 100644 --- a/core/rexcode/ir/spirv/module.odin +++ b/core/rexcode/ir/spirv/module.odin @@ -63,6 +63,14 @@ Module :: struct { // 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. @@ -72,6 +80,13 @@ Def :: struct { 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) diff --git a/core/rexcode/ir/spirv/tests/roundtrip_test.odin b/core/rexcode/ir/spirv/tests/roundtrip_test.odin index 07e3e1145..4c8e3d02d 100644 --- a/core/rexcode/ir/spirv/tests/roundtrip_test.odin +++ b/core/rexcode/ir/spirv/tests/roundtrip_test.odin @@ -235,6 +235,45 @@ main :: proc() { roundtrip("runtime_array", m) } + // (8) OpPhi -- SPIR-V's SSA merge (variadic value/parent-block pairs). The + // generic codec round-trips it with no special handling; non-entry blocks + // use OpPhi, not block params, so this is the whole story. + { + m := spirv.make_module() + m.capabilities = {.Shader} + m.types = { + {kind = .VOID}, {kind = .INT, bits = 32, aux = 1}, + {kind = .FUNCTION, fields = {spirv.Type_Ref(0)}, count = 0}, + } + m.type_ids = {spirv.Id(1), spirv.Id(2), spirv.Id(3)} + phi := spirv.Operation{ // %6 = OpPhi %int %7 %8 (value %7 from block %8) + opcode = u16(spirv.Opcode.OpPhi), result = {spirv.Id(6), spirv.Type_Ref(1)}, + operands = {spirv.op_value(spirv.Id(7)), spirv.op_value(spirv.Id(8))}, + } + m.functions = { + {signature = spirv.Type_Ref(2), blocks = { + {id = spirv.Id(5), ops = {phi, {opcode = u16(spirv.Opcode.OpReturn)}}}, + }}, + } + m.function_ids = {spirv.Id(4)} + roundtrip("phi", m) + } + + // (9) an opaque type captured verbatim: OpTypeImage (sampled type + 6 enum/ + // literal operands) round-trips through the Opaque_Info side table, with no + // per-image-field modelling. + { + m := spirv.make_module() + m.capabilities = {.Shader} + m.types = {{kind = .FLOAT, bits = 32}, {kind = .OPAQUE}} + m.type_ids = {spirv.Id(1), spirv.Id(2)} + m.opaque_info = { + {}, + {opcode = .OpTypeImage, words = {1, 1, 0, 0, 0, 1, 0}}, // %1, Dim 2D, ..., Unknown + } + roundtrip("image_type", m) + } + fmt.printf("\n%d passed, %d failed\n", ok_count, fail_count) if fail_count > 0 { os.exit(1) } }