Section 02 / machine

HardwareJS

A TypeScript hardware runtime for WebAssembly kernels. It creates the machine, runs its virtual CPUs, services physical interfaces, and leaves Linux policy to Linux.

ROLE DEFINITION HardwareJS is

boot firmware · CPU host · RAM owner · interrupt fabric · device backend

HardwareJS is not

a syscall polyfill · a POSIX shim · a VFS · a process model · a Linux emulator

02.01 / SYSTEM ROLE

The browser-facing half of a real kernel port

HardwareJS is the host implementation of the wasm32 machine. It allocates physical RAM, instantiates the kernel module, starts CPU Workers, moves interrupts and device data across shared-memory boundaries, and connects Linux drivers to browser APIs.

It deliberately does not reproduce kernel services. File permissions are checked by Linux. TCP and Unix sockets live in Linux. PTY line discipline is Linux N_TTY. Fork and signals are kernel operations whose host-visible pieces are transported by narrow rings.

Design test: if a capability belongs to a normal Linux subsystem, its authoritative state belongs in shared kernel memory. HardwareJS may transport or persist bytes, but it does not become the second source of truth.

SOURCE MAP · public exports · runtime overview

02.02 / BOOT LIFECYCLE

Preflight first; allocate only after compatibility

bootKernel() is the primary Linux machine entry point. Before creating Workers, it probes the WebAssembly features required by the shipped kernel and reports an attributed error if the engine cannot compile them. The current kernel is single-memory; the retired multi-memory probe is no longer a browser requirement.

1 / PROBEEngine supportThreads, shared memory, bulk memory
2 / CREATEPhysRamShared kernel memory and bounds
3 / COMPILEvmlinuxDeduplicate the process-side module
4 / STARTWorkersKernel CPU 0 and fixed CPU pool
5 / ATTACHBackendsRings, virtqueues, controls
import { bootKernel, PhysRam } from "@brintos/hardwarejs";

const ram = new PhysRam({
  initialPages: 16_384,
  maximumPages: 16_384
});

let controls;
const result = await bootKernel({
  ram,
  wasmBytes: vmlinux,
  workerEntry: new URL("./kernelWorker.mjs", import.meta.url),
  platform: "browser",
  num_vcpu: 5, // CPU 0 + four process CPUs
  cmdline: "console=ttyWASM0 rootfstype=virtiofs",
  onControls: (value) => { controls = value; },
  onDmesg: (level, text) => renderDmesg(level, text)
});

Callers can provide an already compiled WebAssembly.Module to avoid a second main-thread compile. Kernel Workers still compile inside their own isolate, because a Worker cannot inherit a JavaScript execution stack or instance.

SOURCE MAP · bootKernel.ts · wasmBootPreflight.ts · physRam.ts

02.03 / WORKERS & CPUs

A fixed CPU pool hosts an unbounded process population

The dedicated kernel Worker is CPU 0. A fixed pool of process Workers receives CPU IDs 1 through N. Each process Worker instantiates vmlinux.wasm against the common kernel memory and binds the user memory for its current address space. Resident tasks multiplex onto the pool instead of creating one permanent Worker per process.

HardwareJS CPU model showing the browser main thread, a dedicated kernel Worker, a four-Worker virtual CPU pool, vmlinux.wasm instances, process modules, per-process memory, and kernel memory shared across Workers.
An illustrative four-slot process-CPU pool. Every CPU Worker instantiates vmlinux.wasm against the same kernel SharedArrayBuffer; the scheduled process contributes its own user memory, which its threads can share. CPU 0 remains the dedicated kernel Worker. Open the full-resolution model →

The default topology derives from host concurrency and caps itself at eight total virtual CPUs unless the machine definition supplies a value. Production machines pass an explicit specification. HardwareJS cross-checks an externally created pool against the kernel CPU mask and refuses a mismatch that could corrupt per-CPU state.

SOURCE MAP · vCPU policy · process Worker pool

02.04 / MEMORY BOUNDARIES

Shared bytes, explicit authority

PhysRam owns the kernel SharedArrayBuffer-backed WebAssembly.Memory. It also manages the lifecycle and accounting of user-memory objects used by process address spaces. Every kernel Worker and device backend sees the same physical bytes.

MemoryVisible toContentsAuthority
env.kernel_memoryAll kernel instances and device backendsKernel image, Linux state, buffers, ringsLinux allocators and driver contracts
env.user_memoryOne process program and its hosting WorkerExecutable, heap, stack, shared objectsLinux mm plus loader contract
Browser objectsMain thread or backend ownerCanvas, USBDevice, SerialPort, WebSocketBrowser permissions and HardwareJS lifecycle

User pointers are never silently dereferenced in the kernel module. The linux.raw_copy_to_user, linux.raw_copy_from_user, and linux.raw_clear_user imports execute on a process Worker where both memories are available. Bounds failures return bytes-not-copied; a kernel Worker without user memory throws an attributed boundary error.

SOURCE MAP · PhysRam · uaccess boundary

02.05 / SCHEDULING & MACHINE LIFECYCLE

Drive slices; park at clean boundaries

Worker drive loops enter runnable kernel or user continuations for bounded slices. When Linux blocks, the continuation parks and the Worker can return to its event loop. An external device completion increments the shared scheduler wake word, notifies the appropriate Atomics wait, and injects the matching interrupt.

pause()
Requests a cooperative freeze at the next clean slice boundary.
resume()
Clears the shared pause word and wakes paused Workers.
isPaused()
Reads the machine-level pause state.
dispose()
Stops the kernel Worker, CPU pool, ring consumers, device loops, and scheduler tick.

Pause preserves continuations; dispose ends them. Both operations are idempotent. The steady-state pause check is one atomic load per slice. A configured watchdog is an absolute machine-lifetime cap, not an inactivity timer, and reports loudly before teardown.

SOURCE MAP · KernelControls · kernel Worker drive loop

02.06 / PROCESS ORCHESTRATION

Transporting Linux process decisions

The kernel publishes spawn and signal requests through fixed-layout rings in kernel memory. HardwareJS consumers drain those rings and perform the host-only part: allocate or select a Worker, clone/instantiate a WebAssembly image, bind its user memory, and enter the continuation. Linux remains the owner of pid state, file tables, process groups, pipes, and signal policy.

ComponentConsumesHost responsibility
SpawnRingConsumerfork/thread requestsValidate slot, choose Worker, invoke spawn handler
makeKernelSpawnHandlerkernel process descriptionClone memory/continuation or replace executable
SignalRingConsumertarget delivery requestsRoute wake/entry to the owning process Worker
Child image registrycompiled image recordsReuse immutable WebAssembly compilation artifacts

Older JavaScript registries for pipes, file descriptors, and process groups have been retired. Their state now lives where Linux expects it: in the kernel.

SOURCE MAP · spawn ring · signal ring · spawn handler

02.07 / DEVICE MODEL

Linux frontends, TypeScript backends

Virtio devices use split rings located in shared kernel memory. Linux negotiates and drives the frontend through wasm32 MMIO. HardwareJS parses descriptor chains, moves data to a browser capability, updates the used ring, sets interrupt status, and wakes the scheduler.

DRIVERQueue buffersLinux writes descriptors
MMIOQUEUE_NOTIFYwasm32 driver traps to host
BACKENDDrain chainValidate guest offsets and lengths
BROWSERPerform I/OStorage, Canvas, network, device
IRQCompleteUsed ring, pending bit, wake word
Guest deviceHardwareJS backendBrowser or host edge
virtio-fsVirtioFsBackend + FuseServerPluggable FsView
virtio-inputVirtioInputBackendDOM keyboard/pointer capture
virtio-netVirtioNetBackend + NetShimfetch and packet uplink
wasmdrmFramebuffer window + display controlCanvas ImageData
wasm USB HCDUsbHcdBackendWebUSB
wasm serialSerialBridgeBackendWeb Serial

SOURCE MAP · virtqueue parser · device backends

02.08 / STORAGE & FILESYSTEM

A FUSE transport over a pluggable view

The guest mounts a normal virtio-fs filesystem. HardwareJS translates its virtqueue traffic into the FUSE wire protocol and serves requests through FsView. The production implementation connects that interface to content-addressed, chunked, cached storage with hash verification.

Linux sees inodes, names, modes, ownership, file handles, and byte ranges—not object IDs. The host storage layer sees immutable content and metadata transactions—not Linux task credentials. This separation keeps VFS semantics in the guest while making browser persistence swappable and testable.

interface FsView {
  lookup(parent: bigint, name: Uint8Array): Promise<FsAttr>;
  read(node: bigint, offset: bigint, length: number): Promise<Uint8Array>;
  write(node: bigint, offset: bigint, data: Uint8Array): Promise<number>;
  readdir(node: bigint, offset: bigint): Promise<FsDirent[]>;
  // plus lifecycle and metadata operations
}

SOURCE MAP · FUSE server and FsView · virtio-fs backend

02.09 / DISPLAY & INPUT

A shared scanout with kernel-owned mode changes

The wasmdrm driver scans out an ABGR8888 buffer carved from kernel memory. On a little-endian host its bytes are directly compatible with Canvas ImageData. HardwareJS copies the current visible rectangle; the kernel owns the framebuffer console, DRM connector state, and mode commit.

Framebuffer window
Base, fixed maximum geometry, stride, format, and current visible dimensions.
Resize control
Four shared words carry requested/applied modes and sequence/ack values.
Display callback
onDisplayControl receives a bounded setMode() surface.
Input
DOM events are translated to Linux evdev records and delivered through virtio-input.

The scanout is allocated at its maximum configured dimensions. Resize changes the visible mode without relocating the buffer, eliminating a stale-pointer class between the guest and the render loop.

SOURCE MAP · FbWindow and DisplayControl · virtio-input backend

02.10 / NETWORKING

Packets remain packets until an explicit edge

Linux owns Ethernet, IP, TCP/UDP, DNS clients, and sockets. The virtio-net backend moves frames. HardwareJS then chooses one of two explicit host integrations.

NET-A

Browser fetch shim

NetShim answers local ARP/DNS/gateway traffic and terminates eligible HTTP flows into browser fetch(). Access is gated by a key read from the guest filesystem.

NET-B

Packet uplink

WebSocketPacketNaasUplink carries raw L3 packets to a remote relay for general network access while Linux retains its network stack.

NET-C

Test harness

Injectable frame handlers and stub uplinks provide deterministic ARP, echo, error, and queue-completion gates without a live network.

Traffic that no configured boundary can serve is refused with an attributed reason. There is no silent “pretend success” path.

SOURCE MAP · virtio-net backend · NetShim · packet uplink

02.11 / USB, PTY & SERIAL

Permissioned browser devices behind Linux drivers

BoundaryGuest viewHost behaviorConstraint
USBVirtual host controller and USB coreTranslate URBs to WebUSB transfersBrowser class policy and user permission
USB serialFTDI driver and normal ttyWebUSB transfer endpointsSupported device descriptors/endpoints
Console PTYPTY pair with kernel N_TTYShared rings carry master input/outputOne ring ABI and explicit winsize
Web Serial/dev/ttySER0Read/write a granted SerialPortSecure context and user gesture

The USB path is a purpose-built virtual HCD, not a browser xHCI emulator. HardwareJS synthesizes the descriptors Linux needs, forwards control and endpoint transfers, and reports attach/detach/error events. Protected WebUSB classes remain unavailable when the browser withholds them.

SOURCE MAP · USB HCD backend · PTY bridge · serial bridge

02.12 / FAILURE & OBSERVABILITY

A stopped machine must say why

Boot results distinguish a returned kernel, trap, link failure, instantiation failure, constructor failure, missing entry, watchdog, and host error. Dmesg lines preserve kernel levels. Backends report queue, bounds, protocol, permission, and transport failures through attributed callbacks.

GatePreventsFailure shape
WebAssembly preflightCreating a machine an engine cannot runFeature-specific error before Worker allocation
Topology cross-checkPool CPU IDs outside kernel per-CPU storageBoot refusal with both sizes
Ring generationsCompleting a reused or stale slotProtocol error, no silent delivery
Virtqueue validationOut-of-bounds guest descriptor accessBackend error tied to queue/device
Dispose contractWorkers surviving relaunch/navigationIdempotent teardown of every owned loop

The repository’s vitest gates cover boot probes, fork/exec/dlopen acceptance, virtio-fs, console, framebuffer, input, networking, physical I/O, and lifecycle regressions. Distro-level Node harnesses then exercise complete kernel/userspace artifacts.

02.13 / RUNTIME ENVIRONMENTS

Browser first, Node testable

HardwareJS separates platform Worker entry points and injectable host APIs. The same core runtime boots in browsers and under Node-based gates; Canvas, fetch, WebSocket, USB, and serial edges can be replaced by deterministic test implementations.

Browser requirements
Secure context, cross-origin isolation, SharedArrayBuffer, WebAssembly threads, and bulk memory.
Optional APIs
WebUSB and Web Serial only when present and permissioned; packet uplink only when configured.
Node use
Boot, process, scheduler, virtio, and device gates with Node Worker entry points and mocks.
Compatibility
Feature probing is authoritative; user-agent string guesses are not.

Continue to the contract reference for field-level host/kernel interfaces, or return to the Linux / wasm32 port for guest-side semantics.