copy/v86: an x86 PC emulator and x86-to-wasm JIT that runs in the browser
x86 PC emulator and x86-to-wasm JIT, running in the browser
At a glance
- What is it?
- v86 emulates an x86 PC in JavaScript and WebAssembly, translating guest machine code into wasm modules at runtime. It is aimed at embedding a bootable PC in a web page, not at replacing QEMU or KVM.
- Who is it for?
- Adopt copy/v86 when you need a bootable x86 PC inside a web page, an embeddable demo, or a teaching environment where the guest is a 32-bit OS such as FreeDOS, Windows 95/98, ReactOS or a 32-bit Linux image. Do not adopt it as a general virtualization layer: there is no multicore support, no 64-bit guest, and the README lists missing protected-mode and debugging features.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What copy/v86 emulates, and which guests it actually runs
copy/v86 emulates an x86-compatible CPU and a set of PC peripherals in the browser. The README describes the instruction set as roughly Pentium 4 level with full SSE3 support, and lists the emulated hardware: an FPU built on Berkeley SoftFloat, an 8272A floppy controller, an 8042 keyboard controller with PS/2 mouse support, an 8254 PIT, an 8259 PIC, partial APIC support, a CMOS RTC, a VGA card with SVGA and Bochs VBE extensions, a partly incomplete PCI bus, an IDE controller with a built-in ISO 9660 CD-ROM generator, an NE2000 PCI network card, virtio filesystem, network and balloon devices, a SoundBlaster 16, and a Hayes-compatible modem.
The intended user is someone who wants a real operating system booting inside a page: a demo, a museum piece, a training sandbox, or a browser-based lab. The compatibility list is the honest part of the project. FreeDOS, Windows 1.01 and MS-DOS run very well. ReactOS, KolibriOS, Haiku, QNX, 9front, FreeBSD and SerenityOS (32-bit only) work. Linux works, but 64-bit kernels do not. Windows 1, 3.x, 95, 98, ME, NT and 2000 work reasonably well, with the caveat that Windows 2000 and higher need the PC type changed from ACPI PC to Standard PC. Windows XP and Vista work only under conditions the README points to in issue threads. Plan 9 and OS/2 do not work. That list is the first thing to check against your own image, because it is specific to versions, not to families.
The demo profiles linked from the README cover 9front, Arch Linux, Android-x86 1.6-r2 and 4.4-r2, BasicLinux, Buildroot Linux, Damn Small Linux, ELKS, FreeDOS, FreeBSD, FiwixOS, Haiku, SkiffOS, ReactOS, Windows 2000, 98, 95 and 1.01, MS-DOS 6.22, OpenBSD, Oberon, KolibriOS, SkiftOS and QNX. Each one is a URL with a profile parameter, which is also the quickest way to see whether the emulator behaves acceptably for a given OS before you build anything. Guest setup is documented per family: Alpine Linux and Debian with xfce have Dockerfiles under tools/docker/, while MS-DOS/FreeDOS, Windows 3.1x, Windows 9x, Windows NT and Arch Linux each have a document under docs/. There is also a separate repository, copy/images, that the README points to for information on disk images.
The JIT: guest machine code becomes WebAssembly at runtime
The mechanism that separates v86 from a plain interpreter is in the project description: machine code is translated to WebAssembly modules at runtime. The repository layout backs this up. The Rust crate in Cargo.toml builds a cdylib from src/rust/lib.rs, and the Makefile generates instruction tables into src/rust/gen/jit.rs, jit0f.rs, interpreter.rs, interpreter0f.rs, analyzer.rs and analyzer0f.rs. The generator scripts live in gen/, and the Makefile names generate_jit.js, generate_interpreter.js and generate_analyzer.js as separate dependencies. So the pipeline is: a JavaScript generator emits Rust tables, the Rust crate compiles to wasm, and the emulator uses those tables to translate and run guest code.
Two build profiles matter. The default make target produces build/v86-debug.wasm, while make all produces the optimized set: build/v86_all.js, build/libv86.js, build/libv86.mjs and build/v86.wasm. Closure Compiler is used for the optimized JavaScript, which is why java is a build requirement; debug.html skips it. The Cargo.toml release profile sets lto = true, opt-level = 3, panic = "abort" and incremental = false, which is what you would expect from a crate whose output is loaded as a wasm module rather than run as a normal binary. There is also a profiler feature in Cargo.toml, and the README links a profiling document.
The FPU is worth calling out as a design trade-off. The README states calculations use Berkeley SoftFloat and therefore should be precise but slow, and that trigonometric and log functions are emulated using 64-bit floats and may be less precise. Precision over speed is a reasonable choice for compatibility, and it is also the reason floating-point-heavy guest workloads are not where this emulator will feel fast.
The JavaScript side is split between the browser bundle and the library. build/v86_all.js is the browser target named in the Makefile, while libv86.js and libv86.mjs are the embeddable library, and package.json declares build/libv86.mjs as the package main with v86.d.ts as the types entry. The Makefile also defines a STRIP_DEBUG flag that adds --v86-strip-debug to the build when set to true, which suggests the debug and release wasm differ in more than optimization level. Closure Compiler runs with a long list of error-level checks, including checkTypes, checkVars and conformanceViolations, so the optimized build is type-checked by the compiler rather than only minified.
Building copy/v86 from source and booting a first guest
The README lists the build requirements plainly: make, Rust with the wasm32-unknown-unknown target, a clang version compatible with Rust, java for Closure Compiler (not needed for debug.html), nodejs (the README says a recent version is required and that v24.16 is known to be working), and for tests nasm, gdb, qemu-system, gcc, libc-i386 and rustfmt. A full Debian setup is in tools/docker/test-image/Dockerfile, which is the fastest way to get a matching toolchain.
Build the debug target first. This is the default make target and produces build/v86-debug.wasm:
makeFor the optimized build, run make all. This produces build/v86_all.js, build/libv86.js, build/libv86.mjs and build/v86.wasm:
make allROM and disk images are loaded over XHR, so the README warns that opening index.html directly from disk will not work. Serve the directory over a local web server, which the Makefile wraps:
make runIf you only want to embed the emulator in a page, the README points at libv86.js and the examples/ directory, and says the file can be downloaded from the releases section. For bundler-based setups (Vite, React, Next, Webpack) there is an npm package named v86. The package.json in the repository declares version 0.5, main build/libv86.mjs, types v86.d.ts, and ships Readme.md, LICENSE, the type definitions, build/libv86*.js and its source map, build/*.mjs, and build/v86*.wasm. After installing, the examples are the reference: examples/basic.html for a minimal boot, examples/worker.html to run the emulator in a Web Worker, examples/save_restore.html for state, examples/two_instances.html for more than one machine, examples/nodejs.js for running it under Node, and examples/serial.html or examples/tcp_terminal.html for console access. The README does not document a rollback path for saved state, so treat save and restore as forward-only until you have checked the example yourself.
Where copy/v86 is the wrong tool
The limitations are documented rather than hidden, and they decide the use cases. There is no multicore support, so a guest that expects SMP will not get it. There are no 64-bit extensions, which rules out any 64-bit kernel; the README says Linux works, but 64-bit kernels are not supported. Task gates and far calls in protected mode are missing, some 16-bit protected mode features are missing, single stepping via the trap flag and debug registers is missing, and some exceptions, especially floating point and SSE, are missing. If you are debugging a guest at the instruction level, the absence of trap-flag single stepping is a direct blocker.
The PCI bus is described as partly incomplete and not used by every device, so a guest with drivers that assume a full PCI topology may misbehave. Windows 2000 and newer need the PC type switched from ACPI PC to Standard PC, which is a manual guest-side change, not a setting v86 makes for you. Windows XP, Vista and 8 work only under conditions the README points to in issue threads, and known boot issues are tracked in issues #250, #433, #507, #555, #620 and #645. Plan 9 and OS/2 do not work at all. NetBSD needs a custom kernel. OpenBSD needs a specific boot configuration: at the boot> prompt you type boot -c, then at the UKC> prompt disable mpbios and exit. If your requirement is a supported, general-purpose hypervisor, this is the wrong project, and no amount of configuration will change the missing CPU features.
The FPU limitations are a second failure mode that is easy to miss. Because trigonometric and log functions are emulated using 64-bit floats and may be less precise, and because not all FPU exceptions are supported, a guest that depends on exact exception behaviour or on high-precision transcendentals can produce results that differ from real hardware without crashing. That kind of divergence is worse than a failed boot, because it surfaces as wrong numbers rather than an error message.
How copy/v86 differs from QEMU and from a server-side emulator
The obvious comparison is QEMU. QEMU is a native process that uses hardware virtualization (KVM) or dynamic binary translation on the host CPU, and it targets a much wider set of architectures and machine types. v86 runs inside a browser tab, compiles guest code to WebAssembly, and therefore cannot use hardware virtualization extensions at all: every instruction is emulated or JIT-compiled by the wasm runtime. That is the fundamental difference in approach, and it explains the compatibility ceiling. QEMU will boot a modern 64-bit Linux kernel with multiple vCPUs; v86 will not, by design.
The second comparison is with running an emulator on a server and streaming the display to the browser. That approach keeps a native emulator and pays for it in latency, hosting cost and a persistent backend. v86 moves the whole machine into the client, so there is no server process, and the disk image is fetched by the page. The trade-off is that the guest is limited to what the browser-side emulator supports, and the image has to be reachable by the client. There is a networking document and a dial-up modem document in docs/, plus an NE2000 PCI card and virtio network devices, so guest networking is a supported area, but it is browser-side networking with its own constraints rather than a bridged host interface.
A third option is a pure JavaScript interpreter with no JIT, which is simpler to port and easier to audit but much slower for sustained guest execution. v86 sits between that and native virtualization: the generated Rust tables plus the wasm compilation step buy speed at the cost of a build toolchain that needs a specific Rust target and a matching clang. If you cannot control that toolchain, the npm package is the escape hatch, but it ships a prebuilt wasm binary rather than the source of it.
Licence, maintenance and the cost of upgrading
The repository is licensed BSD-2-Clause, and package.json repeats that identifier for the npm package. The repository also contains a LICENSE.MIT file at the top level alongside LICENSE, which means the tree carries more than one licence file and you should read both before redistributing a build. This is not legal advice; the point is that a single SPDX identifier in package.json does not by itself describe every file in the tree.
On maintenance, the last push was on 2026-09-14, and the latest release is dated 2026-09-14. The repository is not archived. That is recent enough that the project is not dormant, but the compatibility list still carries open boot issues for Windows guests, so recent commits and a working guest are separate questions.
Upgrade cost is dominated by the build toolchain, not by an API. The build needs Rust with the wasm32-unknown-unknown target, a clang compatible with that Rust version, java for Closure Compiler on optimized builds, and a recent nodejs (v24.16 is the version the README names as known working). Moving to a newer Rust or clang can break the wasm build before any of your own code changes. The npm package pins nothing about the wasm binary's compatibility with a guest image, so if you ship a bundled image, re-test the boot after every upgrade. The Cargo.toml marks the crate publish = false, so the Rust side is not distributed as a library; you consume the JavaScript and wasm artifacts.
The Makefile also has a WASM_OPT variable defaulting to false, so binary-size optimization of the wasm is opt-in rather than part of the default path. If you are shipping to browsers over a slow connection, that is a knob to check, and turning it on changes the artifact you distribute.
Embedding v86 in a page: what the examples cover
The examples directory is the practical documentation. examples/basic.html is the minimal embed, examples/worker.html moves the emulator into a Web Worker so the main thread stays responsive, examples/async_load.html covers loading an image asynchronously, examples/save_restore.html covers machine state, examples/destroy.html covers teardown, and examples/two_instances.html runs more than one machine. For non-browser use there is examples/nodejs.js and examples/nodejs_state.js. Console-oriented integrations have examples/serial.html and examples/tcp_terminal.html, and examples/broadcast-network.html covers networking between instances.
There are also examples for language runtimes and low-level output. examples/lua.html runs Lua, examples/lang.html covers language support, and examples/sectorc.html is a small C example, which is the kind of thing to look at if you want to see how little guest code is needed to get something running. examples/alpine.html, examples/arch.html and examples/debian-9p.html show Linux guests with different disk setups, and examples/debian-raw-disk.html shows a raw disk rather than a 9p filesystem. The 9p path has its own documents, docs/filesystem.md and docs/linux-9p-image.md, so a Linux guest can mount a shared filesystem instead of an image baked into the page.
The README does not describe the JavaScript API in detail; it points at libv86.js and the examples, and the npm package ships v86.d.ts for types. If you are evaluating this for a product, the type definitions plus the examples are what you have to work from. That is a real gap: there is no single API reference page, so budget time for reading the examples and the .d.ts file rather than expecting a complete manual.
Editorial conclusion
Adopt copy/v86 when you need a bootable x86 PC inside a web page, an embeddable demo, or a teaching environment where the guest is a 32-bit OS such as FreeDOS, Windows 95/98, ReactOS or a 32-bit Linux image. Do not adopt it as a general virtualization layer: there is no multicore support, no 64-bit guest, and the README lists missing protected-mode and debugging features. Before committing, build from source with make all and verify your exact guest image boots, because the compatibility list is per-OS and per-version, and check the npm package version against the repository's package.json, which declares 0.5.
Frequently asked questions
How does v86 work?
It emulates an x86-compatible CPU and PC hardware, and translates guest machine code into WebAssembly modules at runtime to get usable performance. The Rust crate builds a wasm module from generated instruction tables, and the JavaScript side drives the emulated devices.
Is there a website that emulates Windows 95?
The v86 homepage at copy.sh/v86 hosts demo profiles, and the README lists a Windows 95 profile among them. The compatibility list says Windows 95 works reasonably well.
Can x86 emulate Arm?
v86 goes the other direction: it emulates x86 hardware and compiles x86 guest code to WebAssembly, which is a portable target rather than an Arm CPU. The README does not describe emulating Arm guests.
How can I run Windows XP in v86?
The README says Windows XP works only under certain conditions and points to issue threads #86 and #208, plus a Windows NT guest setup document in docs/windows-nt.md. It does not state a guaranteed configuration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/copy-v86)