VM & Bytecode
The Achronyme VM is a register-based bytecode interpreter. It executes compiled .achb files using a fixed-size stack of 65,536 Value slots.
Architecture
Section titled “Architecture”Source (.ach) │ ▼Bytecode Compiler → Function prototypes + bytecode │ ▼Serializer → .achb binary file │ ▼Loader → VM heap + stack + frames │ ▼Interpreter → Register-based executionVM Structure
Section titled “VM Structure”VM { heap: Heap, // typed arenas + GC stack: [Value; 65536], // fixed-size register stack frames: Vec<CallFrame>, // call stack globals: Vec<GlobalEntry>, // global variables natives: Vec<NativeObj>, // 43 built-in functions open_upvalues: linked list, // captured stack variables stress_mode: bool, // force GC every cycle}Call Frames
Section titled “Call Frames”Each function call pushes a CallFrame:
CallFrame { closure: u32, // handle to Closure on heap ip: usize, // instruction pointer base: usize, // base offset in stack dest_reg: usize, // where to store return value}Register R[i] in the current frame maps to stack[frame.base + i].
Value Representation
Section titled “Value Representation”Values are tagged 64-bit integers — no boxing for common types:
Bits 63..60 = 4-bit tagBits 59..0 = 60-bit payload| Tag | Name | Payload |
|---|---|---|
| 0 | INT | i60 signed integer (inline) |
| 1 | NIL | — |
| 2 | FALSE | — |
| 3 | TRUE | — |
| 4 | STRING | u32 handle → strings arena |
| 5 | LIST | u32 handle → lists arena |
| 6 | MAP | u32 handle → maps arena |
| 7 | FUNCTION | u32 handle → functions arena |
| 8 | FIELD | u32 handle → fields arena (BN254) |
| 9 | PROOF | u32 handle → proofs arena |
| 10 | NATIVE | u32 handle → natives table |
| 11 | CLOSURE | u32 handle → closures arena |
| 12 | ITER | u32 handle → iterators arena |
Integers (tag 0) are the most common value type — using tag 0 means no masking is needed for the common case.
Integer range: -2^59 to 2^59 - 1 (576,460,752,303,423,487). Overflow raises IntegerOverflow.
Instruction Encoding
Section titled “Instruction Encoding”Each instruction is a u32 in one of two formats:
ABC Format
Section titled “ABC Format”[opcode:8][A:8][B:8][C:8]Used for 3-operand instructions like Add R[A] = R[B] + R[C].
ABx Format
Section titled “ABx Format”[opcode:8][A:8][Bx:16]Used for instructions with a 16-bit operand, like LoadConst R[A] = K[Bx] or Jump IP = Bx.
Opcodes
Section titled “Opcodes”Constants & Moves
Section titled “Constants & Moves”| Opcode | Code | Format | Description |
|---|---|---|---|
LoadConst | 0 | ABx | R[A] = K[Bx] — load from constant pool |
LoadTrue | 1 | A | R[A] = true |
LoadFalse | 2 | A | R[A] = false |
LoadNil | 3 | A | R[A] = nil |
Move | 5 | AB | R[A] = R[B] |
Arithmetic
Section titled “Arithmetic”| Opcode | Code | Format | Description |
|---|---|---|---|
Add | 10 | ABC | R[A] = R[B] + R[C] |
Sub | 11 | ABC | R[A] = R[B] - R[C] |
Mul | 12 | ABC | R[A] = R[B] * R[C] |
Div | 13 | ABC | R[A] = R[B] / R[C] |
Mod | 14 | ABC | R[A] = R[B] % R[C] |
Pow | 15 | ABC | R[A] = R[B] ^ R[C] |
Neg | 16 | AB | R[A] = -R[B] |
Comparison & Logic
Section titled “Comparison & Logic”| Opcode | Code | Format | Description |
|---|---|---|---|
Eq | 20 | ABC | R[A] = R[B] == R[C] |
Lt | 21 | ABC | R[A] = R[B] < R[C] |
Gt | 22 | ABC | R[A] = R[B] > R[C] |
NotEq | 23 | ABC | R[A] = R[B] != R[C] |
Le | 24 | ABC | R[A] = R[B] <= R[C] |
Ge | 25 | ABC | R[A] = R[B] >= R[C] |
LogNot | 26 | AB | R[A] = !R[B] |
Closures & Upvalues
Section titled “Closures & Upvalues”| Opcode | Code | Format | Description |
|---|---|---|---|
GetUpvalue | 34 | AB | R[A] = Upvalue[B] |
SetUpvalue | 35 | AB | Upvalue[B] = R[A] |
CloseUpvalue | 36 | A | Close upvalue at stack slot A |
Functions
Section titled “Functions”| Opcode | Code | Format | Description |
|---|---|---|---|
Return | 54 | A | Return R[A] from current frame |
Call | 55 | ABC | R[A] = Call(R[B], R[B+1]..R[B+C-1]) |
Closure | 56 | ABx | R[A] = Closure(K[Bx]) |
Control Flow
Section titled “Control Flow”| Opcode | Code | Format | Description |
|---|---|---|---|
Jump | 60 | Bx | IP = Bx |
JumpIfFalse | 61 | ABx | If !R[A] then IP = Bx |
GetIter | 65 | AB | R[A] = Iterator(R[B]) |
ForIter | 66 | ABx | Next item or jump to Bx |
Globals
Section titled “Globals”| Opcode | Code | Format | Description |
|---|---|---|---|
DefGlobalVar | 98 | ABx | Define mutable global |
DefGlobalLet | 99 | ABx | Define immutable global |
GetGlobal | 100 | ABx | R[A] = Global[K[Bx]] |
SetGlobal | 101 | ABx | Global[K[Bx]] = R[A] |
Print | 102 | A | Print R[A] |
Data Structures
Section titled “Data Structures”| Opcode | Code | Format | Description |
|---|---|---|---|
BuildList | 150 | ABC | R[A] = [R[B]..R[B+C-1]] |
BuildMap | 151 | ABC | R[A] = {R[B]:R[B+1], ...} |
GetIndex | 152 | ABC | R[A] = R[B][R[C]] |
SetIndex | 153 | ABC | R[A][R[B]] = R[C] |
| Opcode | Code | Format | Description |
|---|---|---|---|
Prove | 160 | — | Compile + verify ZK circuit |
Special
Section titled “Special”| Opcode | Code | Description |
|---|---|---|
Nop | 255 | No operation |
Function Objects
Section titled “Function Objects”Each compiled function produces a Function struct on the heap:
Function { name: String, // for debugging arity: u8, // parameter count max_slots: u16, // peak register usage chunk: Vec<u32>, // bytecode instructions constants: Vec<Value>, // constant pool upvalue_info: Vec<u8>, // [is_local, index] pairs line_info: Vec<u32>, // line number per instruction}The line_info vector is parallel to chunk — each instruction has a source line number for error reporting.
Binary Format (.achb)
Section titled “Binary Format (.achb)”The .achb file format (version 9):
Magic: b"ACH\x09" (4 bytes)Metadata: max_slots (u16 LE)
Global Strings: count (u32 LE) for each: length (u32 LE) + UTF-8 bytes
Global Constants: count (u32 LE) for each: tag (u8) + payload INT (0): i64 LE STRING (1): u32 LE handle FIELD (8): 4 × u64 LE (Montgomery limbs) NIL (255): (no payload)
Prototypes: count (u32 LE) for each: name_len (u32 LE) + name bytes arity (u8) max_slots (u16 LE) const_count (u32 LE) + constants upvalue_count (u32 LE) + upvalue info bytecode_len (u32 LE) + instructions (u32 LE each)
Main Bytecode: instruction_count (u32 LE) instructions (u32 LE each)The format is compatible with the VM’s loader module, which deserializes into heap objects.
Global Variable Layout
Section titled “Global Variable Layout”Index: 0..22 23.. Natives User globalsThe first 23 slots (0–22) are reserved for native functions. User-defined globals start at index 23 (USER_GLOBAL_START).
Source Files
Section titled “Source Files”| Component | File |
|---|---|
| Opcodes | vm/src/opcode.rs |
| VM interpreter | vm/src/machine/vm.rs |
| Call frames | vm/src/machine/frame.rs |
| Native dispatch | vm/src/specs.rs |
| Value encoding | memory/src/value.rs |
| Function struct | memory/src/heap.rs |
| Bytecode compiler | compiler/src/codegen.rs |
| Function compiler | compiler/src/function_compiler.rs |
| Binary serializer | cli/src/commands/compile.rs |
| Binary loader | vm/src/loader.rs |