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

Akron VM & Bytecode

Register-based general-purpose bytecode VM: opcodes, value encoding, and binary format.

Akron is Achronyme’s general-purpose bytecode VM — a register-based interpreter that runs .achb files produced by the bytecode compiler. It executes the Achronyme language at runtime (ach run) and evaluates prove {} blocks in VM mode.

Akron is paired with Artik, the dedicated witness-computation VM. The split is deliberate: Akron handles scripting and mutable heap state with a GC, while Artik handles side-effect-free witness math with no heap or GC.

Architecture

Source (.ach)
    |
    v
Bytecode Compiler -> Function prototypes + bytecode
    |
    v
Serializer -> .achb binary file
    |
    v
Loader -> Akron heap + stack + frames
    |
    v
Interpreter -> Register-based execution

VM Structure

Akron {
    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>,    // 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
9PROOFu32 handle -> proofs arena
10NATIVEu32 handle -> natives table
11CLOSUREu32 handle -> closures arena
12ITERu32 handle -> iterators arena
14BYTESu32 handle -> bytes arena (ProveIR constants)
15CIRCOM_HANDLEu32 handle -> circom templates

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 & Dispatch

OpcodeCodeFormatDescription
Prove160-Compile + verify ZK circuit
MethodCall161ABCMethod dispatch R[A] = R[B].method(args)
CallCircomTemplate162ABCInvoke a registered Circom template handle

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 x u64 LE (Montgomery limbs)
        BYTES (14): u32 LE length + raw bytes (ProveIR blobs)
        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 Akron’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).

Relationship to Artik

Akron and Artik are sibling VMs with disjoint roles:

AkronArtik
General-purpose scripting + prove {}Witness computation for circuits
Tagged values, 13+ type tagsField elements + fixed-width ints only
Mark-sweep GC, typed arenasNo heap, no GC
~40 opcodes, 3-operand format~25 opcodes, typed registers
Invoked by ach run, VM modeDispatched from R1CS witness-gen (WitnessOp::ArtikCall)

See Artik Witness VM for details on the witness path.

Source Files

ComponentFile
Opcodesakron/src/opcode.rs
VM interpreterakron/src/machine/vm.rs
Call framesakron/src/machine/frame.rs
Interpreter loopakron/src/machine/interpreter.rs
Native dispatchakron/src/specs.rs
Prototype methodsakron/src/machine/methods/
Value encodingmemory/src/value.rs
Function structmemory/src/heap.rs
Bytecode compilerakronc/src/codegen.rs
Function compilerakronc/src/function_compiler.rs
Binary serializercli/src/commands/compile.rs
Binary loaderakron/src/loader.rs
Navigation