pub struct Chunk {
pub code: Vec<u8>,
pub constants: Vec<ConstValue>,
pub source_lines: Vec<u32>,
pub local_names: Vec<String>,
}Expand description
a compiled function (or top-level chunk): instruction bytes + a parallel per-byte source-line map + a constant pool indexed by u16 from the bytes.
the three vectors stay in lockstep; the Chunk::write_op /
Chunk::write_u16 / Chunk::write_i16 helpers are the only way to
append to code, so the invariant is structurally enforced.
Fields§
§code: Vec<u8>the instruction byte stream; one byte per opcode, plus per-operand
bytes per Opcode::operand_bytes.
constants: Vec<ConstValue>the per-function constant pool, indexed by u16 from CONST / FIELD /
MAKE_ENUM_VARIANT operands. append-only – a rewound CONST leaves a
dangling entry, harmless because disassemble walks instructions, not
pool entries. holds ConstValue entries; the VM converts each to a
runtime crate::value::Value when it executes the CONST.
source_lines: Vec<u32>the 1-based source line of the byte at the same index in code.
multi-byte instructions duplicate the line across operand bytes so
source_lines[i] is valid for any i in 0..code.len().
local_names: Vec<String>the source name of each local slot, indexed by slot number (the
GET_LOCAL / SET_LOCAL operand). local_names[i] is the name the
programmer wrote for slot i – "x", "sum", a for loop variable,
a match payload binding. a compiler-synthesized temporary (a hidden
for-loop array/index slot) has an empty string; the VM falls back to
slot{i} for those. codegen fills this when it finalizes a function’s
chunk; the VM’s get_state reads it to surface the in-scope variables
with their real names rather than slot indices. a chunk built by hand
(a test, a comptime body) leaves it empty – Default gives an empty
vec, so older callers are unaffected.
Implementations§
Source§impl Chunk
impl Chunk
Sourcepub fn new() -> Self
pub fn new() -> Self
construct an empty chunk. all three vectors start empty; the
lockstep invariant code.len() == source_lines.len() holds
trivially.
Sourcepub fn write_op(&mut self, op: Opcode, line: u32)
pub fn write_op(&mut self, op: Opcode, line: u32)
emit a zero-operand opcode. line is the 1-based source line of the
originating expression (looked up via LineIndex::location at the
call site). pushes one byte to code and one entry to source_lines
so the invariant code.len() == source_lines.len() is preserved.
Sourcepub fn write_u16(&mut self, v: u16, line: u32)
pub fn write_u16(&mut self, v: u16, line: u32)
emit a u16 operand, little-endian. line is the same line as the
preceding opcode – the map duplicates the line per byte so
source_lines[i] is valid for any i in 0..code.len().
Sourcepub fn write_i16(&mut self, v: i16, line: u32)
pub fn write_i16(&mut self, v: i16, line: u32)
emit a signed i16 operand for relative jump offsets. uses
two’s-complement little-endian, the same byte sequence on every
platform Rust supports. delegates to Chunk::write_u16 via a
v as u16 cast (the bit pattern is identical).
Sourcepub fn read_u16(&self, off: usize) -> u16
pub fn read_u16(&self, off: usize) -> u16
read a u16 operand at byte offset off. the inverse of
Chunk::write_u16. callers must ensure off + 2 <= code.len().
Sourcepub fn read_i16(&self, off: usize) -> i16
pub fn read_i16(&self, off: usize) -> i16
read an i16 operand at byte offset off. the inverse of
Chunk::write_i16. callers must ensure off + 2 <= code.len().
Sourcepub fn add_constant(&mut self, v: ConstValue) -> u16
pub fn add_constant(&mut self, v: ConstValue) -> u16
append a value to the constant pool and return its u16 index. the pool is append-only (no dedupe in v1 per Phase 4 research A6); determinism follows from this. the assumption is that 65536 constants per chunk is more than realistic; debug builds assert the cap, production builds keep the cast for v1. widening to u32 is a v2 concern.
Sourcepub fn last_const(&self) -> Option<(usize, u16)>
pub fn last_const(&self) -> Option<(usize, u16)>
peek the most recent CONST emission. returns (byte_offset, pool_idx)
when the last full instruction in code is a CONST + u16 operand;
None otherwise.
the constant folder in codegen uses this before emitting a binary opcode: when both operands are CONSTs the folder can compute the result at compile time.
Sourcepub fn second_to_last_const(&self) -> Option<(usize, u16)>
pub fn second_to_last_const(&self) -> Option<(usize, u16)>
peek the CONST emission BEFORE the most recent CONST. returns
(byte_offset, pool_idx) when the chunk ends in two consecutive
CONST u16 instructions; None otherwise. used by the binary-
expression constant folder to peek both operands before deciding
to fold.
Sourcepub fn rewind_last_const(&mut self) -> Option<u16>
pub fn rewind_last_const(&mut self) -> Option<u16>
rewind the most recent CONST: drop its 3 bytes from code and 3
entries from source_lines, returning the pool index. the pool
itself is NOT shrunk – the rewound entry remains in
Chunk::constants as a harmless orphan. the determinism contract
(append-only pool) depends on never mutating prior pool entries.
returns None when the chunk does not end with a CONST instruction
(i.e. Chunk::last_const returns None). callers that have
already peeked via last_const / second_to_last_const will
always receive Some; the None path exists so the WASM build
cannot panic on misuse.
Sourcepub fn emit_jump(&mut self, op: Opcode, line: u32) -> usize
pub fn emit_jump(&mut self, op: Opcode, line: u32) -> usize
emit a forward jump with a placeholder i16 operand. returns the
byte position of the FIRST operand byte so the caller can later
Chunk::patch_jump it to the real target.
uses i16::MAX as the placeholder so an unpatched offset is
visually obvious in a disassembly. callers MUST patch the jump
before completing the chunk; an unpatched jump is a codegen bug.
Sourcepub fn patch_jump(&mut self, pos: usize) -> Result<(), QalaError>
pub fn patch_jump(&mut self, pos: usize) -> Result<(), QalaError>
patch the i16 operand bytes at pos to reach the current end of
code. the offset is computed relative to the byte AFTER the
operand (i.e. code.len() - pos - 2), matching the VM’s branch
arithmetic per Crafting Interpreters chapter 23.
returns Err(QalaError::Parse { ..., "branch too far" }) when the
computed offset would not fit in i16. this is a v1 limitation
(Phase 4 research A1); widening to i32 is a v2 concern.
Sourcepub fn emit_loop(
&mut self,
loop_start: usize,
line: u32,
) -> Result<(), QalaError>
pub fn emit_loop( &mut self, loop_start: usize, line: u32, ) -> Result<(), QalaError>
emit a backward jump whose target is loop_start. unlike
Chunk::emit_jump, the offset is known up-front so no patching
is needed. the offset is computed from the byte AFTER the 2-byte
operand back to loop_start (i.e. loop_start - pos - 3 where
pos is the byte position of the JUMP opcode itself).
returns the same Err(QalaError::Parse { ..., "branch too far" })
when the offset exceeds i16 range.
Sourcepub fn disassemble(&self, program: &Program) -> String
pub fn disassemble(&self, program: &Program) -> String
produce the human-readable listing for this chunk. one line per instruction: byte offset, opcode name (uppercase), operand interpretation, source line. the format is the documented contract for Phase 6’s WASM bridge and Phase 8’s playground bytecode panel.
byte-deterministic: same input bytes produce byte-identical output on every run. no HashMap iteration, no debug formatters; constants and program-side name tables are accessed by direct index.
the program argument supplies the function-name and enum-variant-
name lookup tables; the chunk’s own constant pool supplies CONST
operand renderings.