rexcode/ir/spirv: encoded_size + measure-mode writer

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.
This commit is contained in:
Brendan Punsky
2026-06-26 13:34:53 -04:00
committed by Flāvius
parent 6665d5b552
commit d39cf98931
2 changed files with 39 additions and 24 deletions

View File

@@ -25,13 +25,18 @@ import "base:intrinsics"
// -----------------------------------------------------------------------------
Writer :: struct {
code: []u8,
pos: u32, // byte offset; always a multiple of 4
ok: bool,
code: []u8,
pos: u32, // byte offset; always a multiple of 4
ok: bool,
count_only: bool, // measure mode: advance pos without writing (encoded_size)
}
@(private="file")
w_word :: #force_inline proc "contextless" (w: ^Writer, word: u32) {
if w.count_only {
w.pos += 4
return
}
if int(w.pos) + 4 > len(w.code) {
w.ok = false
return
@@ -70,7 +75,7 @@ inst_begin :: #force_inline proc "contextless" (w: ^Writer) -> u32 {
@(private="file")
inst_end :: #force_inline proc "contextless" (w: ^Writer, start: u32, opcode: Opcode) {
if !w.ok { return }
if w.count_only || !w.ok { return }
count := (w.pos - start) / 4
intrinsics.unaligned_store(cast(^u32)&w.code[start], inst_head(count, opcode))
}
@@ -353,15 +358,33 @@ compute_bound :: proc "contextless" (m: ^Module) -> u32 {
// encode: serialize `m` into `code` in spec layout order, returning the byte
// count written. `bound` is computed when left 0 (else taken as-is, so a decoded
// module re-encodes with its original bound).
@(private="file")
emit_module :: proc "contextless" (w: ^Writer, m: ^Module) {
emit_header(w, m)
emit_preamble(w, m)
emit_debug(w, m)
emit_annotations(w, m)
emit_definitions(w, m)
emit_functions(w, m)
}
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)
emit_debug(&w, &m)
emit_annotations(&w, &m)
emit_definitions(&w, &m)
emit_functions(&w, &m)
emit_module(&w, &m)
return w.pos, w.ok
}
// encoded_size: the exact number of bytes `encode` produces for `m` -- the module
// is walked in measure mode (no writes), so a caller can size the output buffer
// precisely. The SPIR-V analog of an ISA's encode_max_code_size (here exact, not
// an upper bound).
@(require_results)
encoded_size :: proc "contextless" (m: Module) -> u32 {
m := m
if m.bound == 0 { m.bound = compute_bound(&m) }
w := Writer{count_only = true, ok = true}
emit_module(&w, &m)
return w.pos
}

View File

@@ -26,20 +26,12 @@ print :: proc(m: Module, sb: ^strings.Builder, options: ^Print_Options = nil) {
if options != nil { opts = options^ }
if opts.value_prefix == "" { opts.value_prefix = "%" }
// encode to a scratch buffer, doubling on overflow.
size := 4096
buf: []u8
n: u32
ok: bool
for {
buf = make([]u8, size, context.temp_allocator)
relocs: [dynamic]Relocation
errors: [dynamic]Error
n, ok = encode(m, buf, &relocs, &errors)
delete(relocs); delete(errors)
if ok || size >= 64 * 1024 * 1024 { break }
size *= 2
}
// 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