mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-17 21:21:04 +00:00
rexcode/ir/spirv: dependency-ordered definitions + runtime arrays
- Definition order: Module.defs records the exact type/constant/global interleaving (SPIR-V's single 'types, constants, global variables' section must be dependency-ordered -- a length constant before the array type that uses it, etc.). decode records it; encode replays it for byte-exact, spec-valid output (empty defs falls back to all-types, then -constants, then -globals). emit_one_type/constant/global factored out for the replay; no encoder alloc. - OpTypeRuntimeArray: ARRAY with len_ref == ID_NONE (vs OpTypeArray's <id> length). Tests: bool_and_array sets defs so OpConstant precedes OpTypeArray (verified in disasm); runtime_array added -> 7 passed.
This commit is contained in:
@@ -49,6 +49,7 @@ Decoder :: struct {
|
||||
global_ids: [dynamic]Id,
|
||||
functions: [dynamic]Function,
|
||||
function_ids: [dynamic]Id,
|
||||
defs: [dynamic]Def, // type/constant/global definition order
|
||||
|
||||
// in-flight function / block
|
||||
in_fn: bool,
|
||||
@@ -107,10 +108,17 @@ tref :: proc(d: ^Decoder, id: u32) -> Type_Ref {
|
||||
@(private="file")
|
||||
add_type :: proc(d: ^Decoder, id: Id, t: Type) {
|
||||
d.id_to_type[id] = Type_Ref(len(d.types))
|
||||
append(&d.defs, Def{.TYPE, u32(len(d.types))})
|
||||
append(&d.types, t)
|
||||
append(&d.type_ids, id)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
add_const :: proc(d: ^Decoder, c: Constant) {
|
||||
append(&d.defs, Def{.CONSTANT, u32(len(d.constants))})
|
||||
append(&d.constants, c)
|
||||
}
|
||||
|
||||
// Decode a function-body operation generically, by its operand layout: the
|
||||
// result-type/result-id prefix from the leading specs, then one operand per
|
||||
// remaining spec (Id specs -> entity/type refs, the rest -> integer literals).
|
||||
@@ -218,6 +226,7 @@ lower :: proc(d: ^Decoder, opcode: Opcode, w: []u32) {
|
||||
case .OpTypeInt: add_type(d, Id(w[0]), Type{kind = .INT, bits = u16(w[1]), aux = u16(w[2] & 1)})
|
||||
case .OpTypeFloat: add_type(d, Id(w[0]), Type{kind = .FLOAT, bits = u16(w[1])})
|
||||
case .OpTypeVector: add_type(d, Id(w[0]), Type{kind = .VECTOR, elem = tref(d, w[1]), count = w[2]})
|
||||
case .OpTypeRuntimeArray: add_type(d, Id(w[0]), Type{kind = .ARRAY, elem = tref(d, w[1]), len_ref = ID_NONE})
|
||||
case .OpTypePointer: add_type(d, Id(w[0]), Type{kind = .POINTER, aux = u16(w[1]), elem = tref(d, w[2])})
|
||||
case .OpTypeStruct:
|
||||
fields := make([]Type_Ref, len(w) - 1)
|
||||
@@ -233,18 +242,19 @@ lower :: proc(d: ^Decoder, opcode: Opcode, w: []u32) {
|
||||
case .OpConstant:
|
||||
c := Constant{result = {Id(w[1]), tref(d, w[0])}, opcode = opcode, value = u64(w[2])}
|
||||
if len(w) > 3 { c.value |= u64(w[3]) << 32 }
|
||||
append(&d.constants, c)
|
||||
add_const(d, c)
|
||||
case .OpConstantTrue, .OpConstantFalse, .OpConstantNull:
|
||||
append(&d.constants, Constant{result = {Id(w[1]), tref(d, w[0])}, opcode = opcode})
|
||||
add_const(d, Constant{result = {Id(w[1]), tref(d, w[0])}, opcode = opcode})
|
||||
case .OpConstantComposite:
|
||||
elems := make([]Id, len(w) - 2)
|
||||
for j in 2 ..< len(w) { elems[j - 2] = Id(w[j]) }
|
||||
append(&d.constants, Constant{result = {Id(w[1]), tref(d, w[0])}, opcode = opcode, elements = elems})
|
||||
add_const(d, Constant{result = {Id(w[1]), tref(d, w[0])}, opcode = opcode, elements = elems})
|
||||
|
||||
case .OpVariable:
|
||||
if d.in_fn {
|
||||
if d.have_blk { append(&d.blk_ops, decode_operation(d, opcode, w)) }
|
||||
} else {
|
||||
append(&d.defs, Def{.GLOBAL, u32(len(d.globals))})
|
||||
append(&d.globals, Global{type = tref(d, w[0]), init = len(w) > 3 ? Id(w[3]) : ID_NONE})
|
||||
append(&d.global_ids, Id(w[1]))
|
||||
}
|
||||
@@ -337,6 +347,7 @@ decode :: proc(data: []u8, m: ^Module, errors: ^[dynamic]Error, allocator := con
|
||||
m.global_ids = d.global_ids[:]
|
||||
m.functions = d.functions[:]
|
||||
m.function_ids = d.function_ids[:]
|
||||
m.defs = d.defs[:]
|
||||
|
||||
return u32(nwords * 4), true
|
||||
}
|
||||
|
||||
@@ -170,64 +170,85 @@ tid :: #force_inline proc "contextless" (m: ^Module, t: Type_Ref) -> Id {
|
||||
// Type.aux. (ARRAY/OPAQUE/REF need a length constant / extra modelling and are
|
||||
// skipped for now.)
|
||||
@(private="file")
|
||||
emit_types :: proc "contextless" (w: ^Writer, m: ^Module) {
|
||||
for t, i in m.types {
|
||||
s := inst_begin(w)
|
||||
w_id(w, i < len(m.type_ids) ? m.type_ids[i] : ID_NONE)
|
||||
op: Opcode
|
||||
switch t.kind {
|
||||
case .VOID: op = .OpTypeVoid
|
||||
case .BOOL: op = .OpTypeBool
|
||||
case .INT: w_word(w, u32(t.bits)); w_word(w, u32(t.aux & 1)); op = .OpTypeInt
|
||||
case .FLOAT: w_word(w, u32(t.bits)); op = .OpTypeFloat
|
||||
case .VECTOR: w_id(w, tid(m, t.elem)); w_word(w, t.count); op = .OpTypeVector
|
||||
case .ARRAY: w_id(w, tid(m, t.elem)); w_id(w, t.len_ref); op = .OpTypeArray // length: a constant <id>
|
||||
case .POINTER: w_word(w, u32(t.aux)); w_id(w, tid(m, t.elem)); op = .OpTypePointer
|
||||
case .STRUCT:
|
||||
for f in t.fields { w_id(w, tid(m, f)) }
|
||||
op = .OpTypeStruct
|
||||
case .FUNCTION:
|
||||
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:
|
||||
w.pos = s // rewind the placeholder; not yet lowered
|
||||
continue
|
||||
}
|
||||
inst_end(w, s, op)
|
||||
emit_one_type :: proc "contextless" (w: ^Writer, m: ^Module, i: int) {
|
||||
t := m.types[i]
|
||||
s := inst_begin(w)
|
||||
w_id(w, i < len(m.type_ids) ? m.type_ids[i] : ID_NONE)
|
||||
op: Opcode
|
||||
switch t.kind {
|
||||
case .VOID: op = .OpTypeVoid
|
||||
case .BOOL: op = .OpTypeBool
|
||||
case .INT: w_word(w, u32(t.bits)); w_word(w, u32(t.aux & 1)); op = .OpTypeInt
|
||||
case .FLOAT: w_word(w, u32(t.bits)); op = .OpTypeFloat
|
||||
case .VECTOR: w_id(w, tid(m, t.elem)); w_word(w, t.count); op = .OpTypeVector
|
||||
case .ARRAY:
|
||||
// length: an <id> (OpTypeArray) or absent (OpTypeRuntimeArray).
|
||||
w_id(w, tid(m, t.elem))
|
||||
if t.len_ref != ID_NONE { w_id(w, t.len_ref); op = .OpTypeArray }
|
||||
else { op = .OpTypeRuntimeArray }
|
||||
case .POINTER: w_word(w, u32(t.aux)); w_id(w, tid(m, t.elem)); op = .OpTypePointer
|
||||
case .STRUCT:
|
||||
for f in t.fields { w_id(w, tid(m, f)) }
|
||||
op = .OpTypeStruct
|
||||
case .FUNCTION:
|
||||
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:
|
||||
w.pos = s // rewind the placeholder; not yet lowered
|
||||
return
|
||||
}
|
||||
inst_end(w, s, op)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
emit_constants :: proc "contextless" (w: ^Writer, m: ^Module) {
|
||||
for c in m.constants {
|
||||
s := inst_begin(w)
|
||||
w_id(w, tid(m, c.result.type))
|
||||
w_id(w, c.result.id)
|
||||
#partial switch c.opcode {
|
||||
case .OpConstant:
|
||||
t := m.types[u32(c.result.type)]
|
||||
w_word(w, u32(c.value))
|
||||
if (t.kind == .INT || t.kind == .FLOAT) && t.bits > 32 {
|
||||
w_word(w, u32(c.value >> 32)) // context-dependent number, second word
|
||||
emit_one_constant :: proc "contextless" (w: ^Writer, m: ^Module, i: int) {
|
||||
c := m.constants[i]
|
||||
s := inst_begin(w)
|
||||
w_id(w, tid(m, c.result.type))
|
||||
w_id(w, c.result.id)
|
||||
#partial switch c.opcode {
|
||||
case .OpConstant:
|
||||
t := m.types[u32(c.result.type)]
|
||||
w_word(w, u32(c.value))
|
||||
if (t.kind == .INT || t.kind == .FLOAT) && t.bits > 32 {
|
||||
w_word(w, u32(c.value >> 32)) // context-dependent number, second word
|
||||
}
|
||||
case .OpConstantComposite:
|
||||
for e in c.elements { w_id(w, e) }
|
||||
}
|
||||
inst_end(w, s, c.opcode)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
emit_one_global :: proc "contextless" (w: ^Writer, m: ^Module, i: int) {
|
||||
g := m.globals[i]
|
||||
s := inst_begin(w)
|
||||
w_id(w, tid(m, g.type)) // a pointer type
|
||||
w_id(w, i < len(m.global_ids) ? m.global_ids[i] : ID_NONE)
|
||||
w_word(w, u32(m.types[u32(g.type)].aux)) // storage class = the pointer's address space
|
||||
if g.init != ID_NONE { w_id(w, g.init) }
|
||||
inst_end(w, s, .OpVariable)
|
||||
}
|
||||
|
||||
// The "types, constants, global variables" section. When Module.defs records the
|
||||
// stream order (from decode), replay it -- byte-exact and dependency-correct.
|
||||
// Otherwise fall back to all-types, then all-constants, then all-globals.
|
||||
@(private="file")
|
||||
emit_definitions :: proc "contextless" (w: ^Writer, m: ^Module) {
|
||||
if len(m.defs) > 0 {
|
||||
for d in m.defs {
|
||||
switch d.kind {
|
||||
case .TYPE: emit_one_type(w, m, int(d.index))
|
||||
case .CONSTANT: emit_one_constant(w, m, int(d.index))
|
||||
case .GLOBAL: emit_one_global(w, m, int(d.index))
|
||||
}
|
||||
case .OpConstantComposite:
|
||||
for e in c.elements { w_id(w, e) }
|
||||
}
|
||||
inst_end(w, s, c.opcode)
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
emit_globals :: proc "contextless" (w: ^Writer, m: ^Module) {
|
||||
for g, gi in m.globals {
|
||||
s := inst_begin(w)
|
||||
w_id(w, tid(m, g.type)) // a pointer type
|
||||
w_id(w, gi < len(m.global_ids) ? m.global_ids[gi] : ID_NONE)
|
||||
w_word(w, u32(m.types[u32(g.type)].aux)) // storage class = the pointer's address space
|
||||
if g.init != ID_NONE { w_id(w, g.init) }
|
||||
inst_end(w, s, .OpVariable)
|
||||
return
|
||||
}
|
||||
for i in 0 ..< len(m.types) { emit_one_type(w, m, i) }
|
||||
for i in 0 ..< len(m.constants) { emit_one_constant(w, m, i) }
|
||||
for i in 0 ..< len(m.globals) { emit_one_global(w, m, i) }
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -331,9 +352,7 @@ encode :: proc(m: Module, code: []u8, relocs: ^[dynamic]Relocation, errors: ^[dy
|
||||
emit_preamble(&w, &m)
|
||||
emit_debug(&w, &m)
|
||||
emit_annotations(&w, &m)
|
||||
emit_types(&w, &m)
|
||||
emit_constants(&w, &m)
|
||||
emit_globals(&w, &m)
|
||||
emit_definitions(&w, &m)
|
||||
emit_functions(&w, &m)
|
||||
return w.pos, w.ok
|
||||
}
|
||||
|
||||
@@ -54,6 +54,22 @@ Module :: struct {
|
||||
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,
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
// Member index sentinel: a whole-target decoration / name (OpDecorate / OpName)
|
||||
|
||||
@@ -218,9 +218,23 @@ main :: proc() {
|
||||
}
|
||||
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 = 4}}
|
||||
// definition order: the length constant before the array type (spec-valid).
|
||||
m.defs = {{.TYPE, 0}, {.TYPE, 1}, {.CONSTANT, 0}, {.TYPE, 2}}
|
||||
roundtrip("bool_and_array", m)
|
||||
}
|
||||
|
||||
// (7) a runtime array (OpTypeRuntimeArray): ARRAY with len_ref == ID_NONE.
|
||||
{
|
||||
m := spirv.make_module()
|
||||
m.capabilities = {.Shader}
|
||||
m.types = {
|
||||
{kind = .INT, bits = 32, aux = 1},
|
||||
{kind = .ARRAY, elem = spirv.Type_Ref(0), len_ref = spirv.ID_NONE}, // int32[]
|
||||
}
|
||||
m.type_ids = {spirv.Id(1), spirv.Id(2)}
|
||||
roundtrip("runtime_array", 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