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.
← Select a region
Click a memory region to see how v86 implements it.
MMIO Block Map
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.
Virtual Address Translation
Virtual Address
TLB Lookup
Page Table Walk
Physical Address
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.
| Offset | Size | Field | Description |
|---|---|---|---|
| 64 | 32 B | reg32[8] | General-purpose registers: EAX ECX EDX EBX ESP EBP ESI EDI |
| 100 | 4 B | flags_changed | Bitmask of flags that need recalculation (lazy flags) |
| 104 | 4 B | last_op1 | First operand for lazy flag computation |
| 108 | 4 B | last_op2 | Second operand for lazy flag computation |
| 112 | 4 B | last_result | Result for lazy flag computation |
| 116 | 4 B | last_op_size | Operand size (1/2/4) for lazy flag computation |
| 120 | 4 B | flags | EFLAGS register (non-lazy bits: IF, DF, etc.) |
| 556 | 4 B | instruction_pointer | Current EIP (virtual) |
| 560 | 4 B | previous_ip | EIP before last instruction (for exceptions) |
| 564 | 4 B | eip_phys | Physical EIP (after TLB translation) |
| 668 | 32 B | sreg[8] | Segment selectors: ES CS SS DS FS GS LDTR TR |
| 684 | 32 B | dreg[8] | CR0 CR1 CR2 CR3 CR4 + page table roots |
| 800 | 4 B | protected_mode | 1 if CR0.PE set (protected mode active) |
| 804 | 4 B | is_32 | 1 if CS is a 32-bit segment (D/B bit) |
| 808 | 4 B | stack_size_32 | 1 if SS uses 32-bit stack operations |
| 812 | 4 B | in_hlt | 1 if CPU executed HLT and is idle |
| 832 | 128 B | reg_xmm32s[8][4] | XMM0–XMM7: 8 × 128-bit SSE registers |
| 960 | 8 B | current_tsc | 64-bit timestamp counter (RDTSC) |
| 1152 | 80 B | fpu_st[8] | FPU register stack: 8 × 80-bit extended-precision floats |
| 1232 | 4 B | fpu_status_word | FPU status word (C0–C3, stack pointer, ES) |
| 1236 | 4 B | fpu_control_word | FPU control word (precision, rounding, exception masks) |
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.