Overview — two engines, one contract
The FVM — the Flux Virtual Machine — is the machine every Flux program runs on: a total, sandboxed, deterministic dataflow machine whose instruction set is the kernels and the graph operations, whose memory is the static linear layout of the memory model, and whose arithmetic is pinned to the byte. It has two conforming implementations. A graph interpreter serves the editor, the live preview and the debugger; a compiled WebAssembly module serves the run and distribution. Neither is the FVM — each is a way of running it, in the exact sense the JVM is one semantics with more than one implementation.
The two implementations are not an approximation of each other. They produce the same bytes, and that equality is checked at every single compilation, on hostile data, before anything ships. That one invariant is the spine of everything else: it is what lets the engine be swapped underneath a running chart with no visual glitch, what lets an optimizer be aggressive without being trusted, and what lets a server re-execute a client’s work and detect a lie. This page maps the machine — the two engines, the contract that binds them, and the pages where each part is specified in full.
New here? Start with Guide §11 — Determinism, replay and trust →
The two engines
| Interpreter | WebAssembly module | |
|---|---|---|
| Serves | editing, live preview, the dataflow debugger | the run, and distribution |
| Feedback | instant — no compile step in the loop | compiled and gated, then swapped in |
| Role in the contract | the oracle | the candidate |
The interpreter pays its cost once, when the graph is built; after that the hot loop is native kernels over pre-allocated columns. The module pays a compile cost — tens of milliseconds, almost all of it lowering the graph to bytes — and buys a sandboxed, opaque, distributable artifact in return. Both run the same typed graph of kernels; they lower it differently but must land on the same bits.
The pipeline
One graph is compiled once and emitted to both back-ends. Parsing is total — an arbitrary input
yields one tree or a clean error — and every later stage is a pure function of the stage before
it: name and def-body inlining (the call graph is acyclic, so it terminates), bottom-up kind
inference, a causality check that every feedback cycle crosses a unit delay, optimization, and a
memory plan that turns liveness intervals into an exact footprint. The emit step forks: interpreter
closures for edit, preview, debug — the oracle — and a WebAssembly module for run and
distribution.
Figure — one graph, two back-ends, one gate between them.
The unit of compilation is (graph, resolved parameters, bar capacity). Parameters are resolved
at compile time — changing a knob recompiles and re-gates. That is a deliberate trade: it buys a
fully static state layout, an exact min = max memory, and a gate that validates the very bytes and
the very instance that will serve. There is no gap between what was verified and what runs. The full
stage-by-stage treatment lives in Compiler & runtime.
The contract — byte-identity
Two invariants bind the engines, and both are checked, not promised.
- I6 — a leaf is byte-identical to its kernel. A node that maps to a native kernel produces
exactly the bytes that kernel produces, warm-up included. Flux imposes no
na-until-N convention of its own: a Flux indicator on a clock is the same citizen as a built-in, from the first bar. That is what makes “rewrite the catalogue in Flux” a safe proposition rather than a rewrite of every golden. - I7 — the interpreter and the module agree, byte for byte. At every compilation, the interpreter (oracle) and the instantiated module (candidate) are compared byte-wise on every sink column, over a hostile, adaptive corpus — holes, ±infinity, negative zero, non-canonical NaN patterns, exact half-integers, flat and monotone runs — in batch, then live, then on any real data supplied. Any divergence blocks the compilation: the module is not shipped, and the interpreter keeps serving.
Three things follow, and they are the reason the whole machine is shaped this way:
- Engine swaps are invisible. Serving interpreter results now and module results a moment later produces the same bytes — no migration state, no reconciliation, no repaint, no golden churn.
- The optimizer needs no trust. Any value change it introduces is caught by the blocking gate at the compilation that produced it.
- A distributed module is verifiable. Anyone holding the graph can re-run the validation locally: identity is checkable, not asserted.
The gate mechanics — why the live path is in the gate too, and what the browser’s worker orchestration looks like — are in Compiler & runtime; the full harness and the reproducible-build seal are the subject of Verification & reproducible builds.
Determinism by construction
Byte-identity does not survive first contact with a standard library: two engines can disagree about
the last bit of a logarithm, the sign of a zero, or the rounding of a half-integer, and any one of
those breaks replay. So Flux pins the routines — the same code on both sides, with no delegation
to the platform. Transcendentals route through a pinned WebAssembly libm; round is ties-to-even;
na at rest is the canonical quiet NaN; decimal, string, calendar, rand(seed) and na
ordering each carry one shared implementation. The interpreter runs on a JavaScript engine and could
reach for Math, Number, Date and Intl — and pointedly does not, because agreeing with itself
would mean disagreeing with the module. The toolchain is pinned the same way: a fixed Binaryen
pipeline, its version folded into the build hash, canonical function and local ordering, no timestamp
or path in the artifact. The emitted bytes are a pure function of (program, toolchain), and every
cache key carries the whole of that identity.
The determinism this buys is the same numbers and the same scene on every machine — bit-identical compute (ARM ≡ x86) and a deterministic draw-list — not identical pixels across GPUs. The pinned-routine table and the toolchain rules are catalogued in Compiler & runtime; the routines themselves are documented where you program against them, across the FDK.
Why WebAssembly
Not for speed. Fusing kernels with glue gains nothing in batch — the boundary is amortized over a
long history — SIMD is excluded by byte-identity (a horizontal reduction reassociates
floating-point and changes the bits), and scalar f64 compute in a modern JavaScript engine is
already within a small factor. Real speed comes from the fused, allocation-free datapath the
compilation model produces and from O(n) algorithms — not from the execution language. The measured,
byte-proven numbers are certified in
Compiler & runtime §Performance:
the win is the datapath, not the engine.
WebAssembly is the target for four structural reasons:
- One execution artifact from day one. Holding the interpreter and the module equal bit for bit is far cheaper to freeze at the start than to retrofit.
- Cross-machine determinism. WebAssembly’s floating-point semantics are strictly specified, which is what makes server-side re-execution meaningful rather than “same runtime, probably”.
- Application panes. One execution and distribution format carries an indicator, a representation, a drawing, a scene, a transition and application logic alike.
- An opaque, sandboxed distribution artifact. A shared or purchased script ships as WASM, never as source: the author’s intellectual property is protected, and the consumer’s trust boundary is a binary plus a sealed manifest.
The honest frontier. WebAssembly computes; it never paints. The module produces geometry, a
draw-list, and a view tree in linear memory, and the host does the painting: scene graphics and text
both render on WebGPU, which sidesteps DOM layout cost — text as an SDF glyph atlas, crisp at
any zoom and DPR. No Web API is reachable from the module — eval and dynamic code generation stay
forbidden, and host access is admitted only through vetted imports, each one gated by its own policy
token. The capability sandbox is exact about who does what: an effect leaves a script as inert data,
and the host — the sole holder of the resource — executes it only against a manifest line the user
granted. Its catalogue lives in host services.
Budgets — counted, never timed
A budget enforced by a stopwatch is not a budget: the same script would be accepted on one machine
and killed on another, and replay would die of it. Every ceiling in Flux is a counter, evaluated
on the graph before anything runs. N_max bounds the length of a window or vec (beyond it,
[ErrTotal]); maxNodes bounds the graph after inlining and dead-code elimination; N_active
bounds how many co-active scripts merge into one shared DAG. The memory ceiling is not a check but
the allocation itself — the module declares its linear memory with min = max, so growth is not
forbidden at run time, it is impossible.
There is no per-bar timeout. Runtime cost is statically bounded, an over-budget graph is rejected at
compilation rather than killed mid-frame, and accept-or-reject stays a pure function of the source. A
build carries a wall-clock timeout only as an interactive cancellation in the editor, never as a
verdict. When the aggregate frame budget is nevertheless exceeded — many co-active scripts on one
chart — the host applies a deterministic degradation policy, throttling or pausing low-priority
scripts in an explicit declared order: bounded, knowable, compile-counted work that degrades the same
way on every machine rather than being held at a rate. This is where the FVM’s not-Turing-complete
core earns its keep — computation is total, unbounded over time but bounded per step, so a runaway
inner loop is a compile-time impossibility, not a hung tab. A script that fails at run time on a valid
graph is quarantined, its node removed from the active graph without disturbing its neighbours; and a
NaN is not a fault — it is na, displayed as a gap.
The FVM pages
Each part of the machine carries its own reference page.
| Page | What it specifies |
|---|---|
| Compiler & runtime | The full pipeline, the I7 gate mechanics, the pinned-routine and toolchain tables, the runtime surface. |
| Memory model | The static linear layout, the liveness plan, and why the plan itself is pinned. |
| The optimizer | The correctness law, the tiers, translation validation, and the rules that look sound and are not. |
| Concurrency & the 1≡N proof | The scheduler, the worker fleet, and the stress suite that proves one worker equals many. |
| Host integration | How the host drives the module: instances, the draw-list, and the boundary contract. |
| Packages & distribution | Content addressing, the lockfile, imports, and what the rebuild gate seals. |
| Verification & reproducible builds | The verification harness, the reproducible-build gate, and the certified performance benchmark. |
See also
- Guide §11 — Determinism, replay and trust — the same machine, told for the reader who must trust it.
- Guarantees — the properties the FVM upholds, stated as promises.
- Compiler & runtime — the pipeline and the contract in full.
- Verification & reproducible builds — the harness, its blocking sub-suites, and the rebuild gate.
- Inference — why deterministic inference is a prerequisite for all of this.
- The FDK — the surface you program the FVM against.