From fae15847a35d5ae889dbfa65bb7274b2918f97e4 Mon Sep 17 00:00:00 2001 From: Brendan Punsky Date: Fri, 19 Jun 2026 04:10:39 -0400 Subject: [PATCH] rexcode: buffer-sizing helpers across all ISAs + naming-contract doc Roll the encode/decode buffer-sizing helpers (added for x86 in 49787b7de) out to every other ISA, and document them in the cross-arch naming contract. Per arch (arm32, arm64, mips, riscv, ppc, ppc_vle, rsp, mos6502, mos65816): - encode_max_code_size / encode_max_relocation_count now key off the []Instruction slice (were int counts); bodies unchanged (* MAX_INST_SIZE). - encode_reserve(code, relocs, instructions): grows the caller's code []u8 by length and reserves relocs by capacity; allocates no new buffers. - decode_max_instruction_count / decode_estimate_instruction_count: exact ceiling and typical estimate, keyed off the min/avg instruction size per arch (fixed-4: arm64/mips/ppc/rsp; min-2: arm32/riscv/ppc_vle; min-1: mos). - decode_reserve(instructions, inst_info, label_defs, data, exact=false). docs/cross_arch_design.md: helpers added to the naming contract. No behavior change to the existing size helpers (signature only). All 10 ISAs check + test green (x86 2282, arm32 600, arm64 461, mips 281, riscv 154, ppc 31, ppc_vle 281, rsp 70, mos6502 148, mos65816 53). --- core/rexcode/docs/cross_arch_design.md | 26 +++++++++++++++++++++++++ core/rexcode/isa/arm32/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/arm32/encoder.odin | 24 +++++++++++++++++++++-- core/rexcode/isa/arm64/decoder.odin | 26 +++++++++++++++++++++++++ core/rexcode/isa/arm64/encoder.odin | 23 ++++++++++++++++++++-- core/rexcode/isa/mips/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/mips/encoder.odin | 23 ++++++++++++++++++---- core/rexcode/isa/mos6502/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/mos6502/encoder.odin | 24 ++++++++++++++++++----- core/rexcode/isa/mos65816/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/mos65816/encoder.odin | 23 ++++++++++++++++++---- core/rexcode/isa/ppc/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/ppc/encoder.odin | 23 ++++++++++++++++++++-- core/rexcode/isa/ppc_vle/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/ppc_vle/encoder.odin | 23 ++++++++++++++++++++-- core/rexcode/isa/riscv/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/riscv/encoder.odin | 23 ++++++++++++++++++---- core/rexcode/isa/rsp/decoder.odin | 27 ++++++++++++++++++++++++++ core/rexcode/isa/rsp/encoder.odin | 23 ++++++++++++++++++---- 19 files changed, 448 insertions(+), 29 deletions(-) diff --git a/core/rexcode/docs/cross_arch_design.md b/core/rexcode/docs/cross_arch_design.md index f0ae3fe12..44ae1a075 100644 --- a/core/rexcode/docs/cross_arch_design.md +++ b/core/rexcode/docs/cross_arch_design.md @@ -178,6 +178,32 @@ print/println/aprint/tprint/bprint/fprint/wprint(+ln)( label_defs: []Label_Definition, tokens=nil, options=nil, label_names=nil) ``` +**Buffer-sizing helpers (identical names across arches).** `encode`/`decode` +allocate nothing — the caller owns every buffer. These let the caller pre-size +them so the hot path never reallocates, either as a plain size (caller manages +its own memory) or by growing the caller's own dynamic arrays directly. They +never allocate a fresh buffer; they only grow the caller's arrays, and Odin's +`reserve` no-ops when capacity already suffices. + +```odin +// size-only (caller does the resize/reserve) +encode_max_code_size(instructions: []Instruction) -> int // exact code bytes +encode_max_relocation_count(instructions: []Instruction) -> int // exact reloc ceiling +decode_max_instruction_count(data: []u8) -> int // exact instr ceiling +decode_estimate_instruction_count(data: []u8) -> int // typical estimate + +// pre-size the caller's dynamic arrays (nil to skip any; reserves on top of existing) +encode_reserve(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) +decode_reserve(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, + label_defs: ^[dynamic]Label_Definition, data: []u8, exact := false) +``` + +> `encode_reserve` grows `code` by *length* (so `code[:]` is a valid emit +> target) and reserves `relocs` by *capacity*. The decode `*_count` pair differs +> per arch by the min/typical instruction size; `decode_reserve(exact=true)` +> uses the guaranteed ceiling, otherwise the lighter estimate. Error arrays grow +> only on the failure path and are intentionally not covered. + **Register/label/print helpers:** `reg_hw reg_class reg_size register_name mnemonic_to_string label label_forward label_named label_reserve label_set`. diff --git a/core/rexcode/isa/arm32/decoder.odin b/core/rexcode/isa/arm32/decoder.odin index 0f2bf9c5d..e975455c4 100644 --- a/core/rexcode/isa/arm32/decoder.odin +++ b/core/rexcode/isa/arm32/decoder.odin @@ -545,3 +545,30 @@ unpack_operand :: proc(word: u32, enc: Operand_Encoding, ot: Operand_Type) -> Op return op_imm(0) } } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (A32 is 4 bytes, Thumb 2; minimum 2). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 2 +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/arm32/encoder.odin b/core/rexcode/isa/arm32/encoder.odin index 98072877b..065213e6d 100644 --- a/core/rexcode/isa/arm32/encoder.odin +++ b/core/rexcode/isa/arm32/encoder.odin @@ -23,8 +23,28 @@ package rexcode_arm32 MAX_INST_SIZE :: 4 -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { return n * 4 } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { return n } +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE +} + +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } +} encode :: proc( instructions: []Instruction, diff --git a/core/rexcode/isa/arm64/decoder.odin b/core/rexcode/isa/arm64/decoder.odin index 8a2deb523..2fb303ab1 100644 --- a/core/rexcode/isa/arm64/decoder.odin +++ b/core/rexcode/isa/arm64/decoder.odin @@ -658,3 +658,29 @@ reg_from_field :: #force_inline proc "contextless" ( } return Operand{reg = Register(cls | hw), kind = .REGISTER, size = 4} } + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Exact instruction-count ceiling for `data` (AArch64 instructions are 4 bytes). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 +} + +// Typical-case estimate (AArch64 is fixed 4 bytes/instruction, so this is exact). +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/arm64/encoder.odin b/core/rexcode/isa/arm64/encoder.odin index 1850be875..ae83b443d 100644 --- a/core/rexcode/isa/arm64/encoder.odin +++ b/core/rexcode/isa/arm64/encoder.odin @@ -31,8 +31,27 @@ package rexcode_arm64 MAX_INST_SIZE :: 4 -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { return n * 4 } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { return n } +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE +} +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } +} encode :: proc( instructions: []Instruction, diff --git a/core/rexcode/isa/mips/decoder.odin b/core/rexcode/isa/mips/decoder.odin index c3315ddb7..1db90eb0a 100644 --- a/core/rexcode/isa/mips/decoder.odin +++ b/core/rexcode/isa/mips/decoder.odin @@ -427,3 +427,30 @@ reg_operand :: #force_inline proc "contextless" (r: Register, ot: Operand_Type) } return Operand{reg = r, kind = .REGISTER, size = size} } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (MIPS instructions are 4 bytes). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/mips/encoder.odin b/core/rexcode/isa/mips/encoder.odin index d7c1aeb33..80492d030 100644 --- a/core/rexcode/isa/mips/encoder.odin +++ b/core/rexcode/isa/mips/encoder.odin @@ -48,14 +48,29 @@ package rexcode_mips MAX_INST_SIZE :: 4 // Upper bound on bytes emitted for `n` instructions. -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { - return n * 4 +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE } // Upper bound on pending relocations for `n` instructions // (each instruction has at most one label-referencing operand). -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { - return n +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } } // ============================================================================= diff --git a/core/rexcode/isa/mos6502/decoder.odin b/core/rexcode/isa/mos6502/decoder.odin index 85700beda..4762b0db8 100644 --- a/core/rexcode/isa/mos6502/decoder.odin +++ b/core/rexcode/isa/mos6502/decoder.odin @@ -245,3 +245,30 @@ mem_operand :: #force_inline proc "contextless" (addr: u16, ot: Operand_Type) -> size = size, } } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (shortest instruction is 1 byte). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 2 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/mos6502/encoder.odin b/core/rexcode/isa/mos6502/encoder.odin index f9cf05da9..d7254b4a8 100644 --- a/core/rexcode/isa/mos6502/encoder.odin +++ b/core/rexcode/isa/mos6502/encoder.odin @@ -31,13 +31,27 @@ import "core:rexcode/isa" MAX_INST_SIZE :: 7 // HuC6280 block transfer -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { - return n * MAX_INST_SIZE +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { - // BBR/BBS have two operands but only one is a label; cap at one per inst. - return n +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } } encode :: proc( diff --git a/core/rexcode/isa/mos65816/decoder.odin b/core/rexcode/isa/mos65816/decoder.odin index 89b9cf4fb..7f7a3e111 100644 --- a/core/rexcode/isa/mos65816/decoder.odin +++ b/core/rexcode/isa/mos65816/decoder.odin @@ -246,3 +246,30 @@ mem_operand_long :: #force_inline proc "contextless" (addr: u32, ot: Operand_Typ size = 3, } } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (shortest instruction is 1 byte). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 3 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/mos65816/encoder.odin b/core/rexcode/isa/mos65816/encoder.odin index 503701263..8a50fa4c1 100644 --- a/core/rexcode/isa/mos65816/encoder.odin +++ b/core/rexcode/isa/mos65816/encoder.odin @@ -24,12 +24,27 @@ import "core:rexcode/isa" MAX_INST_SIZE :: 4 -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { - return n * MAX_INST_SIZE +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { - return n +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } } encode :: proc( diff --git a/core/rexcode/isa/ppc/decoder.odin b/core/rexcode/isa/ppc/decoder.odin index 2111df8f6..0d550d236 100644 --- a/core/rexcode/isa/ppc/decoder.odin +++ b/core/rexcode/isa/ppc/decoder.odin @@ -300,3 +300,30 @@ unpack_operand :: proc(word: u32, enc: Operand_Encoding, ot: Operand_Type) -> Op } return op_imm(0) } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (PowerPC instructions are 4 bytes (prefixed 8); minimum 4). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/ppc/encoder.odin b/core/rexcode/isa/ppc/encoder.odin index 3ae82df23..a1ca85ace 100644 --- a/core/rexcode/isa/ppc/encoder.odin +++ b/core/rexcode/isa/ppc/encoder.odin @@ -23,8 +23,27 @@ package rexcode_ppc MAX_INST_SIZE :: 8 // prefixed instructions -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { return n * MAX_INST_SIZE } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { return n } +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE +} +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } +} encode :: proc( instructions: []Instruction, diff --git a/core/rexcode/isa/ppc_vle/decoder.odin b/core/rexcode/isa/ppc_vle/decoder.odin index 224452eff..df6939905 100644 --- a/core/rexcode/isa/ppc_vle/decoder.odin +++ b/core/rexcode/isa/ppc_vle/decoder.odin @@ -200,3 +200,30 @@ unpack_operand :: proc(word: u32, enc: Operand_Encoding, ot: Operand_Type) -> Op } return op_imm(0) } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (VLE is 2 or 4 bytes; minimum 2). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 2 +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/ppc_vle/encoder.odin b/core/rexcode/isa/ppc_vle/encoder.odin index 3c6d0debd..3a36fc070 100644 --- a/core/rexcode/isa/ppc_vle/encoder.odin +++ b/core/rexcode/isa/ppc_vle/encoder.odin @@ -14,8 +14,27 @@ package rexcode_ppc_vle MAX_INST_SIZE :: 4 -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { return n * 4 } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { return n } +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE +} +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } +} encode :: proc( instructions: []Instruction, diff --git a/core/rexcode/isa/riscv/decoder.odin b/core/rexcode/isa/riscv/decoder.odin index 04a6ed666..47aa72cd2 100644 --- a/core/rexcode/isa/riscv/decoder.odin +++ b/core/rexcode/isa/riscv/decoder.odin @@ -330,3 +330,30 @@ decode_reg :: #force_inline proc "contextless" (word: u32, shift: u8, ot: Operan reg_operand :: #force_inline proc "contextless" (r: Register) -> Operand { return Operand{reg = r, kind = .REGISTER, size = 4} } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (base is 4 bytes, compressed (C) 2; minimum 2). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 2 +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/riscv/encoder.odin b/core/rexcode/isa/riscv/encoder.odin index 6ee9cc618..62a104fe2 100644 --- a/core/rexcode/isa/riscv/encoder.odin +++ b/core/rexcode/isa/riscv/encoder.odin @@ -26,11 +26,26 @@ package rexcode_riscv MAX_INST_SIZE :: 4 -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { - return n * 4 +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { - return n +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } } encode :: proc( diff --git a/core/rexcode/isa/rsp/decoder.odin b/core/rexcode/isa/rsp/decoder.odin index 9b1635691..17ae09c5f 100644 --- a/core/rexcode/isa/rsp/decoder.odin +++ b/core/rexcode/isa/rsp/decoder.odin @@ -252,3 +252,30 @@ decode_gpr :: #force_inline proc "contextless" (word: u32, shift: u8, ot: Operan reg_operand_scalar :: #force_inline proc "contextless" (r: Register, ot: Operand_Type) -> Operand { return Operand{reg = r, kind = .REGISTER, size = 4} } + + +// ----------------------------------------------------------------------------- +// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never +// reallocates; allocates no new buffers -- only the caller's arrays grow). +// ----------------------------------------------------------------------------- + +// Instruction-count ceiling for `data` (RSP instructions are 4 bytes). +@(require_results) +decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 +} + +// Typical-case estimate of the instruction count for `data`. +@(require_results) +decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int { + return len(data) / 4 + 8 +} + +// Pre-size the caller's decode output arrays for `data` (reserves on top of any +// existing elements; nil to skip; exact=true for the ceiling, else the estimate). +decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) { + n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data) + if instructions != nil { reserve(instructions, len(instructions) + n) } + if inst_info != nil { reserve(inst_info, len(inst_info) + n) } + if label_defs != nil { reserve(label_defs, len(label_defs) + n) } +} diff --git a/core/rexcode/isa/rsp/encoder.odin b/core/rexcode/isa/rsp/encoder.odin index eaaef0ca3..92582cc07 100644 --- a/core/rexcode/isa/rsp/encoder.odin +++ b/core/rexcode/isa/rsp/encoder.odin @@ -21,12 +21,27 @@ package rexcode_rsp MAX_INST_SIZE :: 4 -encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int { - return n * 4 +encode_max_code_size :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) * MAX_INST_SIZE } -encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int { - return n +encode_max_relocation_count :: #force_inline proc "contextless" (instructions: []Instruction) -> int { + return len(instructions) +} + +// Pre-size the caller's encode outputs (code grown by length so code[:] is a +// valid emit target; relocs reserved by capacity) so the encode hot path never +// reallocates. Allocates no new buffers; pass nil to skip either array. +encode_reserve :: proc(code: ^[dynamic]u8, relocs: ^[dynamic]Relocation, instructions: []Instruction) { + if code != nil { + size := encode_max_code_size(instructions) + if len(code) < size { + resize(code, size) + } + } + if relocs != nil { + reserve(relocs, len(relocs) + encode_max_relocation_count(instructions)) + } } encode :: proc(