AArchX

Compatibility

Everything here was run by hand and dated. None of it is part of an automated gate, so treat it as “this was seen working on that day” rather than as a guarantee. What is gated is described under Testing.

“Works” means the program did its job: a command-line tool printed what it prints natively, an application drew its main window and stayed up.

Which macOS

Both macOS 26 and 27 run cache mode. macOS 27 keeps the x86-64 shared cache in the Rosetta cryptex, /System/Volumes/Preboot/Cryptexes/Rosetta, and macOS 26 keeps it in /System/Volumes/Preboot/Cryptexes/OS. AArchX looks in the Rosetta cryptex first and falls back to the OS one. From 2026-09-19 to 2026-10-03 it looked only in the Rosetta cryptex, so cache mode did not start on macOS 26 at all in that window. Each row below says where it was measured.

On macOS 27 the bundled applications are arm64 only. Chess, Calculator, Dictionary, Font Book, Grapher, Digital Color Meter, Activity Monitor, Console, TextEdit, Preview and Script Editor ship without an x86-64 slice, so there is nothing to translate: ocerz says so and exits, and a program running under it that starts one of them gets the native binary, as it would under Rosetta. Safari, Steam, Discord and Brawlhalla still carry Intel code and can be run on macOS 27.

Command-line tools

Command-line tools are the most reliable case. Sixteen are checked against their native output byte for byte by tools/apptest.sh cli, and all sixteen match: uname, sw_vers, echo, ls, id, basename, wc, sort, uniq, head, grep, file, xxd, nm, plutil and openssl (version and dgst -sha256). A wider sweep of 74 system tools run with read-only arguments in cache mode, each compared against its own x86-64 slice under Rosetta, agreed on every one, sed, awk, otool, shasum, curl, sqlite3 and perl among them. The only disagreements were tools whose output is meant to change between two runs, such as ps and vm_stat.

For scale: 622 of the 969 executables in /usr/bin and /bin had an x86-64 slice on the machine that sweep ran on. The rest are arm64 only and there is nothing to translate.

sw_vers failed on macOS 27 until 2026-09-22, and the cause was not what it looked like. CoreFoundation names Foundation as an upward dependency, which is how a library declares the back edge of a dependency cycle, and AArchX left upward links out of the initializer walk entirely. Foundation’s code was reachable anyway, because the whole shared cache is mapped, so nothing looked missing; what never ran was Foundation’s own initializer, and the first +[NSString stringWithFormat:] recursed until the 8 MB guest stack ran out. An upward dependency is now initialized after the image that declares it, which is dyld’s own rule. OCERZ_NO_UPWARD_INIT=1 restores the old behaviour, and tests/dynamic/upward_init.c is the regression test.

On macOS 26.7.1 sw_vers crashed as well until 2026-10-03, for a related reason. There libobjc links libswiftCore upward and libswiftCore links Foundation upward, and AArchX initialized an upward dependency right after the library that declared it, so Foundation’s subtree ran before CoreFoundation, its own dependency, had finished. An upward dependency now waits until the whole walk from the program is done, which is the order Rosetta’s dyld uses (tests/dynamic/upward_defer.c), and all sixteen tools match on macOS 26.7.1 too.

/usr/bin/python3 on a Mac without Xcode is a stub that asks xcrun to find the real interpreter, and xcrun has no x86-64 slice to load, so it fails before any Python runs. A real Python installation does not go through that path.

Ollama. The command-line binary (Contents/Resources/ollama, Go with cgo) works as of 2026-09-13: ollama --version prints what it prints natively, ollama serve answers its HTTP API (/api/version, /api/tags, /api/show), and llama-server --list-devices lists the same devices. That took AVX2 (Go turns on its AVX2 paths under Rosetta without checking CPUID), sigaltstack reporting SS_DISABLE, dlopen refusing arm64-only dylibs, bare @loader_path rpaths, and constructors in programs that do not link CoreFoundation. The menu-bar app runs too: in a 30-second run it started its own server, served its settings page to its window and shut down cleanly on SIGTERM. Nobody has run a model under AArchX yet.

Applications, cache mode

Application Result Measured
Chess works and plays: board window, and the sjeng engine subprocess answers moves macOS 26.6, 2026-09-11
Calculator, Dictionary, Font Book, Grapher, Digital Color Meter work macOS 26.6, 2026-09-11
Activity Monitor, Console window on screen; each logs one optional library that is not in the x86-64 cache macOS 26.6, 2026-09-11
TextEdit, Preview, Script Editor run, and open a document window when given a file macOS 26.6, 2026-09-11
Safari runs and browses 2026-09-21, confirmed by the author
Steam, the x86-64 macOS client works with -cef-disable-gpu; see below 2026-09-12
Discord the Electron client reaches its signed-in app view, seven processes as under Rosetta 2026-09-22, confirmed by the author
Brawlhalla reaches its menus, launched from a running Steam client; see Games 2026-09-22, confirmed by the author
Photoshop stops during startup: an initializer runs while libobjc holds its runtime lock, and the recursive acquisition aborts 2026-09-22
Photos aborted on a graphics call whose causes were fixed by 2026-09-22; not run again since 2026-09-13

Safari took the longest. It used to start and never show a window: WebKit’s allocator could not suspend a thread, because AArchX’s thread-suspension emulation was missing the main thread and the worker threads AArchX started ended without running the guest’s thread-exit path. Both were fixed on 2026-09-13, and the WebKit work continued through macOS 27.

Discord’s renderers died the moment they started until mach_vm_region began answering from the guest’s own map: Electron asks about a region it expects to be read-only and stops when the answer says otherwise.

Photoshop stopped, but finding where produced four general fixes on 2026-09-22: upward dependencies are initialized, a dylib with no export trie is indexed from its symbol table (21 of the 60 dylibs Photoshop ships are of that kind), imports declared with -undefined dynamic_lookup are allowed to be missing, and every unimplemented dyld API answers zero in both result registers.

Photos aborted because CGLChoosePixelFormat returned 10002 for every attribute set. There were three causes, all in AArchX’s loader: a symlinked plugin path the cache check rejected, dlsym on a shared-cache image searching the whole cache, and the GPU bug described next.

The GPU

Until 2026-09-22 a translated program saw no GPU at all. MTLCreateSystemDefaultDevice returned nil and MTLCopyAllDevices returned no devices, where Rosetta and a native arm64 build both report Apple M5. OpenGL then had only its software renderer to offer: CGLQueryRendererInfo listed 0x1020400 and not the accelerated 0x1027f00, so CGLChoosePixelFormat failed for every accelerated attribute set.

One bug caused all of it. In cache mode the dyld APIs answer _dyld_image_count, _dyld_get_image_name and _dyld_get_image_header from the image list AArchX keeps. dlopen of a dylib on disk added to that list; dlopen of an image in the shared cache returned its mach header and added nothing. The library was loaded and its symbols resolved, while every walk of the image list said it was not there. libobjc keys its per-image queries off that list, and Metal, which loads its GPU driver that way, concluded there was no device.

A cache image is now registered along with its dependencies at the end of the dlopen, after the Objective-C mapping and the initializers. The order matters: libobjc calls back into the dyld APIs while it maps an image, and an image already in the list when that callback arrives is one it takes as handled, which would cost the new framework its categories. tests/dynamic/dlopen_image_list.c holds the check. Metal, CGLQueryRendererInfo, CGLChoosePixelFormat and CGLCreateContext now return what they return under Rosetta, with GL_RENDERER reading Apple M5.

Steam, the macOS client

The x86-64 macOS Steam client comes up with its full UI, confirmed 2026-09-12: the bootstrapper, ipcserver, the client and the CEF web helper with its GPU, utility and renderer processes, all translated. After its first update the client lives in Application Support:

./ocerz "$HOME/Library/Application Support/Steam/Steam.AppBundle/Steam/Contents/MacOS/steam_osx" -cef-disable-gpu

CEF runs without GPU acceleration there and renders through SwiftShader, which works and is slower. It has not been retried with GPU acceleration since the GPU fix above. Some launches stall before the web helper starts its child processes, with the JIT lock held inside translation; relaunching gets past it.

What it took, roughly in order:

In native mode, on macOS 27 on 2026-09-21, the same client came up as well: the login window, then the 1280 by 800 Steam window with its menus and store view, the GPU process running hardware ANGLE over the host’s own OpenGL, and two renderers. From the web helper starting to the main window took 23 s in native mode and 34 s in cache mode on the same machine; the arm64 Steam takes 2 to 4 s. hardwareupdater still fails to run its script there, and several imports Steam has not reached yet are stubs. See Native mode in depth.

Games

Games are launched the way Steam launches them under a compatibility tool: set the game’s launch options to /path/to/ocerz %command%. Steam then passes the game’s .app bundle, and AArchX runs the executable its Info.plist names. It also honours DYLD_INSERT_LIBRARIES in cache mode, which is how Steam injects its loader and overlay: AArchX moves the variable aside so the host’s dyld does not load the arm64 slices into AArchX itself, then opens the libraries in the guest before the game’s entry point. OCERZ_FPS=1 prints the frame rate once a second, since Steam’s own overlay counter relies on dyld interposing, which AArchX does not do.

Brawlhalla, an Adobe AIR game, reaches its menus against a running Steam client (2026-09-22). Measured that day against Rosetta, with one Steam client for both: the first frame took 5.3 s against 0.54 s. Its main thread used to saturate a core where Rosetta uses a quarter to half of one. Three JIT changes that evening brought it to about three quarters of a core while doing 75% more work per second, and at the menu it now spends most of its time waiting on the display, as under Rosetta:

32-bit Mac games run through m32 (October 2026). From Steam, Batman: Arkham Asylum, Left 4 Dead 2 and Call of Duty: Modern Warfare 3 play; see 32-bit Mac games from Steam. Free games downloaded as they were released play too: AssaultCube, SuperTuxKart, Neverball and Neverputt, Battle for Wesnoth, Teeworlds and Nexuiz (started from its .app, whose executable is a shell script). Their sound plays. 32-bit apps such as Adium, VLC 1.1.12 and Kega Fusion open their windows. What each needed is in 32-bit Mac programs.

Wine and i386

Wine 11.8 runs x86-64 and i386 PE applications through AArchX. With a WoW64 prefix, 32-bit Notepad and WineMine load winemac.drv, open titled Cocoa windows and stay up:

WINE="/path/to/Wine Devel.app/Contents/Resources/wine"
export WINEARCH=wow64
export WINEPREFIX="$HOME/.wine-ocerz"

./ocerz "$WINE/bin/wine" wineboot -u
./ocerz "$WINE/bin/wine" notepad

Wine boots to cmd /c ver in about fourteen seconds. Rosetta runs i386 PE code through WoW64 too, so AArchX’s i386 support is parity rather than something new. Standalone i386 Mach-O programs, which current macOS does not run at all, run through m32: see 32-bit Mac programs. The i386 side has its own differential gate of twenty thousand generated cases.

A few things it took, because they explain behaviour a Wine user may notice:

Windows Steam reaches its main window. On 2026-09-27 that took about 33 s, where Rosetta takes 11 to 12 s; with the translation store warm from an earlier run it took 34 to 35 s against 47 s from an empty store. In one measured startup translation took about a quarter of the CPU time, which is what the store saves on later runs. Translated code is also slower in Wine’s memory map than in the others, because every memory access there checks its range. On 2026-10-01 it was run on stock Wine Staging 11.16 with DXMT as well, which needed thread-specific data, signals and the cross-process memory calls Chromium’s sandbox makes to be translated correctly.

Counter-Strike 2 under Wine with D3DMetal now loads D3DMetal and reaches its own window (2026-09-26), after a dlopen of an @rpath path was resolved against the calling image and a sandbox check made from a Wine thread’s low stack stopped failing. It then stops on a fatal “Multiple entity classes have the same designer name” error that Rosetta never reaches. OCERZ_EXE_ENV exists to narrow that down inside cs2.exe alone.

Applications, native mode

Native mode runs a narrower set, and it is growing. Image Capture opens its main window and quits cleanly, Stickies opens a note, and the x86-64 Steam client comes up as described above.

How far native mode reaches can be counted before anything runs. On a macOS 26.7.1 machine with 2,913 x86-64 executables and libraries, system tools, system applications and 57 third-party applications with their own frameworks among them, every import of each was matched against the API databases (2026-10-05): 2,094 bind completely, and 1,344 import nothing that is only a stub. Of the 1,260 belonging to third-party applications, 1,117 bind, and every binary of 36 of the 57 applications does, OBS among them. Of 84 command-line tools run both ways, 71 print the same output and exit with the same status under native mode as their arm64 slices, sqlite3 and unzip among them. What keeps the rest from binding is mostly Swift: the Swift interface of Foundation and the Swift-only frameworks, SwiftUI and Combine above all, which native mode cannot cross, and then private frameworks the SDK describes nothing of. Among the stubs that bind but stop when called, the largest group is Objective-C’s exception entry points. Discord, the x86-64 slice of its Electron build, starts its browser process, network and utility services and two renderers, shows its splash screen and connects to Discord’s gateway (macOS 26.7.1, 2026-10-04); its main window had not finished loading when that run was stopped at 90 seconds, and its five processes held about 11 GB between them. Getting there took databases for eight more frameworks, records for private libSystem calls Chromium makes, a host stack of at least 512 KB for the threads it creates, the cipher-suite calls of Secure Transport, a comparator for sortSubviewsUsingFunction:context:, and region queries answered from AArchX’s own map, without which every renderer stopped at an int3. What stops the rest is listed in Native mode in depth.

Known limitations

These apply to cache mode, or to both modes. Native mode’s own are in Native mode in depth.

Reporting something that does not work

A failure that names a symbol or a library is the useful kind: it says exactly what is missing. Run with -v and OCERZ_DLPATH=1, keep the first ocerz: line, and see Troubleshooting for what each kind of report means.