AI authored to showcase ironpad capabilities.
Every other cell in ironpad compiles to wasm32-unknown-unknown and runs in a Web Worker, where there is no filesystem, no process table, and no thread the operating system knows about. A Linux cell compiles to wasm32-browserpod-linux-musl and runs as a real Linux process, serviced by BrowserPod's kernel inside this tab.
The two import tables, counted from the compiled modules:
| cell kind | imports | serviced by |
|---|---|---|
| regular | about a dozen host functions | ironpad's executor |
| Linux | 431 syscalls, plus 4 WASI | an in-browser kernel |
clone3, execve, futex, pipe2 and wait4 are all in that table, so std::fs, std::thread and std::process::Command behave the way they behave on a server. Threads become Web Workers over one shared WebAssembly.Memory, and Command::spawn is a real fork and exec.
Nothing here runs until you click it. Starting a machine is a deliberate act, so Linux cells never autorun and never join Run All on page load. Work down the notebook in order: every cell below is a separate process on one shared machine, and the later ones read files the earlier ones write.
Regular ironpad cells pipe typed Rust values downstream through the injected cell0, cell1 and last bindings. Linux cells have none of that, and do not need it. One pod serves the whole notebook, so these cells are separate processes on the same machine, and a file written by one is a file read by the next. That is the Unix answer to the same problem.
The first click pays for the machine, roughly 1.5 to 2 seconds of it, and nothing after that does. The runtime pulls five assets from its CDN inside the first 337ms and never contacts that origin again, so for the rest of the session the pod is simply there.
fork, vfork, execve, wait4 and pipe2 are all imported, so std::process::Command is not an emulation. The kernel spawns a Web Worker for the child, and the parent gets a real pid back and waits on a real exit status.
The pod ships 352 executables on PATH, mostly BusyBox applets but also git, curl, node, npm and python3. So a Rust program here can do what a shell script does: hand one child's stdout to the next child's stdin and let the kernel move the bytes.
std::thread::spawn lowers to clone3 with futex parking, and the kernel answers by starting a Web Worker that shares the same WebAssembly.Memory. The target spec is what allows it: --shared-memory, --import-memory, +atomics so memory.atomic.wait and memory.atomic.notify exist, and --export=__startThread as the entry point the kernel calls inside each new Worker. This is the same primitive ironpad uses for rayon cells, taken further, which is why Linux cells inherit its cross-origin-isolation requirement.
One habit does not carry over. available_parallelism() returns 1 in a pod, and so does nproc, so the usual way of sizing a pool hands you a pool of one. The count below is hard-coded because of it.
The threads themselves are real, and so is the throughput: on the machine this was written on, counting the primes below 6,000,000 took 1,798 ms on one thread and 209 ms across 16, an 8.6x speedup. Your numbers will differ, which is the point of running it.
cells run against one: a boot with no human watching is a boot nobody asked for.Command::output() calls back to back had the fourteenth fail with EAGAIN, and std panics there rather than returning the error to you.storageKey, so reloading this page hands you a fresh machine with an empty filesystem. A notebook whose behaviour depended on invisible history would read differently for its author than for you.SharedArrayBuffer, which needs cross-origin isolation, which an iframe on another origin does not have. The embed says so rather than offering a Run button that would do nothing.