Skip to main content
Stoffel VM bytecode is register-based. Instructions operate on absolute frame registers, call functions by name before resolution, and are serialized into .stflb bytecode by stoffel-vm-types. Stoffel VM execution model showing bytecode flowing into the instruction dispatcher, clear and secret register spaces, runtime stores, builtins, and MPC protocol hooks.

Architecture Overview

The VM uses two related instruction forms: During function registration and bytecode loading, labels are resolved, immediates are interned into a per-function constant table, and CALL("name") becomes a numeric call-target index plus a call-target name table. The runtime then lowers resolved instructions into packed runtime instructions for dispatch. The packed runtime opcode mapping is an internal optimization and is not the same thing as the serialized opcode values shown below.

Registers

Bytecode register operands are absolute frame indices. The default layout uses register 16 as the secret-register boundary: Important details:
  • r0 is the return register.
  • The secret boundary is a layout convention, not a hard 32-register maximum.
  • Each VMFunction has a frame register count. Registration normalizes the count so it is large enough for parameters, the return register, and every referenced register.
  • The register file stores clear and secret banks separately even though bytecode operands are absolute indices.
  • Moving clear values into secret registers creates or carries share values. Moving secret values back to clear registers becomes a reveal/open operation.
  • Pending reveals are stored as register-slot state until the async MPC operation completes.

Frame memory

Each activation frame contains:
  • function name and local variable map;
  • register file;
  • captured upvalues;
  • a volatile argument stack for PUSHARG, LD, and calls;
  • a dedicated spill vector for STS / LDS;
  • a compare flag;
  • an instruction pointer;
  • optional closure metadata.
LD reads from the current frame’s argument stack. Offset 0 resolves to the top argument; negative offsets read below the top. STS and LDS do not use that argument stack. They access a stable per-frame spill area used by register allocation, so spilled values survive between call argument pushes without being confused with function-call arguments.

Value types

The VM value enum includes: Object, array, and foreign values are typed handles into VM-managed stores. They are not serialized as constants in .stflb files. Share type metadata is shape-oriented:
Useful defaults:
  • secret int: 64 bits;
  • secret bool: one-bit secret int;
  • fixed point: 64 total bits, 16 fractional bits.

Instruction set

Loading and movement

Arithmetic and bitwise operations

Arithmetic instructions use dest, left, right operands: Bitwise instructions: SHL and SHR take the shift amount from a register, not an immediate literal operand.

Comparisons and jumps

CMP left, right sets a typed compare flag:
  • Less
  • Equal
  • Greater
Conditional jumps read that flag: JMP jumps unconditionally to a label in symbolic form or instruction index in resolved form.

Calls and returns

At the end of a VM function, the runtime also treats r0 as the return register for fallthrough-style completion.

Serialized opcode values

The opcode values used by serialized bytecode are:

Bytecode format

Compiled bytecode is stored in .stflb files. The format is defined by stoffel-vm-types::compiled_binary. Current format facts:
  • magic bytes: STFL;
  • format version: 9;
  • version 9 added LDS / STS spill-slot instructions;
  • generic collection guardrail: 1,000,000 items;
  • per-function instruction guardrail: 8,000,000 instructions;
  • string/blob guardrail: 16 MiB.
Top-level serialized layout:

Constants

The constant pool supports scalar constants:
  • Unit
  • signed and unsigned integer values
  • Float
  • Bool
  • String
Complex runtime values such as objects, arrays, foreign objects, closures, and shares are created at runtime and are not serialized as constants.

Function records

A compiled function stores:
  • name;
  • parameters;
  • parameter types;
  • return type;
  • upvalues;
  • optional parent function name;
  • frame register count;
  • labels;
  • instructions.
Function names, parameter names, upvalues, and labels are length-prefixed strings. Register counts and many counts are bounded integer fields; instruction and label offsets use wider fields for large generated programs.

Client and MPC manifest

The bytecode manifest carries MPC-facing metadata used by the CLI and SDK:
  • selected MPC backend, such as HoneyBadger or AVSS;
  • selected curve/field configuration;
  • client input/output schemas by client slot;
  • static preprocessing demand estimate.
The preprocessing estimate includes counts for Beaver triples, random shares, PRandBits, PRandInts, and a dynamic flag for cases where runtime demand may exceed the static estimate.

See also