The VM and compiled artifacts
The Rust VM executes TinyFX CPU code and is also the portable semantic
reference for much of the language. Its implementation lives in
crates/tinyfx-vm.
Bytecode program model
tinyfx_vm::compile_program lowers a checked compiler Frontend to a
Program. A program contains, among other things:
- bytecode functions, globals, statics, strings, structs, enums, and traits;
- source spans used for runtime errors and stack traces;
- shader and compute blueprints containing generated WGSL;
- exact uniform packing plans and resource metadata;
- program metadata retained from the compiler; and
- bytecode instructions for rendering, compute, UI, paint, profiles, assets, and ordinary CPU semantics.
Function indices are stable inside a compiled program. The compiler reserves the user-function range before it emits bodies, then appends implementation helpers such as arrow functions, shader thunks, and static/global initializers. This prevents an auxiliary function emitted early from changing a user function’s identity.
Calls with omitted default arguments push a sentinel. The callee prologue detects that sentinel and evaluates its own default expression, which preserves the declaration module and scope where the default was authored.
Values, heap, and garbage collection
The operand stack uses unboxed scalar, vector, and matrix values. Vectors and matrices use glam types directly, avoiding a heap allocation for common numeric/GPU data.
Heap objects are addressed by compact handles and include:
- strings;
- dynamic and fixed arrays;
- maps;
- structs and closures;
- shader values; and
- opaque runtime resources such as meshes, textures, buffers, and models.
The heap is mark-and-sweep. Roots include the operand stack, call frames, globals, statics, and retained runtime values. Because aggregate values are handles, reference semantics and cycle collection follow naturally.
Host boundary
The VM has no windowing or graphics dependency. Its Host trait
forms the platform seam. A host can provide:
- diagnostic output, statistics, profiling, and synchronization;
- render and compute command submission;
- texture, buffer, mesh, and model operations;
- asset loading;
- immediate-mode UI and paint commands;
- camera, stats-overlay, and native-window configuration; and
- asynchronous host-task and media callbacks.
The Rust runtime implements this trait using wgpu. MockHost gives
tests a deterministic, headless implementation for CPU execution and resource
semantics.
The host’s compute method reports whether it encoded a GPU dispatch. A host that declines a dispatch must leave state unchanged; the VM then runs the compiled auxiliary kernel sequentially. That fallback preserves ordering, read-your-own-write behavior, and atomic publication of all kernel outputs, which makes it useful as a reference path in tests.
Shader values and compute state
Evaluating a shader expression creates a heap shader object. It stores the blueprint identity, explicit uniform values, and local capture snapshots. It does not need to cache a pre-packed GPU buffer: at draw time the VM resolves the current capture values, packs the exact blueprint layout, and supplies resource handles to the host.
This is why local and global captures differ:
- a local capture is a creation-time snapshot; and
- a global capture is looked up at each draw, so values such as environment time stay live.
Compute code uses the same blueprint and capture ordering. GPU-written resources use front/back publication state. A kernel reads the current front, writes a back resource, and publishes completed outputs together. The VM fallback keeps a per-invocation shadow so conditional writes and local read-after-write semantics match the compute model.
Asynchronous tasks and media
VmSession is the VM's explicit suspending execution lifecycle. The runtime
uses it for operations that cannot synchronously return a browser or native
resource. A task has a session/task identity. The Host start_async_* hooks
start physical work and enqueue terminal completion DTOs; only a later
poll_setup or poll_loop resumes TinyFX execution. Promise callbacks and
worker callbacks never re-enter a running interpreter.
Stopping or reloading a program cancels outstanding work, forgets the old session, and rejects late completions. The current implementation uses this mechanism for text and media acquisition plus persistent storage, while keeping ordinary VM operations synchronous.
The .tfxb artifact
.tfxb is the VM’s self-contained binary artifact. It begins with
a magic value and a format revision, then serializes the compiled Program with
little-endian primitives and length-prefixed values.
It includes generated WGSL text and the metadata needed to bind and pack it.
Running a .tfxb file therefore does not need the compiler or the
original TinyFX source:
tfx build effect.tfx -o effect.tfxb
tfx run effect.tfxbThe reader deliberately accepts only its exact supported format revision and tells the user to rebuild source when necessary. This is safer than attempting to interpret a partially compatible artifact.
Some wire data has stricter compatibility rules:
- instruction encoding and decode tags must change together;
- builtin-key tables are append-only because their order is serialized;
- enum metadata and program metadata must be validated on read; and
- generated WGSL/layout/packing data are artifact content, not recomputed from an AST at runtime.
The serialized artifact is distinct from generated TypeScript. Generated TypeScript is a separate source-level artifact that runs on the JavaScript core and carries capability/physical-blueprint contracts instead of VM bytecode.
Useful source locations
| Concern | Location |
|---|---|
| Instruction and Program definitions | src/bytecode.rs |
| Frontend-to-bytecode lowering | src/compile.rs |
| Interpreter | src/interp.rs |
| Heap and values | src/heap.rs, src/value.rs |
| Host contract and mock host | src/host.rs |
| Async scheduler | src/async_host.rs |
| Serialization | src/tfxb.rs |
Advanced compute values
Revision 14 adds retained workspace, atomic, append-list, counter, slot, and
GPU-count heap objects plus append-only instruction tags for their constructors
and operations. The VM is the deterministic sequential reference schedule.
Schedule-observing unordered programs compare invariants rather than ticket
order. A GPU-count value retains the producer handle and checked maximum; it
is never interchangeable with an ordinary integer or atomic buffer.