Hosts
What the platform lends a machine: its WebAssembly modules, files, disks, sockets, display and sound. Node and the browser each have a ready-made host.
What QEMU takes from the computer it runs on, Warm64 takes from a host: a plain object passed to WEMU.launch(). The two ready-made hosts cover the common cases, and you can extend either one by spreading it into a new object.
const host = {
...nodeWEMUHost(),
display: { refreshHz: 30, frame: (width, height, pixels) => draw(width, height, pixels) },
};nodeWEMUHost()
The host for Node: files on disk, disks opened in place, real TCP sockets and DNS.
import { nodeWEMUHost } from "warm64/wemu-node";
nodeWEMUHost(options?): WEMUHostNodeWEMUHostOptions:
| Option | Type | Default | Description |
|---|---|---|---|
wasmDir | string or URL | the package's wasm/ | Where the .wasm modules are. |
snapshotDir | string | none (in memory) | Where savevm keeps snapshots, as NAME.warm64snap files. |
warn | (line) => void | writes to stderr | Where warnings go. |
It provides wasm, readFile, openDisk (raw images only), writeFile, connector and resolve.
browserWEMUHost()
The host for a web page or worker: it fetches everything by URL.
import { browserWEMUHost } from "warm64";
browserWEMUHost(options?): WEMUHostBrowserWEMUHostOptions:
| Option | Type | Default | Description |
|---|---|---|---|
base | string or URL | the page's URL | What relative paths are resolved against. The cores are fetched from ${base}wasm/${kind}.wasm. |
warn | (line) => void | console.warn | Where warnings go. |
It provides only wasm, readFile and warn. Paths on the command line may be relative to base, absolute URLs, or blob: URLs of local files:
const url = URL.createObjectURL(fileInput.files[0]);
await WEMU.launch(`-kernel ${url} -display none`, browserWEMUHost());Add the rest yourself: a connector for networking, openDisk for disks that persist, display and audio.
WEMUHost
Every member except wasm and readFile is optional.
| Member | Type | Description |
|---|---|---|
wasm | (kind) => WasmSource | Returns the WebAssembly module Warm64 asks for: a WebAssembly.Module, bytes, a Response, or a URL string to fetch. See Core modules. |
readFile | (path) => Promise<Uint8Array> | Reads what the command line names: -kernel, -initrd, -bios, -pflash, and disks when there's no openDisk. |
openDisk | (path, readOnly, format) => WEMUDiskFile | Opens a disk in place. See openDisk. |
writeFile | (path, data) => Promise<void> | Writes files for screendump, migrate file:, dumpdtb, dump-guest-memory and -audiodev wav. |
connector | Connector | Opens TCP connections for -netdev user. See connector. |
resolve | (name) => Promise<string or null> | Answers the guest's DNS lookups with an IPv4 address. Without it every name is unknown. |
webSocket | a WebSocket-like constructor | Used by -netdev socket,connect=. Defaults to the global WebSocket. |
snapshots | WEMUSnapshotStore | Where savevm keeps snapshots. Defaults to memory, for the machine's lifetime. |
display | WEMUDisplay | The screen. See display. |
audio | WEMUAudio | The sound card. See audio. |
warn | (line) => void | Warnings, such as options accepted but ignored. Defaults to console.warn. |
Core modules
Warm64 picks the module from the command line and asks the host for it by name, a WEMUCoreKind. The file is ${kind}.wasm.
| Kind | When |
|---|---|
warm64 | One CPU, up to 3 GiB of RAM. |
warm64-memory64 | One CPU and more than 3 GiB of RAM. |
warm64-threads | -smp of 2 or more: one host thread per guest core. |
warm64-gpu-threads | A virtio-gpu device, where shared memory is available (Node, or a cross-origin-isolated page). |
warm64-gpu | A virtio-gpu device, where shared memory isn't. |
Note The threads modules need SharedArrayBuffer. Browsers give it only to cross-origin-isolated pages, served with the Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers. Node has no such condition.
openDisk
openDisk(path: string, readOnly: boolean, format: string): WEMUDiskFile | Promise<WEMUDiskFile>Opens a disk so the guest reads and writes it in place, with nothing to flush. format is the drive's format= (raw by default); a host that can't serve a format should throw. Return a WEMUDiskFile:
{ size: number, backing: DiskBacking, close?(): void }A DiskBacking has three synchronous methods, called while the guest runs:
| Method | Description |
|---|---|
read(offset, into) | Fill the Uint8Array into from the disk at offset. |
write(offset, data) | Write data at offset. |
discard(offset, length) | Optional. Free a range; called only for drives with discard=unmap. |
Returning false or throwing gives the guest an I/O error. Without openDisk, a disk is read whole through readFile and its writes stay in memory.
In a browser, disks that persist come from the Origin Private File System. Its synchronous access handles exist only in workers, so run Warm64 in a worker:
import { opfsDiskBacking } from "warm64";
const root = await navigator.storage.getDirectory();
const host = {
...browserWEMUHost(),
async openDisk(path) {
const file = await root.getFileHandle(path);
const handle = await file.createSyncAccessHandle();
return { size: handle.getSize(), backing: opfsDiskBacking(handle), close: () => handle.close() };
},
};connector
-netdev user is a built-in NAT: the guest gets DHCP, DNS and ping from it, and its TCP connections go out through the host's connector. Without one the guest's connections are refused.
interface Connector {
connect(host: string, port: number, handlers: SocketHandlers): Promise<StackSocket>;
}
interface SocketHandlers { onData(bytes: Uint8Array): void; onEnd(): void; onError(error: Error): void }
interface StackSocket { write(bytes: Uint8Array): void; end(): void; destroy(): void }Node's host connects real sockets. A browser can't, so webSocketConnector() carries each connection over a WebSocket to a relay you run, such as examples/net/tcp-relay.mjs in the Warm64 repository:
import { webSocketConnector } from "warm64";
const host = { ...browserWEMUHost(), connector: webSocketConnector("wss://relay.example.com/") };See Networking.
display
interface WEMUDisplay {
canvas?: OffscreenCanvas;
frame?(width: number, height: number, pixels: Uint8Array): void;
refreshHz?: number; // default 60
}canvas: anOffscreenCanvasfromcanvas.transferControlToOffscreen(). The GPU worker draws on it with WebGPU or WebGL2 (software when neither is there, or with-display ...,gl=off). A host GPU is used for 3D (-device virtio-gpu-gl-pci); 2Dvirtio-gpu-pcialways renders in software.frame: called with each changed frame, at mostrefreshHztimes a second.pixelsis a8r8g8b8 (bytes B, G, R, X) and is valid only during the call.
The display is used unless the command line says -display none or -nographic. See Display and input.
audio
interface WEMUAudio {
play(samples: Int16Array, rate: number, channels: number): void;
record?(): Int16Array | null;
}Used for an -audiodev other than none or wav. play receives what the guest plays; record, polled regularly, supplies what it captures. Without a host audio, Warm64 warns and drops the sound.
snapshots
interface WEMUSnapshotStore {
load(name: string): Promise<Uint8Array | null>;
save(name: string, state: Uint8Array): Promise<void>;
delete(name: string): Promise<boolean>;
list(): Promise<string[]>;
}In Node, fileSnapshots(dir) keeps them as files.