◆ Flux

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.

The compilation pipeline 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.

Three things follow, and they are the reason the whole machine is shaped this way:

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:

  1. 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.
  2. 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”.
  3. Application panes. One execution and distribution format carries an indicator, a representation, a drawing, a scene, a transition and application logic alike.
  4. 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