diff --git a/core/rexcode/ir/spirv/tests/roundtrip_test.odin b/core/rexcode/ir/spirv/tests/roundtrip_test.odin new file mode 100644 index 000000000..b937e6942 --- /dev/null +++ b/core/rexcode/ir/spirv/tests/roundtrip_test.odin @@ -0,0 +1,130 @@ +// rexcode ยท Brendan Punsky (dotbmp@github), original author + +// SPIR-V codec round-trip suite. For each module shape: encode -> decode -> +// re-encode and assert the two encodings are byte-identical. A byte-exact +// round-trip exercises the encoder, the decoder, the side tables, and the +// generic table-driven operation codec all at once: any drift in operand order, +// id mapping, or section layout shows up as a word mismatch. +package rexcode_spirv_tests + +import "core:fmt" +import "core:os" +import "core:slice" +import spirv ".." + +ok_count: int +fail_count: int + +@(private="file") +roundtrip :: proc(name: string, m: spirv.Module) { + buf1 := make([]u8, 64 * 1024, context.temp_allocator) + buf2 := make([]u8, 64 * 1024, context.temp_allocator) + relocs: [dynamic]spirv.Relocation; defer delete(relocs) + errors: [dynamic]spirv.Error; defer delete(errors) + + n1, ok1 := spirv.encode(m, buf1, &relocs, &errors) + if !ok1 { + fmt.printf(" [FAIL] %s: encode failed\n", name); fail_count += 1; return + } + + m2: spirv.Module + derr: [dynamic]spirv.Error; defer delete(derr) + _, ok2 := spirv.decode(buf1[:n1], &m2, &derr, context.temp_allocator) + if !ok2 { + fmt.printf(" [FAIL] %s: decode failed\n", name); fail_count += 1; return + } + + n2, ok3 := spirv.encode(m2, buf2, &relocs, &errors) + if !ok3 { + fmt.printf(" [FAIL] %s: re-encode failed\n", name); fail_count += 1; return + } + + if n1 != n2 || !slice.equal(buf1[:n1], buf2[:n2]) { + fmt.printf(" [FAIL] %s: round-trip mismatch (n1=%d n2=%d)\n", name, n1, n2) + for i := u32(0); i < max(n1, n2); i += 4 { + w1 := i < n1 ? word_at(buf1, i) : 0 + w2 := i < n2 ? word_at(buf2, i) : 0 + if w1 != w2 { fmt.printf(" [%d] %08x != %08x\n", i / 4, w1, w2) } + } + fail_count += 1 + return + } + fmt.printf(" [ok] %-22s %d bytes\n", name, n1) + ok_count += 1 +} + +@(private="file") +word_at :: proc(b: []u8, i: u32) -> u32 { + return u32(b[i]) | u32(b[i + 1]) << 8 | u32(b[i + 2]) << 16 | u32(b[i + 3]) << 24 +} + +main :: proc() { + fmt.println("==== SPIR-V codec round-trip ====") + + // (1) a void compute entry point: header + preamble + a void function. + { + m := spirv.make_module() + m.capabilities = {.Shader} + m.entry_points = {{model = .GLCompute, function = spirv.Id(4), name = "main"}} + m.types = { + {kind = .VOID}, + {kind = .FUNCTION, fields = {spirv.Type_Ref(0)}, count = 0}, + } + m.type_ids = {spirv.Id(2), spirv.Id(3)} + m.functions = { + {signature = spirv.Type_Ref(1), blocks = { + {id = spirv.Id(5), ops = {{opcode = u16(spirv.Opcode.OpReturn)}}}, + }}, + } + m.function_ids = {spirv.Id(4)} + m.bound = 6 + roundtrip("void_main", m) + } + + // (2) an int type + two scalar constants (the constant pool + OpConstant width). + { + m := spirv.make_module() + m.capabilities = {.Shader} + m.types = {{kind = .INT, bits = 32, aux = 1}} // signed i32 + m.type_ids = {spirv.Id(1)} + m.constants = { + {result = {spirv.Id(2), spirv.Type_Ref(0)}, opcode = .OpConstant, value = 10}, + {result = {spirv.Id(3), spirv.Type_Ref(0)}, opcode = .OpConstant, value = 0xDEAD_BEEF}, + } + m.bound = 4 + roundtrip("int_constants", m) + } + + // (3) a function body with a real operation (the generic operand codec): + // %6 = OpIAdd %i32 %const_a %const_b ; OpReturn + { + 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)} + m.constants = { + {result = {spirv.Id(4), spirv.Type_Ref(1)}, opcode = .OpConstant, value = 10}, + {result = {spirv.Id(5), spirv.Type_Ref(1)}, opcode = .OpConstant, value = 20}, + } + add := spirv.Operation{ + opcode = u16(spirv.Opcode.OpIAdd), + result = {spirv.Id(6), spirv.Type_Ref(1)}, + operands = {spirv.op_value(spirv.Id(4)), spirv.op_value(spirv.Id(5))}, + } + m.functions = { + {signature = spirv.Type_Ref(2), blocks = { + {id = spirv.Id(8), ops = {add, {opcode = u16(spirv.Opcode.OpReturn)}}}, + }}, + } + m.function_ids = {spirv.Id(7)} + m.bound = 9 + roundtrip("iadd_function", m) + } + + fmt.printf("\n%d passed, %d failed\n", ok_count, fail_count) + if fail_count > 0 { os.exit(1) } +}