Browser
The emulator is a C++ library, and Emscripten compiles it to WebAssembly as readily as a native toolchain compiles it to a .so. @cartesi/machine/wasm is that build, driven through the same C API the native addon binds — so it is the same machine, with the same root hashes, proofs and access logs, at roughly the same speed.
It is built for web, worker and node environments, so it runs on a page, in a worker, and in Node.js — anywhere with WebAssembly and no need for a compiler or an emulator installation.
Importing
import { init } from "@cartesi/machine/wasm";The package's main entry also resolves here through the browser condition, so bundlers that honor it — Vite, webpack, Rollup, esbuild with --platform=browser — give the WebAssembly build to import { init } from "@cartesi/machine" as well. The /wasm subpath is the explicit form, and the one to use when the WebAssembly build is wanted in Node.js, where the bare specifier resolves to the native addon.
Nothing else is needed at build time: the module is a single ES file with the WebAssembly embedded in it, shipped inside the package.
Instantiating
WebAssembly is instantiated asynchronously, so unlike the Node.js entry — where the addon is bound at import time — the machine constructors come from init():
import { } from "@cartesi/machine/wasm";
const = await ();
// same constructors as the Node.js binding, on the same interface
const = .({
: { : 0x4000000 },
});
.(1_000_000n);
.(.());
.();The value init() resolves to carries create, load, empty and the verification functions, plus the two handles that only exist in this build — the module and its filesystem. See init for the full surface.
Each call builds an independent module, with its own heap and its own filesystem. Machines from different modules cannot be mixed, and one module is normally all a page needs.
Snapshots
There is no host filesystem to store to. The module has its own in-memory one, and a stored machine is a directory in it, so a snapshot has to be moved in before it can be loaded and moved out to be kept. writeSnapshot and readSnapshot do that as a tar archive — the exact bytes tar produces from a directory machine.store() wrote:
import { } from "@cartesi/machine/wasm";
const = await ();
// unpack a snapshot served alongside the page
const = await ("/app.tar");
.("/machines/app", new (await .()));
const = .("/machines/app");
.(1_000_000n);
// pack the machine back up, to keep it beyond the life of the page
.("/machines/app-after");
const = .("/machines/app-after");
.(.);cartesi.fs is the filesystem itself (Emscripten's FS) for anything the two helpers do not cover — mounting IndexedDB-backed storage, listing a directory, deleting one.
Rollups
Rollups machines work here too, from a machine rather than from a directory:
import { , } from "@cartesi/machine/wasm";
import { } from "viem";
const = await ();
const = await ("/app.tar");
.("/machines/app", new (await .()));
const = (.("/machines/app"), {
: "/machines/snapshots",
});
for (const of .(("test"), { : true })) {
.();
}Rejected inputs are rolled back through a snapshot taken in that directory, since there is no process to fork. That is the same code path local machines take in Node.js — see Rollups Machines.
What is not available
Everything that needs a process or a socket:
- spawn and connect, and the server-level methods of RemoteCartesiMachine —
fork,rebind,emancipate,shutdown. Calling one throws. To drive a machine server from a browser, use connectHttp, which speaks the same JSON-RPC overfetch; - host networking inside the machine (virtio net-user), which is compiled out;
- concurrency: the build is single-threaded, so hash tree updates are not parallelized.
Memory
The module starts with 64MB and grows to at most 4GB. A machine's RAM and flash drives are allocated in that heap, which puts a practical ceiling on machine size — and, on 32-bit wasm32, a hard one at 4GB for everything at once.
Machines are freed when garbage collected, through a FinalizationRegistry, but that is a best-effort backstop: call destroy() on a machine you are done with, or the heap keeps its memory ranges until the collector gets around to it.
Staying responsive
run() occupies the thread it is called on, and an advance is seconds of compute — enough to freeze a page. Run the module in a worker instead: Worker.
A terminal on a page
A guest's console is two buffers the host owns, so a WebAssembly machine can be wired straight to a terminal emulator like xterm.js — the guest running Linux on one side, a <div> on the other, and a plain synchronous loop in between. See Terminal.