mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-22 23:42:33 +00:00
Add a count-only mode to the Writer (advance pos without storing) and factor the section walk into emit_module, so encode and the new encoded_size(m) share one path. encoded_size returns the exact byte count encode will produce -- the SPIR-V analog of an ISA's encode_max_code_size, here exact rather than an upper bound. The printer now sizes its scratch buffer with it instead of retry-on-overflow. Verified: encoded_size == encode byte_count; suite 10 passed.
175 lines
5.9 KiB
Odin
175 lines
5.9 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
|
|
package rexcode_spirv
|
|
|
|
import "core:fmt"
|
|
import "core:mem"
|
|
import "core:strings"
|
|
|
|
// =============================================================================
|
|
// SECTION: Printer (Module -> textual disassembly)
|
|
// =============================================================================
|
|
//
|
|
// The third ir verb. Disassembles into a spirv-dis-style listing -- a header
|
|
// comment block, then one line per instruction: `%result = OpName args`, args
|
|
// right-aligned past the result column.
|
|
//
|
|
// It works by encoding to a scratch buffer and walking the word stream
|
|
// generically through the operand-layout table, so every opcode disassembles
|
|
// with no per-op code: Id operands print as %id, LiteralString as "...", and
|
|
// everything else (literals, enums) as its numeric value for now (symbolic enum
|
|
// names are a later refinement).
|
|
|
|
// print: disassemble `m` into the caller-owned builder `sb`.
|
|
print :: proc(m: Module, sb: ^strings.Builder, options: ^Print_Options = nil) {
|
|
opts := DEFAULT_PRINT_OPTIONS
|
|
if options != nil { opts = options^ }
|
|
if opts.value_prefix == "" { opts.value_prefix = "%" }
|
|
|
|
// encode to an exactly-sized scratch buffer.
|
|
buf := make([]u8, encoded_size(m), context.temp_allocator)
|
|
relocs: [dynamic]Relocation
|
|
errors: [dynamic]Error
|
|
n, ok := encode(m, buf, &relocs, &errors)
|
|
delete(relocs); delete(errors)
|
|
if !ok {
|
|
strings.write_string(sb, "; <encode failed>\n")
|
|
return
|
|
}
|
|
|
|
words := mem.slice_data_cast([]u32, buf[:n])
|
|
ver := words[1] // header fields as actually encoded (bound is computed there)
|
|
fmt.sbprintf(sb, "; SPIR-V\n; Version: %d.%d\n; Generator: 0x%08x\n; Bound: %d\n; Schema: 0\n",
|
|
(ver >> 16) & 0xFF, (ver >> 8) & 0xFF, words[2], words[3])
|
|
wi := int(HEADER_WORDS)
|
|
for wi < len(words) {
|
|
head := words[wi]
|
|
count := int(head >> 16)
|
|
opcode := Opcode(head & 0xFFFF)
|
|
if count == 0 || wi + count > len(words) { break }
|
|
fmt_inst(sb, opcode, words[wi + 1 : wi + count], &opts)
|
|
wi += count
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
fmt_inst :: proc(sb: ^strings.Builder, opcode: Opcode, w: []u32, opts: ^Print_Options) {
|
|
run: Spec_Run
|
|
if int(opcode) < len(INSTRUCTION_INDEX) { run = INSTRUCTION_INDEX[u16(opcode)] }
|
|
|
|
args := strings.builder_make(context.temp_allocator)
|
|
result_id: u32
|
|
has_result: bool
|
|
|
|
wi, si := 0, 0
|
|
for si < int(run.count) && wi < len(w) {
|
|
spec := INSTRUCTION_SPECS[int(run.start) + si]
|
|
si += 1
|
|
if spec.kind == .IdResult {
|
|
result_id = w[wi]; has_result = true; wi += 1
|
|
continue
|
|
}
|
|
if spec.quant == .VARIADIC {
|
|
for wi < len(w) {
|
|
strings.write_byte(&args, ' ')
|
|
wi += fmt_operand(&args, spec.kind, w[wi:], opts)
|
|
}
|
|
} else {
|
|
strings.write_byte(&args, ' ')
|
|
wi += fmt_operand(&args, spec.kind, w[wi:], opts)
|
|
}
|
|
}
|
|
// trailing words beyond the fixed layout (enum-parameter operands, e.g.
|
|
// MemoryAccess Aligned's alignment) -- printed numerically.
|
|
for wi < len(w) {
|
|
fmt.sbprintf(&args, " %d", w[wi])
|
|
wi += 1
|
|
}
|
|
|
|
// right-align the "%id = " result column, then OpName + args.
|
|
COL :: 14
|
|
prefix := has_result ? fmt.tprintf("%s%d = ", opts.value_prefix, result_id) : ""
|
|
for _ in 0 ..< max(0, COL - len(prefix)) { strings.write_byte(sb, ' ') }
|
|
strings.write_string(sb, prefix)
|
|
fmt.sbprintf(sb, "%v%s\n", opcode, strings.to_string(args))
|
|
}
|
|
|
|
@(private="file")
|
|
fmt_operand :: proc(sb: ^strings.Builder, kind: Spec_Kind, w: []u32, opts: ^Print_Options) -> int {
|
|
#partial switch kind {
|
|
case .IdResultType, .IdRef, .IdScope, .IdMemorySemantics:
|
|
fmt.sbprintf(sb, "%s%d", opts.value_prefix, w[0]); return 1
|
|
case .LiteralString:
|
|
s, nwords := str_from_words(w)
|
|
fmt.sbprintf(sb, "%q", s); return nwords
|
|
case:
|
|
if fmt_enum(sb, kind, w[0]) { return 1 }
|
|
if fmt_bitenum(sb, kind, w[0]) { return 1 }
|
|
fmt.sbprintf(sb, "%d", w[0]); return 1
|
|
}
|
|
}
|
|
|
|
// Symbolic name for the common enum operand kinds (others fall back to numeric).
|
|
// ValueEnums print their member name, BitEnums their set; an out-of-range value
|
|
// prints numerically via fmt's enum handling.
|
|
@(private="file")
|
|
fmt_enum :: proc(sb: ^strings.Builder, kind: Spec_Kind, v: u32) -> bool {
|
|
#partial switch kind {
|
|
case .SourceLanguage: fmt.sbprintf(sb, "%v", Source_Language(v))
|
|
case .ExecutionModel: fmt.sbprintf(sb, "%v", Execution_Model(v))
|
|
case .AddressingModel: fmt.sbprintf(sb, "%v", Addressing_Model(v))
|
|
case .MemoryModel: fmt.sbprintf(sb, "%v", Memory_Model(v))
|
|
case .ExecutionMode: fmt.sbprintf(sb, "%v", Execution_Mode(v))
|
|
case .StorageClass: fmt.sbprintf(sb, "%v", Storage_Class(v))
|
|
case .Decoration: fmt.sbprintf(sb, "%v", Decoration(v))
|
|
case .Capability: fmt.sbprintf(sb, "%v", Capability(v))
|
|
case: return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Symbolic name for the common BitEnum operands: the set bits' names joined with
|
|
// '|', or "None" when empty (the spirv-dis convention; %v on the bit_set would
|
|
// print the verbose "bit_set[X_Bit; u32]{...}").
|
|
@(private="file")
|
|
fmt_bitenum :: proc(sb: ^strings.Builder, kind: Spec_Kind, v: u32) -> bool {
|
|
#partial switch kind {
|
|
case .FunctionControl: fmt_bits(sb, v, Function_Control_Bit)
|
|
case .MemoryAccess: fmt_bits(sb, v, Memory_Access_Bit)
|
|
case .SelectionControl: fmt_bits(sb, v, Selection_Control_Bit)
|
|
case .LoopControl: fmt_bits(sb, v, Loop_Control_Bit)
|
|
case .ImageOperands: fmt_bits(sb, v, Image_Operands_Bit)
|
|
case: return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
@(private="file")
|
|
fmt_bits :: proc(sb: ^strings.Builder, v: u32, $E: typeid) {
|
|
if v == 0 {
|
|
strings.write_string(sb, "None")
|
|
return
|
|
}
|
|
first := true
|
|
for pos in 0 ..< 32 {
|
|
if v & (u32(1) << uint(pos)) != 0 {
|
|
if !first { strings.write_byte(sb, '|') }
|
|
fmt.sbprintf(sb, "%v", cast(E)pos)
|
|
first = false
|
|
}
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
str_from_words :: proc(w: []u32) -> (s: string, nwords: int) {
|
|
buf := strings.builder_make(context.temp_allocator)
|
|
outer: for word in w {
|
|
for b in 0 ..< 4 {
|
|
c := u8(word >> uint(b * 8))
|
|
if c == 0 { break outer }
|
|
strings.write_byte(&buf, c)
|
|
}
|
|
}
|
|
return strings.to_string(buf), (len(buf.buf) + 4) / 4
|
|
}
|