diff --git a/core/rexcode/ir/spirv/decoder.odin b/core/rexcode/ir/spirv/decoder.odin index c75aa5252..7dcfd27ba 100644 --- a/core/rexcode/ir/spirv/decoder.odin +++ b/core/rexcode/ir/spirv/decoder.odin @@ -51,13 +51,16 @@ Decoder :: struct { function_ids: [dynamic]Id, // in-flight function / block - in_fn: bool, - fn_sig: Type_Ref, - fn_id: Id, - fn_blocks: [dynamic]Block, - have_blk: bool, - blk_id: Id, - blk_ops: [dynamic]Operation, + in_fn: bool, + fn_sig: Type_Ref, + fn_id: Id, + fn_blocks: [dynamic]Block, + fn_params: [dynamic]Result, // OpFunctionParameters, attached to the entry block + first_block: bool, + have_blk: bool, + blk_id: Id, + blk_params: []Result, + blk_ops: [dynamic]Operation, } @(private="file") @@ -140,6 +143,13 @@ decode_operation :: proc(d: ^Decoder, opcode: Opcode, w: []u32) -> Operation { wi += 1 } } + // Trailing words beyond the fixed layout: the parameter operands an enum + // value/bit pulls in (MemoryAccess Aligned's alignment, etc.). Captured as + // literals -- enough to re-encode byte-exact (semantic typing is a refinement). + for wi < len(w) { + append(&ops, op_int(i64(w[wi]))) + wi += 1 + } op.operands = ops[:] return op } @@ -147,8 +157,9 @@ decode_operation :: proc(d: ^Decoder, opcode: Opcode, w: []u32) -> Operation { @(private="file") finish_block :: proc(d: ^Decoder) { if d.have_blk { - append(&d.fn_blocks, Block{id = d.blk_id, ops = d.blk_ops[:]}) + append(&d.fn_blocks, Block{id = d.blk_id, ops = d.blk_ops[:], params = d.blk_params}) d.blk_ops = nil + d.blk_params = nil d.have_blk = false } } @@ -240,10 +251,13 @@ lower :: proc(d: ^Decoder, opcode: Opcode, w: []u32) { case .OpFunction: d.in_fn = true d.fn_id = Id(w[1]); d.fn_sig = tref(d, w[3]) - d.fn_blocks = nil + d.fn_blocks = nil; d.fn_params = nil; d.first_block = true + case .OpFunctionParameter: + append(&d.fn_params, Result{id = Id(w[1]), type = tref(d, w[0])}) case .OpLabel: finish_block(d) d.have_blk = true; d.blk_id = Id(w[0]); d.blk_ops = nil + if d.first_block { d.blk_params = d.fn_params[:]; d.first_block = false } case .OpFunctionEnd: finish_function(d) diff --git a/core/rexcode/ir/spirv/encoder.odin b/core/rexcode/ir/spirv/encoder.odin index 530d0dd82..10ec5c3b6 100644 --- a/core/rexcode/ir/spirv/encoder.odin +++ b/core/rexcode/ir/spirv/encoder.odin @@ -274,7 +274,13 @@ emit_functions :: proc "contextless" (w: ^Writer, m: ^Module) { w_word(w, 0) // FunctionControl (none) w_id(w, tid(m, fn.signature)) inst_end(w, s, .OpFunction) - // (OpFunctionParameter not yet modelled in ir.Function) + // Function parameters: the entry block's params are the OpFunctionParameters, + // emitted between OpFunction and the entry OpLabel. + if len(fn.blocks) > 0 { + for p in fn.blocks[0].params { + sp := inst_begin(w); w_id(w, tid(m, p.type)); w_id(w, p.id); inst_end(w, sp, .OpFunctionParameter) + } + } for blk in fn.blocks { sl := inst_begin(w); w_id(w, blk.id); inst_end(w, sl, .OpLabel) for &op in blk.ops { emit_operation(w, m, &op) } @@ -287,10 +293,37 @@ emit_functions :: proc "contextless" (w: ^Writer, m: ^Module) { // Entry point // ----------------------------------------------------------------------------- +@(private="file") +max_id :: #force_inline proc "contextless" (cur: u32, id: Id) -> u32 { + return id != ID_NONE ? max(cur, u32(id)) : cur +} + +// The exclusive upper bound on every in the module (the header's `bound`). +@(private="file") +compute_bound :: proc "contextless" (m: ^Module) -> u32 { + hi := u32(0) + for id in m.type_ids { hi = max_id(hi, id) } + for id in m.global_ids { hi = max_id(hi, id) } + for id in m.function_ids { hi = max_id(hi, id) } + for ei in m.ext_imports { hi = max_id(hi, ei.result) } + for s in m.debug.strings { hi = max_id(hi, s.result) } + for c in m.constants { hi = max_id(hi, c.result.id) } + for fn in m.functions { + for blk in fn.blocks { + hi = max_id(hi, blk.id) + for p in blk.params { hi = max_id(hi, p.id) } + for op in blk.ops { hi = max_id(hi, op.result.id) } + } + } + return hi + 1 +} + // encode: serialize `m` into `code` in spec layout order, returning the byte -// count written. `m.bound` must be the exclusive upper bound on all s. +// count written. `bound` is computed when left 0 (else taken as-is, so a decoded +// module re-encodes with its original bound). encode :: proc(m: Module, code: []u8, relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (byte_count: u32, ok: bool) { m := m + if m.bound == 0 { m.bound = compute_bound(&m) } w := Writer{code = code, ok = true} emit_header(&w, &m) emit_preamble(&w, &m) diff --git a/core/rexcode/ir/spirv/tests/roundtrip_test.odin b/core/rexcode/ir/spirv/tests/roundtrip_test.odin index b937e6942..6e7c73644 100644 --- a/core/rexcode/ir/spirv/tests/roundtrip_test.odin +++ b/core/rexcode/ir/spirv/tests/roundtrip_test.odin @@ -125,6 +125,64 @@ main :: proc() { roundtrip("iadd_function", m) } + // (4) a function with a parameter, and bound left 0 so encode computes it: + // i32 @f(i32 %4) { %5: OpReturnValue %4 } + { + m := spirv.make_module() + m.capabilities = {.Shader} + m.types = { + {kind = .INT, bits = 32, aux = 1}, + {kind = .FUNCTION, fields = {spirv.Type_Ref(0), spirv.Type_Ref(0)}, count = 1}, + } + m.type_ids = {spirv.Id(1), spirv.Id(2)} + ret := spirv.Operation{ + opcode = u16(spirv.Opcode.OpReturnValue), + result = {id = spirv.ID_NONE}, + operands = {spirv.op_value(spirv.Id(4))}, + } + m.functions = { + {signature = spirv.Type_Ref(1), blocks = { + { + id = spirv.Id(5), + params = {{id = spirv.Id(4), type = spirv.Type_Ref(0)}}, // OpFunctionParameter + ops = {ret}, + }, + }}, + } + m.function_ids = {spirv.Id(3)} + // m.bound left 0 -> computed + roundtrip("param_function", m) + } + + // (5) an enum-parameter operand: OpLoad ... MemoryAccess Aligned 16, where the + // alignment (16) is a trailing operand pulled in by the Aligned bit. + { + m := spirv.make_module() + m.capabilities = {.Shader} + m.types = { + {kind = .VOID}, + {kind = .FLOAT, bits = 32}, + {kind = .POINTER, aux = 7, elem = spirv.Type_Ref(1)}, // Function* float + {kind = .FUNCTION, fields = {spirv.Type_Ref(0)}, count = 0}, + } + m.type_ids = {spirv.Id(1), spirv.Id(2), spirv.Id(3), spirv.Id(4)} + var := spirv.Operation{ // %7 = OpVariable %ptr Function + opcode = u16(spirv.Opcode.OpVariable), result = {spirv.Id(7), spirv.Type_Ref(2)}, + operands = {spirv.op_int(7)}, + } + load := spirv.Operation{ // %8 = OpLoad %float %7 Aligned 16 + opcode = u16(spirv.Opcode.OpLoad), result = {spirv.Id(8), spirv.Type_Ref(1)}, + operands = {spirv.op_value(spirv.Id(7)), spirv.op_int(0x2), spirv.op_int(16)}, + } + m.functions = { + {signature = spirv.Type_Ref(3), blocks = { + {id = spirv.Id(6), ops = {var, load, {opcode = u16(spirv.Opcode.OpReturn)}}}, + }}, + } + m.function_ids = {spirv.Id(5)} + roundtrip("load_aligned", m) + } + fmt.printf("\n%d passed, %d failed\n", ok_count, fail_count) if fail_count > 0 { os.exit(1) } }