Disclosure up front: I build Kyvenza, a paid VM manager for Apple Silicon. This post is about the platform constraints, not a pitch — I'll be specific about where my own app is worse than the free alternatives, because that's the part people actually want to know.
Every few weeks someone asks me why there are only four apps that boot Windows on an M-series Mac, and why two of them are free while one costs a hundred dollars a year. The short answer is that Apple gives you two very different APIs to build on, and the choice between them decides almost everything the user will experience afterward.
Here's what I learned implementing it.
The fork in the road: two Apple frameworks
macOS ships two virtualization APIs, and they sit at very different altitudes.
Virtualization.framework is the high-level one. You hand it a bootloader, a disk, some virtio devices, and it runs. Apple has already written the device models, the boot flow, and — importantly — a paravirtualized GPU with real Metal acceleration behind it. If you're running Linux or macOS guests, this is a genuinely great API. Maybe 200 lines of Swift gets you a working Ubuntu VM.
Hypervisor.framework is the low-level one. It gives you vCPUs, memory mapping, and exit handling. That's it. No devices, no firmware, no disk controller. You bring your own device model, which in practice means you bring QEMU.
The natural instinct is to use the high-level API for everything. I did, for the first year. Then I tried to boot Windows 11.
Windows 11 refuses to install into a clean VM
Windows 11's setup checks for TPM 2.0 and Secure Boot before it will proceed. On real hardware that's a chip on the motherboard and a key database in the UEFI firmware. In a VM, both have to be emulated.
Virtualization.framework does not give you an emulated TPM. There's no API for it. You can set up a Linux VM or a macOS VM in a few dozen lines, and then hit a wall the moment the guest is Windows.
So the Windows path becomes: QEMU on top of Hypervisor.framework, with
-
swtpmproviding a software TPM 2.0 the guest can talk to over a virtual interface, and - EDK2/OVMF built for AArch64, with the Microsoft Secure Boot keys already enrolled in the variable store.
Get the key enrollment wrong and the installer throws "This PC can't run Windows 11" with no further detail, which is a fun afternoon. There are registry bypasses floating around for this (LabConfig, BypassTPMCheck), and they do work, but they leave you with a Windows install that Microsoft considers unsupported and that may or may not take future feature updates. Emulating the thing properly is more work once and no work afterward.
This is the split that explains the whole market. Parallels and VMware both wrote their own device models over years. UTM wraps QEMU and exposes its configuration surface to you directly. I went QEMU-on-Hypervisor.framework for Windows and Virtualization.framework for everything else, which means my app has two engines with different capabilities depending on the guest — a design decision I'd describe as "honest but annoying to document."
"Is Windows on ARM slow?" is two questions
This one comes up constantly and the answer depends on which layer you mean, because there are two and they behave completely differently.
Layer one is the host. Your guest is Windows 11 on ARM64. Your CPU is ARM64. Guest instructions execute on the real processor. There is no translation here at all — this is virtualization, not emulation, and the overhead is the normal few percent you'd get from any hypervisor.
Layer two is inside the guest. Most Windows software is still shipped as x86 or x64 binaries, so Windows translates them itself. On 24H2 and later that's Prism; before that it was the older xtajit path. This is Microsoft's problem, not your VM app's, and it's the same code whether you paid $99 or nothing.
Which means: the choice of VM app has almost no effect on how fast your Windows applications run. Fusion, UTM, Parallels and mine all hand the same ARM64 instructions to the same CPU, and the same Microsoft translator runs inside all four. People shopping on benchmarks for this are measuring the wrong thing. What you're actually buying is integration — clipboard, shared folders, USB, GPU.
The genuinely slow path is UTM's x86 emulation mode, where QEMU interprets x86 instructions in software. That runs somewhere in the 5–15× slower range depending on workload. But that's a separate mode you opt into when you have an x86-only installer and no alternative.
Three things that don't work, and why
These aren't bugs anyone is going to fix. They're consequences of the architecture.
x86 drivers will never load
Prism translates user-mode code. Kernel-mode code is not translated — it runs on the bare processor, so it has to be native ARM64. Any driver compiled for x86 Windows simply cannot load, which takes out older VPN clients, older endpoint security agents, and any peripheral whose vendor never shipped an ARM64 driver.
People hit this and assume the VM is broken. The VM is fine. The driver is the wrong architecture for the CPU, and no amount of configuration changes that.
Nested virtualization
On ARM, the virtualization extensions live at EL2. The host hypervisor occupies EL2, so the guest runs at EL1 and can't see the hardware it would need to be a hypervisor itself. Without explicit nested-virt support plumbed through, anything inside your Windows guest that wants a hypervisor — Hyper-V, WSL2, Docker Desktop — won't start.
M3 and later added hardware support for this, and Apple has begun exposing it for Linux guests, but it doesn't currently reach Windows guests through the QEMU path. If your plan was "Windows VM running Docker running containers," collapse a layer: run Docker on macOS directly, or use an ARM Linux VM.
Kernel-level anti-cheat
BattlEye, EAC and Vanguard detect virtualization and refuse to initialize, deliberately. This isn't a capability gap you can engineer around — detecting VMs is the product working as designed. Single-player and older titles are generally fine; competitive multiplayer is not, in any of the four apps.
The virtio driver bootstrap problem
Small thing, but it burns people during install. Linux ships virtio drivers in the kernel, so a Linux guest sees virtio disks and network adapters immediately. Windows doesn't. So at the point in setup where Windows asks where to install, it sees no disks — the controller is virtio and it has no driver for it.
The fix is mounting a second virtual CD with the virtio drivers on it and loading them from the installer's "Load driver" dialog. Every app handles this somehow; the only real difference is whether you have to know about it. I mount that CD automatically because the support emails were exhausting.
Where my own app is worse
Since I'm posting this on DEV under my own name, the honest comparison:
Going the QEMU-on-Hypervisor.framework route for Windows bought me a TPM and cost me Apple's paravirtualized GPU. Windows guests in Kyvenza render in software, on the CPU. No 3D acceleration. Also no audio and no USB passthrough, NAT-only networking for Windows guests, and snapshots require the VM to be powered off. Shared folders for Windows aren't done yet — clipboard works, files currently mean a network share or an attached disk image.
So:
- GPU, USB, or Microsoft's authorized path → Parallels. It's the only one Microsoft names in its support docs, and the only one with real graphics acceleration in a Windows guest. Worth the subscription if you live in Windows daily.
- Zero budget, Windows is the only guest → VMware Fusion. Free for personal and commercial use since Broadcom's acquisition. The download lives behind a Broadcom portal built for IT departments, which is the main tax.
- You need x86 emulation, or you want to read the source → UTM. GPLv3, actively developed, the only one that emulates x86 on Apple Silicon.
- Mixed library of macOS/Linux ARM guests with Windows as the occasional Office box, and you hate subscriptions → that's the case I built for. $49 once, 7-day trial.
If your Windows work needs the GPU, don't buy mine. It won't do the thing.
The part I'd want to know before starting
If I were picking this up fresh: decide your guest OS mix first, then pick the framework. Virtualization.framework is a pleasure to work with and will cover Linux and macOS guests with a fraction of the code. The moment Windows enters the requirements you're writing a QEMU integration, and that's not a detour — it's a second engine, with its own device model, its own bug surface, and its own documentation burden forever.
I'd make the same call again. I would have budgeted differently for it.
Happy to go deeper on any of this in the comments — the swtpm and EDK2 key-enrollment part especially, since there's very little written about it for the AArch64 case.
Top comments (0)