Memory System

How v86 manages a 4 GB WASM linear memory space — guest RAM, CPU state, MMIO regions, paging, and TLB.

One giant array

v86 allocates a single WebAssembly.Memory object. Both the CPU internal state (registers, flags) and the guest RAM live inside this same linear buffer. JavaScript accesses it via Uint8Array / Int32Array views; Rust accesses it natively in WASM.

Physical Address Space

Click a region to see details.

Physical Memory Map
Conventional RAM (640 KB)0x00000000 – 0x0009FFFF
VGA Video RAM0x000A0000 – 0x000BFFFF
Option ROMs (VGA BIOS)0x000C0000 – 0x000EFFFF
System BIOS ROM0x000F0000 – 0x000FFFFF
Extended RAM (configurable)0x00100000 – 0x3FFFFFFF
PCI MMIO / Virtio0x40000000 – 0x9FFFFFFF
VGA Linear Framebuffer0xE0000000 – 0xE3FFFFFF
I/O APIC / Local APIC0xFEC00000 / 0xFEE00000
BIOS Shadow (reset vector)0xFFFF0000 – 0xFFFFFFFF

← Select a region

Click a memory region to see how v86 implements it.

MMIO Block Map

128 KB blocks

The 4 GB physical address space is split into 32 768 blocks of 128 KB each (MMAP_BLOCK_BITS = 17). Each block can have independent read and write handlers. Non-mapped blocks go straight to WASM guest RAM.

0xA0000–0xAFFFF
VGA Plane Memory
4-plane write mode for CGA/VGA graphics. Reads go through the latches.
0xB0000–0xB7FFF
MDA Text Framebuffer
Monochrome display adapter region (rarely used by modern OSes).
0xB8000–0xBFFFF
CGA / VGA Text Buffer
Character + attribute pairs. VGA text mode 80×25: 2 bytes per character cell.
0xC0000–0xCFFFF
VGA BIOS ROM
Option ROM image (SeaBIOS VGA BIOS). Read-only; contains INT 10h video services.
0xF0000–0xFFFFF
System BIOS ROM
SeaBIOS ROM image. Read-only. CPU starts here after reset (via 0xFFFFFFF0 alias).
0xE0000000+
VGA Linear Framebuffer
Bochs VBE SVGA framebuffer. Direct pixel writes for high-resolution modes.
0xFEC00000
I/O APIC Registers
I/O APIC MMIO interface — index register at +0, data window at +0x10.
0xFEE00000
Local APIC Registers
Task priority, end-of-interrupt, LVT timer, spurious interrupt vector, etc.
Virtio BAR (PCI)
Virtio Device Registers
Each Virtio PCI device gets an MMIO BAR for queue notify registers and device config.

Virtual Address Translation

Virtual Address

0x08048000
32-bit linear addr from guest EIP or memory operand

TLB Lookup

tlb[vaddr >> 12]
Software TLB: 4096 entries, caches page translations

Page Table Walk

CR3 → PDE → PTE
On TLB miss: 2-level (or 3-level PAE) walk in guest RAM

Physical Address

0x00348000
Offset into WASM linear memory — direct array access
TLB miss cost

On a TLB miss, the Rust code reads the page directory entry (PDE) at CR3 + dir_index * 4 and the page table entry (PTE) at the address in the PDE. If either has the Present bit clear, a #PF exception is raised with the faulting address in CR2. The guest's page fault handler is then invoked to satisfy the fault (e.g. demand-page a process).

CPU State in WASM Memory

Key fields from src/cpu.js — byte offsets into WASM linear memory.

OffsetSizeFieldDescription
6432 Breg32[8]General-purpose registers: EAX ECX EDX EBX ESP EBP ESI EDI
1004 Bflags_changedBitmask of flags that need recalculation (lazy flags)
1044 Blast_op1First operand for lazy flag computation
1084 Blast_op2Second operand for lazy flag computation
1124 Blast_resultResult for lazy flag computation
1164 Blast_op_sizeOperand size (1/2/4) for lazy flag computation
1204 BflagsEFLAGS register (non-lazy bits: IF, DF, etc.)
5564 Binstruction_pointerCurrent EIP (virtual)
5604 Bprevious_ipEIP before last instruction (for exceptions)
5644 Beip_physPhysical EIP (after TLB translation)
66832 Bsreg[8]Segment selectors: ES CS SS DS FS GS LDTR TR
68432 Bdreg[8]CR0 CR1 CR2 CR3 CR4 + page table roots
8004 Bprotected_mode1 if CR0.PE set (protected mode active)
8044 Bis_321 if CS is a 32-bit segment (D/B bit)
8084 Bstack_size_321 if SS uses 32-bit stack operations
8124 Bin_hlt1 if CPU executed HLT and is idle
832128 Breg_xmm32s[8][4]XMM0–XMM7: 8 × 128-bit SSE registers
9608 Bcurrent_tsc64-bit timestamp counter (RDTSC)
115280 Bfpu_st[8]FPU register stack: 8 × 80-bit extended-precision floats
12324 Bfpu_status_wordFPU status word (C0–C3, stack pointer, ES)
12364 Bfpu_control_wordFPU control word (precision, rounding, exception masks)
Why fixed offsets?

Both Rust and JavaScript need to access the same CPU state. Fixed byte offsets are agreed upon at compile time: Rust uses global pointer constants; JavaScript uses new Int32Array(cpu.mem8.buffer)[offset / 4]. No serialization or copying is needed — they share the same underlying ArrayBuffer.