Download Game! Currently 98 players. Last logged in:MaggaraRequiemWhlxchCordoba

Blitzer's Blog >> 72936

Back to blogs index
Posted: 04 Oct 2026 14:19 [ permalink ]
Porting the virtual machine to WebAssembly (Wasm) is the ultimate endgame for
an isomorphic execution engine. By rewriting the worker.js loop in a systems
language like Rust or C, you bypass the V8 JavaScript engine's JIT compiler,
garbage collector, and object overhead, achieving near-native hardware speed.
The beauty of your current architecture is that the Gateway, Static Analyzer,
and Macro Preprocessor remain entirely in JavaScript. Only the compilers final
output and the workers execution loop change.
Here are the four major architectural shifts required to move from a JS VM to
a WebAssembly VM.
1. From JSON Bytecode to a Flat Binary Format
Currently, your compiler emits an array of JavaScript objects: [{ op:
'OP_CONST', val: 5 }, { op: 'OP_ADD' }]. WebAssembly cannot read JavaScript
arrays natively without heavy serialization penalties.
You must add a Binary Assembler to your JavaScript compiler. Instead of
emitting objects, it packs the instructions into a flat Uint8Array byte
buffer.
 * OP_ADD becomes a single byte (e.g., 0x14).
 * OP_CONST 5 becomes a 1-byte opcode (0x01), a 1-byte type flag (0x02 for
Number), and an 8-byte payload (the float64 value of 5).
 * Variables (like "x") are converted by the compiler into integer memory
addresses, so the Wasm VM never has to do expensive string lookups.
2. The Data Stack: Tagged Unions
In JavaScript, an array can hold mixed types [5, "hello", { status: "ok" }].
In Rust/C, memory is strictly typed.
Inside the Wasm module, your dataStack becomes an array of Tagged Unions.
Every value pushed to the stack is precisely 16 bytes:
 * Bytes 0-3: Type Tag (0 = Null, 1 = Bool, 2 = Number, 3 = String Pointer, 4
= Record Pointer)
 * Bytes 4-15: The Payload (Either the literal number/boolean, or a 32-bit
integer pointer to where the string/record lives in Wasm linear memory).
3. Wasm Linear Memory & Arenas
Because your JSON-Lisp supports dynamic structures (["array", 1, 2],
["record", "status", "ok"]), the Wasm VM must manage its own memory.
Since your engine utilizes strict gasLimits and short-lived edge executions,
you don't need a complex Garbage Collector. You can implement a Bump Allocator
(Arena) in Rust/C.
 * The Wasm module allocates a chunk of linear memory (e.g., 2 MB).
 * When the JSON-Lisp script creates a record, the Wasm VM advances an
internal pointer, writes the record, and pushes that pointer to the dataStack.
 * When the execution finishes, the entire 2 MB chunk is instantly wiped
clean. Zero garbage collection overhead.
4. The FFI Boundary (Imports/Exports)
In our JS worker, we injected standardLibrary directly into the environment
map. WebAssembly handles Foreign Function Interfaces natively through the
importObject.
When your JavaScript worker thread instantiates the compiled .wasm file, it
passes the native functions into the module.
/ Inside your JS worker thread booting up the Wasm module:
const importObject = {
  env: {
    // 1. The SAB is passed directly into Wasm linear memory!
    memory: new WebAssembly.Memory({ initial: 1, shared: true }),
    
    // 2. The Standard Library
    print_ffi: (stringPtr, length) => {
      // JS reads the string from Wasm memory and prints it
      const str = decodeWasmString(stringPtr, length);
      console.log("[WASM Print]:", str);
    }
  }
};

const { instance } = await WebAssembly.instantiate(wasmBuffer, importObject);
/ Start the VM!
instance.exports.execute_vm_loop(binaryBytecodePtr, gasLimit);

The New Architecture Pipeline
If you implement this, your Fastify Gateway's pipeline becomes an absolute
powerhouse:
 * Request: Fastify receives the JSON-Lisp AST.
 * JS Phase: Macros expand -> Static Analyzer verifies -> Compiler flattens to
Uint8Array binary.
 * Dispatch: JS sends the raw binary byte array to the Worker Thread.
 * Wasm Phase: The Worker invokes the Rust/C WebAssembly module.
 * Execution: Wasm executes the binary byte-for-byte. It checks the 16-byte
SharedArrayBuffer for interrupts natively. If it hits an injected function, it
calls back to JS.
 * Return: Wasm returns a pointer to the final serialized JSON object in
memory, JS decodes it, and Fastify replies to the client.