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
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.
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
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.
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
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.
| Memory | Visible to | Contents | Authority |
|---|---|---|---|
env.kernel_memory | All kernel instances and device backends | Kernel image, Linux state, buffers, rings | Linux allocators and driver contracts |
env.user_memory | One process program and its hosting Worker | Executable, heap, stack, shared objects | Linux mm plus loader contract |
| Browser objects | Main thread or backend owner | Canvas, USBDevice, SerialPort, WebSocket | Browser 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
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
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.
| Component | Consumes | Host responsibility |
|---|---|---|
SpawnRingConsumer | fork/thread requests | Validate slot, choose Worker, invoke spawn handler |
makeKernelSpawnHandler | kernel process description | Clone memory/continuation or replace executable |
SignalRingConsumer | target delivery requests | Route wake/entry to the owning process Worker |
| Child image registry | compiled image records | Reuse 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
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.
| Guest device | HardwareJS backend | Browser or host edge |
|---|---|---|
| virtio-fs | VirtioFsBackend + FuseServer | Pluggable FsView |
| virtio-input | VirtioInputBackend | DOM keyboard/pointer capture |
| virtio-net | VirtioNetBackend + NetShim | fetch and packet uplink |
| wasmdrm | Framebuffer window + display control | Canvas ImageData |
| wasm USB HCD | UsbHcdBackend | WebUSB |
| wasm serial | SerialBridgeBackend | Web Serial |
SOURCE MAP · virtqueue parser · device backends
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
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
onDisplayControlreceives a boundedsetMode()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
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.
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.
Packet uplink
WebSocketPacketNaasUplink carries raw L3 packets to a remote
relay for general network access while Linux retains its network stack.
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
Permissioned browser devices behind Linux drivers
| Boundary | Guest view | Host behavior | Constraint |
|---|---|---|---|
| USB | Virtual host controller and USB core | Translate URBs to WebUSB transfers | Browser class policy and user permission |
| USB serial | FTDI driver and normal tty | WebUSB transfer endpoints | Supported device descriptors/endpoints |
| Console PTY | PTY pair with kernel N_TTY | Shared rings carry master input/output | One ring ABI and explicit winsize |
| Web Serial | /dev/ttySER0 | Read/write a granted SerialPort | Secure 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
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.
| Gate | Prevents | Failure shape |
|---|---|---|
| WebAssembly preflight | Creating a machine an engine cannot run | Feature-specific error before Worker allocation |
| Topology cross-check | Pool CPU IDs outside kernel per-CPU storage | Boot refusal with both sizes |
| Ring generations | Completing a reused or stale slot | Protocol error, no silent delivery |
| Virtqueue validation | Out-of-bounds guest descriptor access | Backend error tied to queue/device |
| Dispose contract | Workers surviving relaunch/navigation | Idempotent 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.
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.
