Flux Virtual Machine (FVM) — Complete Reference

Source file: fvm.py Copyright: 2026 Karac V. Thweatt Role: Experimental comptime execution backend for the Flux language compiler.


Table of Contents

  1. Overview
  2. Architecture
  3. Getting Started
  4. The .fvm File Format
  5. Type System (TTag)
  6. Stack Values (Val)
  7. Instruction Reference (Op)
  8. Memory Model
  9. Control Flow & Functions
  10. Exception Handling
  11. Structs and Arrays
  12. Type Introspection
  13. File I/O
  14. Foreign Function Interface (FFI)
  15. Inline Assembly (INLINE_ASM)
  16. Compiler Built-ins
  17. Boundary Crossing — Emit Operations
  18. String Operations
  19. Type Conversion
  20. Diagnostics
  21. Python API
  22. Serialisation and Deserialisation
  23. Error Handling
  24. Examples

1. Overview

The Flux Virtual Machine (FVM) is a stack-based bytecode interpreter that executes comptime (compile-time) code for the Flux programming language. It is not a general-purpose runtime — it runs during the compiler's compilation phase to evaluate constant expressions, generate code fragments, perform metaprogramming, and interact with the host system (file I/O, FFI, inline assembly) before the final program is emitted.

Key characteristics:


2. Architecture

flux
┌──────────────────────────────────────────────────────────┐
│                       FluxVM                             │
│                                                          │
│  ┌─────────┐  ┌──────────────┐  ┌────────────────────┐  │
│  │  Stack  │  │  Call Frames │  │  VMHeap (16 MB)    │  │
│  │ List[Val│  │ List[CallFrm]│  │  bump + free-list  │  │
│  └─────────┘  └──────────────┘  └────────────────────┘  │
│                                                          │
│  ┌──────────────────┐  ┌──────────────────────────────┐  │
│  │  _functions dict │  │  _globals dict               │  │
│  │  name->List[Instr│  │  cross-frame comptime vars   │  │
│  └──────────────────┘  └──────────────────────────────┘  │
│                                                          │
│  ┌──────────────────────────────────────────────────┐    │
│  │  emit_results  [ ('const', val), ('flux', src) ] │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘
         ↑  parse_fvm_source()
         │
    .fvm text file

Core classes:

ClassPurpose
FluxVMThe interpreter — holds stack, heap, frames, and all execution logic.
VMHeap16 MB bump-allocator with a type-tag map and a free list.
CallFrameA function activation record — instruction pointer, locals array, exception handler stack.
ValA typed value on the VM stack (tag + data + optional meta dict).
InstrA single decoded instruction (opcode + operand list + source line).
StructLayoutCached field layout for a Flux struct type (names, tags, byte offsets, sizes).
OpEnum of all opcodes.
TTagEnum of all type tags.

3. Getting Started

Running a .fvm file directly

flux
python fvm.py <program.fvm>

The CLI parses the file, creates a FluxVM, registers all func blocks, and executes the main instruction block. Any emit_results are printed to stdout.

Embedding the VM in Python

python
from fvm import FluxVM, parse_fvm_source, VMError

source = open("my_program.fvm").read()
instructions, local_count, functions, struct_layouts = parse_fvm_source(source)

vm = FluxVM(struct_layouts=struct_layouts)

for name, instrs in functions.items():
    vm.register_function(name, instrs)

try:
    result = vm.execute(instructions, local_count)
except VMError as e:
    print(f"Runtime error: {e}")

# Inspect emit results from boundary-crossing opcodes:
for entry in vm.emit_results:
    print(entry)

Optional integrations

The CLI auto-detects and loads two optional companion modules:


4. The .fvm File Format

A .fvm file is a plain UTF-8 text file. Lines beginning with # (after stripping leading whitespace) are comments. Blank lines are ignored.

Directives

locals <N>

Declares that the top-level block uses N local variable slots. Must appear before any instructions at the top level.

flux
locals 4

func <name> / endfunc

Defines a named comptime function. Instructions between func and endfunc form the function body.

flux
func add_one
    LOCAL_GET 0
    PUSH int:1
    ADD
    RET
endfunc

struct <name> <endian> <total_byte_size> <total_bits> / endstruct

Declares a struct layout used by STRUCT_NEW, STRUCT_LOAD, STRUCT_STORE.

flux
struct Point little 8 0
    field x int 0 4
    field y int 4 4
endstruct

Each field line has the format:

flux
field <name> <type_tag> <byte_offset> <byte_size>

Instruction lines

Each instruction line has the form:

flux
OPCODE [operand1] [operand2] ...

Operands are space-separated. Quoted strings may contain spaces.

Value token syntax

Values appear as operands to PUSH and are also used in serialised .fvm files.

SyntaxMeaning
int:42Signed 32-bit integer
uint:100Unsigned 32-bit integer
long:-1Signed 64-bit integer
ulong:0xFFUnsigned 64-bit integer (hex OK)
float:3.1432-bit float
double:2.71864-bit double
bool:true / bool:1Boolean
bool:false / bool:0Boolean
byte:0x41Unsigned 8-bit integer
char:65Character (8-bit)
ptr:0Pointer (heap offset)
bytes:"hello\n"Raw byte string (UTF-8, escape sequences supported)
"hello"Shorthand for bytes:"hello"
void:0Void value

PTR meta bracket (for round-tripping struct pointers):

flux
ptr:29[struct_type=Point,stack_slot,x:0,y:32]

5. Type System (TTag)

Every Val carries one of the following type tags:

TTagFlux TypePython Representation
INTint (i32)int
UINTuint (u32)int
LONGlong (i64)int
ULONGulong (u64)int
FLOATfloat (f32)float
DOUBLEdouble (f64)float
BOOLboolint (0 or 1)
BYTEbyte (u8)int
CHARchar (u8)int
DATAdata<N> (arbitrary bit-width)int
PTRpointerint (heap offset or OS address)
VOIDvoid0
STRUCTstruct valuestr (type name), fields in meta['fields']
ENUMenum valueint or str discriminant
ARRAYarray valueint (count), elements in meta['elements']
BYTESraw byte bufferbytes or bytearray
FFI_LIBloaded native librarystr (library path)
FFI_SYMsymbol in a native libraryctypes callable
FILEopen file handleint (handle ID)

Integer width semantics

Arithmetic and bitwise results are wrapped to the bit width implied by the value's TTag:

This matches Flux's fixed-width integer overflow semantics.


6. Stack Values (Val)

python
@dataclass
class Val:
    tag:  TTag
    data: Any
    meta: Dict[str, Any] = {}

The meta dict carries optional auxiliary information:

KeyUsed byMeaning
struct_typeSTRUCTName of the Flux struct type
fieldsSTRUCTdict[str, Val] — field values
stack_slotPTRFlag: pointer addresses a frame local slot
field_bit_offsetsPTR/STRUCTdict[str, int] — bit offsets of fields
elem_typeARRAY/PTRElement type name string
countARRAY/PTRNumber of elements
elem_sizeARRAYSize of each element in bytes
elementsARRAYlist[Val] — element values
sym_nameFFI_SYMSymbol name string

7. Instruction Reference (Op)

Stack Operations

OpcodeOperandsStack effectDescription
PUSHvalue-- valPush a typed literal value onto the stack.
POPval --Discard the top stack value.
DUPa -- a aDuplicate the top value.
SWAPa b -- b aSwap the top two values.
ROTa b c -- b c aRotate the top three values: third rises to top.
OVERa b -- a b aCopy the second-from-top onto the stack.

Arithmetic Operations

All binary arithmetic ops pop two values and push the result, preserving the type tag of the first (left) operand and wrapping to its integer bit width.

OpcodeStack effectDescription
ADDa b -- a+bAddition.
SUBa b -- a-bSubtraction.
MULa b -- a*bMultiplication.
DIVa b -- a/bDivision (integer truncation for integer types; raises VMError on divide-by-zero).
MODa b -- a%bModulo.
NEGa -- -aUnary negation.
POWa b -- a**bExponentiation.
ABS`a -- \a\`Absolute value.
MINa b -- min(a,b)Minimum of two values.
MAXa b -- max(a,b)Maximum of two values.
CLAMPval lo hi -- clampedClamp val to [lo, hi]. Pops hi first, then lo, then val.

Special: BYTES + int → pointer arithmetic. When adding an integer offset to a BYTES value, the bytes are pinned in native memory and a PTR to bytes_base + offset is returned. This enables C-style string/buffer pointer arithmetic.

Bitwise Operations

OpcodeOperandsStack effectDescription
BANDa b -- a&bBitwise AND.
BOR`a b -- a\b`Bitwise OR.
BXORa b -- a^bBitwise XOR.
BNOTa -- ~aBitwise NOT (complement).
SHLa b -- a<<bShift left.
SHRa b -- a>>bShift right (arithmetic for signed types).
ROTLwidtha -- rotl(a,width)Rotate left within width bits.
ROTRwidtha -- rotr(a,width)Rotate right within width bits.
BITREVwidtha -- bitrev(a,width)Reverse bits within width bits.
POPCOUNTa -- popcount(a)Count set bits (operates on low 64 bits).
CLZwidtha -- clz(a,width)Count leading zeros within width bits.
CTZwidtha -- ctz(a,width)Count trailing zeros within width bits.

Comparison Operations

All comparisons pop two values and push a BOOL (1 or 0).

OpcodeDescription
CMP_EQEqual (==)
CMP_NENot equal (!=)
CMP_LTLess than (<)
CMP_LELess than or equal (<=)
CMP_GTGreater than (>)
CMP_GEGreater than or equal (>=)

Logic Operations

OpcodeStack effectDescription
ANDa b -- bool(a and b)Logical AND (truthy check).
ORa b -- bool(a or b)Logical OR (truthy check).
NOTa -- bool(not a)Logical NOT.

8. Memory Model

The FVM has two memory spaces:

1. VM Heap — a private 16 MB bytearray managed by VMHeap. Allocations use a bump-pointer with a free list for reuse. Pointer values into this heap are small integers (byte offsets). The null pointer is offset 0; allocations start at offset 8.

2. OS memory — native addresses from extern calls (e.g. malloc), FFI, or pinned BYTES buffers. These are tracked in _os_ptrs and accessed via ctypes.

Memory Instructions

OpcodeOperandsStack effectDescription
ALLOCsize -- ptrPop integer byte size, allocate that many bytes on the VM heap, push the pointer offset.
FREEptr --Pop a pointer and return its allocation to the free list.
LOADttag byte_sizeptr -- valPop a pointer, read byte_size bytes from the VM heap (or OS memory), and push a Val with tag ttag.
STOREttag byte_sizeval ptr --Pop a pointer and a value, serialise the value to byte_size bytes, and write to memory.
OFFSETptr n -- ptr+nAdd integer n to pointer ptr (pointer arithmetic).

LOCAL_DEREF — pop a PTR(slot) or OS address, push the value it points to. If the address is an OS pointer, reads one byte. If it is a slot index, returns locals[slot].

LOCAL_DEREF_SET — pop a value and a pointer, store the value at the pointer location.


9. Control Flow & Functions

Jumps

All jump addresses are zero-based instruction indices within the current function's instruction list.

OpcodeOperandsStack effectDescription
JMPaddrUnconditional jump to instruction addr.
JIFaddrcond --Pop cond; jump to addr if cond is truthy.
JNFaddrcond --Pop cond; jump to addr if cond is falsy.
JTABLEdefault addr0 addr1 ...idx --Pop integer index; jump to addrs[idx], or default if out of range.
HALTStop execution. The top-of-stack value becomes the return value of execute(). Clears all frames.
RETReturn from the current function frame. The return value is whatever is on top of the stack.

Function Calls

OpcodeOperandsStack effectDescription
CALLname argcargN .. arg1 -- retvalPop argc arguments (last-pushed = first arg), push a new CallFrame for function name, execute it.
CALL_PTRargcname argN .. arg1 -- retvalPop a BYTES function name string, then call it with argc arguments, same as CALL.
TAIL_SELFargcargN .. arg1 --Self tail-call optimisation: pop argc args, reload into locals[0..argc-1], reset ip to 0. No new frame is allocated.

Overload resolution:

Namespace-qualified names:

If name is not found exactly, the VM tries a suffix match against names like standard__datetime__dt_from_unix_ms when calling dt_from_unix_ms.

Locals and Globals

OpcodeOperandsStack effectDescription
LOCAL_GETslot-- valPush locals[slot] of the current frame.
LOCAL_SETslotval --Pop and store into locals[slot].
GLOBAL_GETname-- valPush vm._globals[name] (or int:0 if absent).
GLOBAL_SETnameval --Pop and store into vm._globals[name].

Globals persist across function calls and across multiple execute() invocations on the same VM instance.


10. Exception Handling

The FVM supports comptime try/catch/throw.

OpcodeOperandsStack effectDescription
TRY_BEGINcatch_addrRegister a catch handler at catch_addr. Records the current stack depth.
TRY_ENDRemove the innermost exception handler (normal exit from a try body).
THROWval --Pop a value and raise FluxThrowSignal(val). The VM walks the frame stack looking for a TRY_BEGIN handler. If found, the stack is trimmed to the recorded depth, the thrown value is pushed, and execution jumps to catch_addr. If no handler exists, a VMError is raised.

Example pattern:

flux
TRY_BEGIN 10        # catch_addr = instruction 10
PUSH bytes:"hello"
THROW               # throws the string value
TRY_END
JMP 12              # skip catch block
# instruction 10: catch handler
COMPILER_PRINT      # print the caught exception
# instruction 12:
HALT

11. Structs and Arrays

Struct Operations

Struct values are stored as Val(TTag.STRUCT, type_name, meta={'fields': {...}}).

OpcodeOperandsStack effectDescription
STRUCT_NEWtype_name-- struct_valCreate a zero-initialised struct of the given type. Requires the type to be in struct_layouts.
STRUCT_LOADfield_namestruct -- field_valPop a struct value, push the value of the named field.
STRUCT_STOREfield_namenew_val struct -- updated_structPop a value and a struct, return an updated struct with the named field replaced. (Structs are immutable values; STRUCT_STORE produces a new copy.)

Enum Operations

Enum values are stored as Val(TTag.ENUM, discriminant).

OpcodeOperandsStack effectDescription
ENUM_NEWtype_name-- enum_valPush a zero enum value with the given type name in meta.
ENUM_LOADenum -- int_or_strPop an enum, push its integer or string discriminant.
ENUM_STOREval enum -- updated_enumPop an integer or string, pop an enum, push an updated enum with the new discriminant.

Array Operations

Array values are stored as Val(TTag.ARRAY, count, meta={'elem_type': ..., 'elements': [...]}).

OpcodeOperandsStack effectDescription
ARRAY_NEWtype_name count-- array_valAllocate an array of count zero-initialised elements of type_name.
ARRAY_LENarr -- intPop an array, push its element count.
ARRAY_LOADarr idx -- elemPop an array and an index, push the element at that index. Also supports native pointer indexing (OS and VM heap).
ARRAY_STOREval idx arr -- updated_arrPop a value, index, and array, push an updated array with element [idx] replaced.

Native pointer indexing: if the array value is a PTR (e.g. from ALLOC or extern malloc), ARRAY_LOAD reads one byte at ptr + idx. ARRAY_STORE writes one byte.


12. Type Introspection

OpcodeOperandsStack effectDescription
SIZEOFtype_name-- uintPush the size of the type in bits (not bytes).
TYPEOFval -- val bytesPeek at the top value, push its TTag name as a BYTES value. Does not consume the original value.
ALIGNOFtype_name-- uintPush the alignment requirement in bytes. For structs, this is the size of the largest field.
ENDIANOFtype_name-- bytesPush bytes:"little" or bytes:"big" depending on the type's declared endianness.

13. File I/O

The FVM exposes low-level file handles.

OpcodeStack effectDescription
IO_OPENpath mode -- handlePop a mode string and a path string, open the file, push a FILE handle. Mode strings follow Python open() conventions ("r", "wb", etc.).
IO_READhandle size -- bytesPop a handle and a byte count (0 = read all), push the read bytes as a BYTES value.
IO_WRITEhandle ptr size --Pop a handle, a VM heap pointer, and a byte count; write that many bytes from the heap to the file.
IO_CLOSEhandle --Close and release the file handle.

Higher-level wrappers are also available via the compiler built-ins COMPILER_READFILE and COMPILER_WRITEFILE (see §16).


14. Foreign Function Interface (FFI)

The FVM can load native shared libraries and call their functions at comptime.

FFI Instructions

OpcodeOperandsStack effectDescription
FFI_LOAD"lib_path"-- ffi_libLoad the shared library at the given path using ctypes.CDLL, push an FFI_LIB handle.
FFI_SYMsym_nameffi_lib -- ffi_symPop an FFI_LIB, look up the named symbol, push an FFI_SYM callable.
FFI_CALLargc ret_ttagffi_sym argN .. arg1 -- resultPop the symbol and argc arguments, call the native function, push the result with tag ret_ttag.
FFI_FREEffi_lib --Unload the library and discard the handle.

Extern Declarations

The EXTERN_DECL opcode registers a function name so it can be called via CALL without an explicit FFI_LOAD/FFI_SYM cycle. The VM resolves the symbol through explicitly loaded libraries (compiler.fvm.loadlib) and then falls back to the C runtime.

flux
EXTERN_DECL malloc PTR

Calls to malloc via CALL malloc 1 will then dispatch through ctypes.

Argument marshalling

TTagCtypes type
INTc_int32
UINTc_uint32
LONGc_int64
ULONGc_uint64
FLOATc_float
DOUBLEc_double
BOOLc_bool
BYTEc_uint8
PTRc_void_p

15. Inline Assembly (INLINE_ASM)

Requires: keystone-engine (pip install keystone-engine). Only supported on x86-64 hosts.

Syntax

flux
INLINE_ASM "body" "constraints" n_inputs n_outputs [output_names...]

Operand placeholders

Input values are loaded into scratch registers (%r10%r15) via movabsq immediates before the user body runs. The register size is inferred from the instruction mnemonic suffix (l→32-bit, w→16-bit, b→8-bit, else 64-bit).

External calls

call symbol in the body is automatically resolved to a movabsq $addr, %rax; callq *%rax sequence, searching explicitly loaded libs (via compiler.fvm.loadlib) before falling back to the C runtime.

Stack frame

The generated code includes a standard function prologue (pushq %rbp; movq %rsp, %rbp) and epilogue (popq %rbp; retq). Callee-saved scratch registers (%r12%r15) are preserved if used.

Example

flux
PUSH int:42
INLINE_ASM "movq $1, $0\naddq $1, $0" "" 1 1 result
# Pops 42 as an input, computes rax = rax + 42 (where rax starts as 1), pushes the result.

16. Compiler Built-ins

These opcodes implement the compiler.* namespace that Flux comptime code uses to interact with the compilation environment.

Console I/O

OpcodeStack effectDescription
COMPILER_PRINTval --Print a value to stdout. Handles BYTES, PTR, CHAR, or any scalar (via str()).
COMPILER_INPUT-- bytesRead one line from stdin, push as BYTES.

File I/O

OpcodeStack effectDescription
COMPILER_READFILEpath -- ptrRead the entire file at path into the VM heap, push a PTR with meta['count'] set to the file size.
COMPILER_WRITEFILEpath content flags --Write content (PTR or BYTES) to path. flags is one of: "r", "w", "a", "rw" (optionally suffixed with t for text mode).

Debugging and Diagnostics

OpcodeStack effectDescription
COMPILER_FVM_TRACE_BEGINEnable per-instruction tracing to stdout for the next 2000 instructions. Prints opcode, operands, locals, and top-4 stack at each step.
COMPILER_FVM_TRACE_ENDDisable per-instruction tracing.
COMPILER_FVM_SETBPComptime breakpoint: print current function, instruction pointer, all locals, and the full stack to stderr, then pause execution until the user presses Enter.
COMPILER_FVM_DUMPpath --Serialise the current comptime state (all instructions logged so far + functions + struct layouts) to a .fvm file at path.

Imports and Packages

OpcodeStack effectDescription
COMPILER_IMPORT_STDLIBpath --Import a standard library module (resolved relative to the compiler's stdlib root). Requires _codegen_class to be injected.
COMPILER_IMPORT_LOCALpath --Import a local Flux source file (resolved relative to source_file). Requires _codegen_class.
COMPILER_FPM_PACKAGEpath --Install or resolve an FPM (Flux Package Manager) package. Requires _codegen_class.
COMPILER_LOADLIBname ext --Load a native shared library by name and extension (e.g. "mylib" "so"). The library is made available for INLINE_ASM symbol resolution and EXTERN_DECL calls. Platform-specific candidates are tried automatically (lib<name>.<ext>, lib<name>.so, lib<name>.dylib).

17. Boundary Crossing — Emit Operations

These opcodes pass comptime-computed values back to the compiler's code-generation layer. Results are accumulated in vm.emit_results as a list of tuples.

OpcodeStack effectEmit entryDescription
EMIT_CONSTval --('const', val)Emit a comptime value as an IR constant.
EMIT_GLOBALsize -- bytes('global', bytes)Pop a size, snapshot that many bytes from the VM heap starting at the current bump pointer (or per the operand), emit as a global variable.
EMIT_TYPEval --('type', val)Emit a type reference. The value should be a type tag.
EMITFLUXsrc_text var_names('flux', substituted_text)Substitute comptime local values into src_text using {varname:slot} tokens, then emit the resulting Flux source fragment.

EMITFLUX operand format

flux
EMITFLUX "const N = {value:0};" value:0

Each name:slot pair substitutes the value from locals[slot] into the placeholder {name} in the source text.


18. String Operations

All string ops work on BYTES values (UTF-8 byte strings).

OpcodeStack effectDescription
STR_LENbytes -- intPush the byte length of the top string.
STR_CATa b -- abConcatenate two strings (pushes BYTES).
STR_SLICEstr start len -- substrPop a string, a start index, and a length; push the substring.
STR_EQa b -- boolPush 1 if two strings are equal, else 0.
STR_FINDhaystack needle -- intPop needle then haystack, push byte offset of first occurrence, or −1 if not found.
INT_TO_STRint -- bytesConvert an integer to its decimal string representation.
STR_TO_INTbytes -- intParse a decimal string to an integer.

19. Type Conversion

OpcodeOperandsStack effectDescription
CASTttag [hint]val -- val'Convert the top value to the target TTag. Handles numeric widening/narrowing, bool↔numeric, bytes↔numeric (UTF-8 decode/encode), and struct/array identity casts. An optional hint string provides additional context.
BITCASTttagval -- val'Reinterpret the bytes of the top value as the target TTag without converting. E.g. bitcasting float:1.0 to uint yields the IEEE 754 bit pattern.

20. Diagnostics

OpcodeStack effectDescription
ASSERTval --Pop a value; if it is falsy, abort with VMError: comptime assertion failed.
WARNstr --Pop a string and print it as a compiler warning to stderr.
PANICstr --Pop a string and unconditionally abort with VMError.

21. Python API

FluxVM

python
class FluxVM:
    def __init__(
        self,
        struct_layouts: Dict[str, StructLayout] = None,
        type_sizes:     Dict[str, int]          = None,
        heap_size:      int                     = 16 * 1024 * 1024,
        source_file:    str                     = None,
    ): ...
ParameterDescription
struct_layoutsDict of struct name → StructLayout, pre-populated by the parser or compiler.
type_sizesOptional override dict of type name → byte size for custom types.
heap_sizeComptime heap size in bytes (default 16 MB).
source_filePath of the source file being compiled; used to resolve relative imports.

Public methods:

python
vm.register_function(name: str, instructions: List[Instr])

Register a comptime function by name. Must be called before execute() if the instructions call this function.

python
vm.execute(instructions: List[Instr], local_count: int = 0) -> Optional[Val]

Execute a flat instruction list as a top-level comptime block. Returns the top-of-stack Val after HALT, or None if the stack is empty.

Public attributes after execute():

AttributeTypeDescription
vm.emit_resultsList[tuple]Accumulated emit operations from EMIT_CONST, EMIT_GLOBAL, EMIT_TYPE, EMITFLUX.
vm.last_localsList[Val]Snapshot of the top-level frame's locals after the last HALT.
vm.stackList[Val]The value stack (may be non-empty if HALT was reached mid-stack).
vm._globalsdictComptime global variables (persist between execute() calls).

Injected attributes (optional, set by the host compiler):

AttributeDescription
vm._codegen_classCompiler's code-generation class, used by compiler.import.*.
vm._compiler_constantsDict of preprocessor #def constants from fmacros.

parse_fvm_source

python
def parse_fvm_source(source: str) -> Tuple[
    List[Instr],
    int,
    Dict[str, List[Instr]],
    Dict[str, StructLayout],
]:

Parse a .fvm text file. Returns (instructions, local_count, functions, struct_layouts).

Raises FVMParseError with a line number on any syntax error.

serialise_fvm

python
def serialise_fvm(
    instructions: List[Instr],
    local_count:  int,
    functions:    Dict[str, List[Instr]],
    struct_layouts: Dict[str, StructLayout] = None,
) -> str:

Serialise a set of instructions and function definitions back to .fvm text. This is the inverse of parse_fvm_source(), used by COMPILER_FVM_DUMP.

VMHeap

python
class VMHeap:
    DEFAULT_SIZE = 16 * 1024 * 1024

    def alloc(self, byte_size: int, tag: TTag = TTag.PTR) -> int: ...
    def free(self, ptr: int): ...
    def read(self, ptr: int, byte_size: int) -> bytes: ...
    def write(self, ptr: int, data: bytes): ...
    def type_of(self, ptr: int) -> Optional[TTag]: ...
    def snapshot(self, ptr: int, byte_size: int) -> bytes: ...

The heap can be accessed directly via vm.heap if needed from Python host code.

Errors

python
class VMError(Exception): ...          # Runtime errors
class FVMParseError(Exception):        # Parse errors; has .lineno attribute
    lineno: int
class FluxThrowSignal(Exception):      # Raised by THROW; has .value: Val
    value: Val

22. Serialisation and Deserialisation

The .fvm format is fully round-trippable. The host compiler uses COMPILER_FVM_DUMP to snapshot comptime state to disk; the CLI can then replay it.

Value serialisation (_serialise_val)

Converts a Val back to a type:value token string. PTR values with meta (struct pointer, array pointer) include a bracketed meta suffix so struct field values and array elements are preserved across serialisation.

Instruction serialisation (_serialise_instr)

Converts an Instr to a .fvm line (opcode + operands). Handles all opcodes including multi-operand ones (CALL, JTABLE, EMITFLUX, INLINE_ASM).

File layout (output of serialise_fvm)

flux
# Generated by compiler.fvm.dump

struct PointList little 16 0
    field head ptr 0 8
    field len int 8 4
endstruct

func compute_sum
    LOCAL_GET 0
    LOCAL_GET 1
    ADD
    RET
endfunc

locals 3
PUSH int:10
PUSH int:20
CALL compute_sum 2
HALT

23. Error Handling

ConditionError
Stack underflowVMError: Stack underflow
Division by zeroVMError: Division by zero in comptime
Unknown opcodeVMError: Unknown opcode: <name>
Unknown functionVMError: Unknown comptime function: <name>
Ambiguous overloadVMError: Ambiguous comptime function: <name> matches [...]
Heap out-of-memoryVMError: VMHeap: out of memory (requested N, available M)
Heap out-of-boundsVMError: VMHeap: out-of-bounds access at ptr=N size=M
Invalid freeVMError: VMHeap.free: invalid pointer N
Unhandled throwVMError: Unhandled comptime throw: <val>
FFI load failureVMError: FFI_LOAD: <os error>
FFI symbol not foundVMError: FFI_SYM: symbol <name> not found
Extern symbol not resolvedVMError: extern: symbol <name> not found in any loaded library
INLINE_ASM: wrong archVMError: INLINE_ASM: x86-64 required at comptime, got <arch>
INLINE_ASM: keystone missingVMError: INLINE_ASM: keystone-engine is not installed
INLINE_ASM: assembly failedVMError: INLINE_ASM: assembly failed: <details>
Watchdog timeoutVMError: comptime execution exceeded 300s (N instructions) ...
Parse errorFVMParseError: line N: <message>

The watchdog checks every 50,000 instructions and raises VMError if the 5-minute limit is exceeded. The error message includes the current function name, instruction pointer, locals, and top-6 stack values to assist debugging.


24. Examples

Hello world

flux
# hello.fvm
PUSH bytes:"Hello, world!\n"
COMPILER_PRINT
HALT

Run with:

flux
python fvm.py hello.fvm

Arithmetic and locals

flux
# compute.fvm
locals 2
PUSH int:10
LOCAL_SET 0
PUSH int:32
LOCAL_SET 1
LOCAL_GET 0
LOCAL_GET 1
ADD
INT_TO_STR
COMPILER_PRINT
HALT

Output: 42

Defining and calling a function

flux
# factorial.fvm
func factorial
    # arg in locals[0]
    LOCAL_GET 0
    PUSH int:1
    CMP_LE
    JIF 8           # if n <= 1, jump to instruction 8
    LOCAL_GET 0
    PUSH int:1
    SUB
    CALL factorial 1
    LOCAL_GET 0
    MUL
    RET
    # instruction 8: base case
    PUSH int:1
    RET
endfunc

locals 1
PUSH int:10
CALL factorial 1
INT_TO_STR
COMPILER_PRINT
HALT

Using a struct

flux
# struct_demo.fvm
struct Vec2 little 8 0
    field x int 0 4
    field y int 4 4
endstruct

STRUCT_NEW Vec2
PUSH int:3
STRUCT_STORE x
PUSH int:4
STRUCT_STORE y
DUP
STRUCT_LOAD x
INT_TO_STR
COMPILER_PRINT
HALT

Reading a file at comptime

flux
# read_cfg.fvm
PUSH bytes:"config.txt"
COMPILER_READFILE
COMPILER_PRINT
HALT

Exception handling

flux
# exceptions.fvm
TRY_BEGIN 6        # catch at instruction 6
PUSH bytes:"something failed"
THROW
TRY_END            # not reached
JMP 7              # skip catch block
# instruction 6: catch handler
PUSH bytes:"Caught: "
SWAP
STR_CAT
COMPILER_PRINT
# instruction 7:
HALT

FFI call to strlen

flux
# ffi_strlen.fvm
FFI_LOAD "libc.so.6"
FFI_SYM strlen
PUSH bytes:"hello world"
FFI_CALL 1 uint
INT_TO_STR
COMPILER_PRINT
HALT

Emitting a constant to the compiler

flux
# emit_const.fvm
PUSH int:256
EMIT_CONST
HALT

After execute(), vm.emit_results will contain [('const', Val(TTag.INT, 256))].