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?): WEMUHost

NodeWEMUHostOptions:

OptionTypeDefaultDescription
wasmDirstring or URLthe package's wasm/Where the .wasm modules are.
snapshotDirstringnone (in memory)Where savevm keeps snapshots, as NAME.warm64snap files.
warn(line) => voidwrites to stderrWhere 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?): WEMUHost

BrowserWEMUHostOptions:

OptionTypeDefaultDescription
basestring or URLthe page's URLWhat relative paths are resolved against. The cores are fetched from ${base}wasm/${kind}.wasm.
warn(line) => voidconsole.warnWhere 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.

MemberTypeDescription
wasm(kind) => WasmSourceReturns 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) => WEMUDiskFileOpens a disk in place. See openDisk.
writeFile(path, data) => Promise<void>Writes files for screendump, migrate file:, dumpdtb, dump-guest-memory and -audiodev wav.
connectorConnectorOpens 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.
webSocketa WebSocket-like constructorUsed by -netdev socket,connect=. Defaults to the global WebSocket.
snapshotsWEMUSnapshotStoreWhere savevm keeps snapshots. Defaults to memory, for the machine's lifetime.
displayWEMUDisplayThe screen. See display.
audioWEMUAudioThe sound card. See audio.
warn(line) => voidWarnings, 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.

KindWhen
warm64One CPU, up to 3 GiB of RAM.
warm64-memory64One CPU and more than 3 GiB of RAM.
warm64-threads-smp of 2 or more: one host thread per guest core.
warm64-gpu-threadsA virtio-gpu device, where shared memory is available (Node, or a cross-origin-isolated page).
warm64-gpuA 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:

MethodDescription
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: an OffscreenCanvas from canvas.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); 2D virtio-gpu-pci always renders in software.
  • frame: called with each changed frame, at most refreshHz times a second. pixels is 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.

See also