mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-17 21:21:04 +00:00
rexcode/ir/spirv: codec completeness -- function params, computed bound, enum-param operands
- OpFunctionParameter: the entry block's Block.params round-trip as OpFunctionParameter (emitted between OpFunction and the entry OpLabel, decoded back onto the entry block). - encode computes bound (max <id> + 1) when the caller leaves it 0; a non-zero bound (e.g. from decode) is preserved, so re-encode is stable. - Trailing operands an enum value/bit pulls in (MemoryAccess Aligned's alignment, ...) are captured on decode as literals, so enum-parameter instructions re-encode byte-exact. Tests: param_function + load_aligned added -> 5 passed.
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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 <id> 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 <id>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)
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user