v86 Architecture Explorer

An interactive guide to how v86 emulates an x86 PC entirely in the browser using WebAssembly.

Rust
JIT & CPU core
JS
Device controllers
4 GB
Linear WASM memory
65 536
I/O ports
14+
Emulated devices
Browser Layer User-facing adapters — bridge between browser APIs and emulator
Keyboard Mouse Screen Audio Network Serial V86 API
Event Bus  (bus.js)
JavaScript Core Device models, I/O dispatch, CPU wrapper — all device state lives here
CPU (cpu.js) I/O Bus PCI VGA IDE PS/2 PIT RTC DMA UART NE2000 Virtio SB16 Floppy
WASM imports/exports  (v86.wasm)
WebAssembly Runtime Rust-compiled CPU engine — JIT compiles x86 blocks to WASM at runtime
JIT Engine Interpreter CPU State TLB / Paging Linear Memory FPU / SSE APIC

Key Data Flows

1
User presses a keyBrowser fires keydown event — keyboard.js adapter reads the scancode.
2
Adapter sends to busbus.send("keyboard-code", scancode) — routes through BusConnector pair.
3
PS/2 device receives itPS/2 controller (port 0x60) queues the scancode in its internal FIFO buffer.
4
IRQ1 raisedPS/2 calls cpu.device_raise_irq(1) — signals keyboard interrupt to PIC.
5
CPU handles interruptWASM CPU checks pending interrupts, suspends current instruction, vectors to IDT[33] (IRQ1 handler).
6
Guest OS reads port 0x60Kernel interrupt handler executes in al, 0x60 → WASM calls JS io_port_read8(0x60) → PS/2 dequeues scancode.
1
Guest writes to video memoryGuest OS writes pixel/text data to 0xA0000 (VGA) or VBE framebuffer at 0xE0000000+.
2
MMAP handler firesWASM calls mmap_write8(addr, value) → JS dispatches to VGA device's memory write handler.
3
VGA marks dirty regionvga.js records which scanlines changed and schedules a redraw on the next timer tick.
4
VGA pushes pixel databus.send("screen-fill-buffer", {x, y, w, h, buffer}) — sends changed pixels to adapter.
5
Screen adapter rendersscreen.js draws the pixel buffer into an HTML <canvas> using putImageData().
1
Guest issues ATA commandGuest OS writes command 0x20 (READ SECTORS) to IDE command register at port 0x1F7.
2
IDE device processes commandide.js reads the LBA address and sector count from ports 0x1F3–0x1F6, locates data in the disk image buffer.
3
DMA transfer to guest memoryIDE uses DMA controller to write sector data directly into guest RAM. DMA channel 3 maps physical pages.
4
IRQ14 raisedIDE raises IRQ14 signaling transfer complete — CPU vectors to disk interrupt handler.
5
Guest OS reads dataKernel's DMA callback finds the sector data now in guest RAM and resumes the blocked process.
1
V86 constructor calledstarter.js creates BusConnector, loads WASM binary (v86.wasm), sets up adapters.
2
Devices initializedcpu.init() creates all devices in order: PCI → RTC → DMA → VGA → PS/2 → UART → Floppy → IDE → PIT → Network.
3
BIOS loaded into ROMBIOS binary mapped at physical address 0xF0000. VGA BIOS mapped at 0xC0000. CMOS filled with boot config.
4
CPU starts at reset vectorWASM CPU initializes EIP to 0xFFFFFFF0 (16-byte alias into BIOS ROM). First instruction runs.
5
JIT warms upFrequently-executed x86 basic blocks get JIT-compiled to WASM functions. Subsequent runs are much faster.
6
Emulator-ready eventOnce BIOS finishes POST and loads bootloader, bus.send("emulator-ready") fires for the application.

Explore Components