A RISC-V Linux userland emulator compiled to WebAssembly. Runs BusyBox, Node.js, and the full npm/TypeScript toolchain entirely in the browser — no server required.
📚 Documentation lives at https://userland.run/docs/. This README is a quick orientation; the hosted docs cover the CLI/JS API, syscalls, host API, architecture, build, and the full SDK reference. NanoVM is the emulator core of userland.run — most users consume it through the SDK, the terminal web component, and the app catalog (see Part of userland.run below).
NanoVM emulates an RV64GC RISC-V CPU with ~80 Linux syscalls, enough to run:
- BusyBox — echo, cat, ls, sort, grep, head, tail, and more
- Node.js v25 — full runtime with
require(), fs, path, crypto, http, streams, Buffer, EventEmitter, async/await - npm toolchain — TypeScript compiler, ESLint, Prettier
Everything runs inside a single WASM module. The emulator handles memory management (brk/mmap), file I/O (via an in-memory POSIX filesystem), sockets, epoll, timerfds, futex-based threading, and ELF loading.
The default build is slim (~2.4 MB, BusyBox only): Node.js and the dev tools are not embedded — they
are installed on demand from the signed app catalog at
runtime. A fully-bundled build (make build-full, ~68 MB) embeds BusyBox + Node.js + devenv for offline use.
# Build the WASM module (~585KB without bundled binaries)
make build
# Run tests
make test
# Run a BusyBox command
node test/run.mjs images/busybox --cmd echo "Hello from RISC-V"
# Run a Node.js script
node test/run.mjs images/node --cmd node -e "console.log(process.arch)"The demo is a browser-based IDE with a file tree, code editor, and console/preview panel:
make demo # Builds WASM with bundled binaries + starts Vite dev serverRequires images/busybox, images/node, and build/devenv.tar.gz. See docs/build.md for details.
The demo includes examples that run inside the emulator: basic Node.js (hello world, filesystem, crypto), and HTTP servers with live preview in an iframe via a Service Worker bridge.
NanoVM follows Fabrice Bellard's approach to high-performance WASM interpreters:
- Monolithic
exec()function — Dense dispatch compiles to WASMbr_table(O(1) jump tables). Source code is split across files with#[inline(always)]; fat LTO fuses everything into a single function. #![no_std]Rust — No standard library, no heap allocation in the hot path. Zero crate dependencies (math likesqrtlowers straight to WASM opcodes).- Minimal host boundary — 5 WASM imports, ~30 exports. Filesystem I/O goes through a shared-memory protocol, not per-instruction callbacks.
- Cooperative threading — clone/futex-based multithreading with context switching at syscall boundaries.
The WASM binary is ~585KB without bundled binaries, or ~68MB with BusyBox + Node.js + devenv embedded.
src/
├── cpu.rs RV64GC interpreter loop (instruction decode & dispatch)
├── decode.rs Instruction field extraction
├── syscall.rs Linux syscall dispatch (~80 syscalls)
├── mem.rs Guest memory read/write
├── elf.rs ELF loader (segments, argv/envp/auxv)
├── types.rs VM struct (12,680 bytes, #[repr(C)])
├── exports.rs WASM exports
├── alloc.rs Bump allocator
├── host.rs Host import declarations
└── lib.rs Crate root
container/
├── nanovm.mjs Browser NanoVM wrapper (WASM + MemFS + virtual server)
└── memfs.mjs In-memory POSIX filesystem
web/demo/ React + Vite IDE demo app
test/ Test suite (Node.js runner + RISC-V ELF test binaries)
build/ Devenv Docker build scripts
Full, hosted documentation: https://userland.run/docs/ — getting started, CLI & JS API, syscalls, host API, networking, architecture, performance, build, and the complete SDK reference.
The source pages also live in this repo under docs/:
- Architecture — Design principles, memory layout, execution model, VM struct
- Syscalls — Complete syscall reference with handling modes
- Host API — WASM imports/exports and FS_PENDING protocol
- Virtual Server — HTTP request injection for the preview iframe
- Build Guide — Build targets, feature flags, testing, devenv setup
- Demo — Web IDE architecture and Service Worker bridge
NanoVM is the emulator core of the userland.run workspace — a set of repos that turn the raw VM into a product:
| Repo | What it is |
|---|---|
| nano | The RV64GC → WASM emulator core — this repo |
| sdk | @userland-run/nano-sdk — typed TypeScript SDK that drives the VM (code / terminal / serve / scripting / worker) |
| terminal | <nano-terminal> Shadow-DOM web component — the terminal UI, consumed via the SDK |
| catalog | Signed, content-addressed app marketplace (node, typescript, eslint, prettier, …) installed on demand |
| website | Landing page + the hosted docs at userland.run/docs |
$ make test
============================================
NanoVM Test Suite
============================================
--- MemFS Unit Tests --- 50 passed
--- ELF Execution Tests --- 6 passed (hello, test_suite, rvc, memory, syscalls, float)
--- BusyBox Smoke Tests --- 17 passed (echo, cat, head, tail, sort, id, ...)
--- Devenv Tool Tests --- 6 passed (node, tsc, npm, eslint, prettier)
============================================
Results: 24 passed, 0 failed
============================================