WIE
Exécutez des binaires Windows 64-bit sur macOS Apple Silicon sans Windows, sans Wine, ni machine virtuelle.
Ce que c'est
Je voulais exécuter des programmes Windows 64-bit sur macOS Apple Silicon sans lancer de machine virtuelle lourde ni configurer des préfixes Wine. WIE (Wie Is Emulator) est un émulateur en mode utilisateur écrit en Rust. Il compile le code x86-64 en ARM64 à la volée avec Cranelift, intercepte les appels Win32 et les exécute directement sur macOS.
Les applications graphiques ouvrent de vraies fenêtres macOS avec menus Cocoa natifs, polices système et sélecteurs de fichiers intégrés. L'accès disque passe par une bouteille applicative dédiée ou directement par les fichiers choisis dans les fenêtres d'ouverture natives.
Architecture
Le projet est découpé en crates Rust ciblées :
- Chargeur PE64 & mémoire : Analyse des en-têtes PE, mapping mémoire, résolution des tables d'imports/exports et gestion des exceptions structurées (SEH).
- JIT Cranelift par blocs : Compilation à la volée des blocs d'instructions x86-64 vers du code machine natif ARM64.
- Runtime hôte Win32 : Réimplémentation de Kernel32, User32 et GDI32 en Rust, redirigés vers les API système de macOS.
- Moteur de rendu Direct3D 9 : Rasteriseur logiciel avec shaders VS 2.0 / PS 2.0 (contrôle de flux), textures mipmappées et rendu Metal via
wgpu.
flowchart TD
subgraph Host["Host macOS (ARM64)"]
CLI["wie CLI / Runner"]
API["Win32 Host Handlers (Kernel32, User32, GDI32)"]
UI["Native Cocoa Windows & Dialogs"]
D3D["Direct3D 9 Software Renderer"]
GPU["wgpu (Metal Backend)"]
end
subgraph Guest["Guest x86-64 PE64"]
PE["PE64 Binary Loader"]
JIT["Cranelift Block JIT (x86-64 to ARM64)"]
MEM["Guest Memory / Bottle Filesystem"]
end
PE --> JIT
JIT <--> MEM
JIT -->|WinAPI Import Thunks| API
API --> UI
API --> D3D
D3D --> GPU Voir un vrai binaire Windows ouvrir une fenêtre Cocoa native sans Wine, c'était assez magique la première fois.
Ce qui tourne aujourd'hui
L'émulateur fait déjà tourner des exécutables réels :
- Bloc-notes Windows (Notepad) : Interface complète avec menus, Rechercher/Remplacer, polices système et impression native via macOS.
- 7-Zip Console : Création, inspection et extraction d'archives
.7z. - Threads & synchronisation : Threads 1:1, sections critiques, événements Windows, sémaphores et TLS.
- Jeux en terminal : Snake et 2048 compilés avec mingw-w64.
Les parties difficiles
Traduction d'ABI et conventions d'appel. Windows x64 utilise RCX/RDX/R8/R9 avec 32 octets de shadow space sur la pile ; macOS ARM64 utilise AAPCS64. Aligner la pile, préserver les registres non-volatils et synchroniser l'état JIT-hôte m'a valu des heures de débogage dans lldb.
Rasteriseur Direct3D 9. Coder un moteur de rendu logiciel complet avec mipmapping, test de profondeur et shaders programmables de zéro en Rust pur a demandé pas mal de sueur mathématique.
Ce que j'ai appris
L'émulation, c'est 10% d'architecture propre et 90% de cas particuliers. Les vrais binaires exploitent des comportements non documentés de Win32, des accès mémoire non alignés et des flags CPUID bizarres. On ne peut pas juste suivre une doc : il faut lancer Notepad et réparer ce qui plante.