Introducing Achronyme — a language for zero-knowledge proofs. Read the announcement

VM & Bytecode

Register-based virtual machine, opcodes, and binary format.

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

Source (.ach)


Bytecode Compiler → Function prototypes + bytecode


Serializer → .achb binary file


Loader → VM heap + stack + frames


Interpreter → Register-based execution

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

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

Values are tagged 64-bit integers — no boxing for common types:

Bits 63..60 = 4-bit tag
Bits 59..0  = 60-bit payload

Tags

TagNamePayload
0INTi60 signed integer (inline)
1NIL
2FALSE
3TRUE
4STRINGu32 handle → strings arena
5LISTu32 handle → lists arena
6MAPu32 handle → maps arena
7FUNCTIONu32 handle → functions arena
8FIELDu32 handle → fields arena (BN254)
9PROOFu32 handle → proofs arena
10NATIVEu32 handle → natives table
11CLOSUREu32 handle → closures arena
12ITERu32 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

Each instruction is a u32 in one of two formats:

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

[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

Constants & Moves

OpcodeCodeFormatDescription
LoadConst0ABxR[A] = K[Bx] — load from constant pool
LoadTrue1AR[A] = true
LoadFalse2AR[A] = false
LoadNil3AR[A] = nil
Move5ABR[A] = R[B]

Arithmetic

OpcodeCodeFormatDescription
Add10ABCR[A] = R[B] + R[C]
Sub11ABCR[A] = R[B] - R[C]
Mul12ABCR[A] = R[B] * R[C]
Div13ABCR[A] = R[B] / R[C]
Mod14ABCR[A] = R[B] % R[C]
Pow15ABCR[A] = R[B] ^ R[C]
Neg16ABR[A] = -R[B]

Comparison & Logic

OpcodeCodeFormatDescription
Eq20ABCR[A] = R[B] == R[C]
Lt21ABCR[A] = R[B] < R[C]
Gt22ABCR[A] = R[B] > R[C]
NotEq23ABCR[A] = R[B] != R[C]
Le24ABCR[A] = R[B] <= R[C]
Ge25ABCR[A] = R[B] >= R[C]
LogNot26ABR[A] = !R[B]

Closures & Upvalues

OpcodeCodeFormatDescription
GetUpvalue34ABR[A] = Upvalue[B]
SetUpvalue35ABUpvalue[B] = R[A]
CloseUpvalue36AClose upvalue at stack slot A

Functions

OpcodeCodeFormatDescription
Return54AReturn R[A] from current frame
Call55ABCR[A] = Call(R[B], R[B+1]..R[B+C-1])
Closure56ABxR[A] = Closure(K[Bx])

Control Flow

OpcodeCodeFormatDescription
Jump60BxIP = Bx
JumpIfFalse61ABxIf !R[A] then IP = Bx
GetIter65ABR[A] = Iterator(R[B])
ForIter66ABxNext item or jump to Bx

Globals

OpcodeCodeFormatDescription
DefGlobalVar98ABxDefine mutable global
DefGlobalLet99ABxDefine immutable global
GetGlobal100ABxR[A] = Global[K[Bx]]
SetGlobal101ABxGlobal[K[Bx]] = R[A]
Print102APrint R[A]

Data Structures

OpcodeCodeFormatDescription
BuildList150ABCR[A] = [R[B]..R[B+C-1]]
BuildMap151ABCR[A] = {R[B]:R[B+1], ...}
GetIndex152ABCR[A] = R[B][R[C]]
SetIndex153ABCR[A][R[B]] = R[C]

ZK

OpcodeCodeFormatDescription
Prove160Compile + verify ZK circuit

Special

OpcodeCodeDescription
Nop255No operation

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)

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

Index:  0..22       23..
        Natives     User globals

The first 23 slots (0–22) are reserved for native functions. User-defined globals start at index 23 (USER_GLOBAL_START).

Source Files

ComponentFile
Opcodesvm/src/opcode.rs
VM interpretervm/src/machine/vm.rs
Call framesvm/src/machine/frame.rs
Native dispatchvm/src/specs.rs
Value encodingmemory/src/value.rs
Function structmemory/src/heap.rs
Bytecode compilercompiler/src/codegen.rs
Function compilercompiler/src/function_compiler.rs
Binary serializercli/src/commands/compile.rs
Binary loadervm/src/loader.rs
Navigation