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:
FormPurpose
InstructionSymbolic representation with labels, function names, and embedded immediate Values. Used by compiler output and direct VM construction.
ResolvedInstructionExecution-oriented representation with label targets, constants, and call targets resolved to numeric indices.
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:
RangeDefault bank
r0..r15clear registers
r16..secret registers
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:
Variant familyRuntime shape
Signed integersI64, I32, I16, I8
Unsigned integersU64, U32, U16, U8
Other scalarsFloat(F64), Bool, String, Unit
VM-managed referencesObject(ObjectRef), Array(ArrayRef), Foreign(ForeignObjectRef), Closure(...)
SharesShare(ShareType, ShareData)
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

InstructionOperandsEffect
NOPNo operation.
LDdest, stack_offsetLoad from current frame’s argument stack.
LDIdest, valueLoad an immediate value; serialized bytecode stores a constant index.
MOVdest, srcMove/copy a register value, including clear/secret bank transitions.
PUSHARGsrcPush a register value onto the call argument stack.
STSslot, srcStore a raw value into the frame spill area.
LDSdest, slotLoad a raw value from the frame spill area.

Arithmetic and bitwise operations

Arithmetic instructions use dest, left, right operands:
InstructionMeaning
ADDdest = left + right
SUBdest = left - right
MULdest = left * right
DIVdest = left / right
MODdest = left % right
Bitwise instructions:
InstructionMeaning
ANDdest = left & right
ORdest = left | right
XORdest = left ^ right
NOTdest = !src / bitwise not depending on value type
SHLdest = value << amount_register
SHRdest = value >> amount_register
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:
InstructionJumps when
JMPEQcomparison was equal
JMPNEQcomparison was not equal
JMPLTcomparison was less than
JMPGTcomparison was greater than
JMP jumps unconditionally to a label in symbolic form or instruction index in resolved form.

Calls and returns

InstructionPurpose
CALL nameCall a VM or foreign function. Arguments must have been pushed with PUSHARG.
RET srcReturn a register value to the caller.
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:
HexInstruction
0x00LD
0x01LDI
0x02MOV
0x03ADD
0x04SUB
0x05MUL
0x06DIV
0x07MOD
0x08AND
0x09OR
0x0AXOR
0x0BNOT
0x0CSHL
0x0DSHR
0x0EJMP
0x0FJMPEQ
0x10JMPNEQ
0x11CALL
0x12RET
0x13PUSHARG
0x14CMP
0x15JMPLT
0x16JMPGT
0x17NOP
0x18LDS
0x19STS

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