Repository navigation
Releases: mont127/AArchX
Release list
AArchX 0.9
AArchX 0.9 runs more 32-bit Mac games, delivers signals that used to be lost, and gets WineHQ's 32-bit Wine for macOS, the engine inside TransGaming's Cider ports, as far as running Windows programs.
32-bit Mac games
Free games that now play, each from its own Mac release:
- ScummVM 1.4.1, with Beneath a Steel Sky. Its Cocoa code uses Objective-C 1's own constant strings, which m32 now makes host strings.
- DOSBox 0.74, running DOS programs.
- Warzone 2100 2.3.9, with the X11 libraries Mac OS X 10.5 and 10.6 shipped (fontconfig, freetype), which the installer now fetches.
- Armagetron Advanced 0.2.8.3. Apple's GCC 4 laid out
std::ofstreamandstd::fstreamdifferently from the GCC 15 runtime AArchX uses, and the game crashed saving its settings; the runtime now gets Apple's layout for those classes. - Chocolate Doom 1.7.0 with Freedoom, at about 350 frames a second in its timedemos.
- Enigma 1.01, Pingus 0.7.6 and LinCity-NG 2.0.
- Cube 2: Sauerbraten (Justice Edition), its 32-bit build, with bots, at about 65 frames a second in a window.
On the way, for any 32-bit program:
- Signals. A handler for SIGINT, SIGTERM, SIGALRM and the like runs, so an SDL game asked to stop quits as on a Mac and saves its settings. A fault handler (SIGSEGV, SIGFPE and the rest) runs in i386's signal frame, as Mono's and Wine's need.
- C++ exceptions thrown through a
libgcc_sa game ships itself unwind. - Libraries missing where a program says fall back to their leaf names as dyld does.
- Full screen.
CGDisplayBestModeForParametersfinds the mode SDL 1.2 asks for; today's macOS no longer answers it exactly. - 4 KB pages, as on the Intel Macs, by every call that tells a program the page size.
- Threads run on the stack their attributes name, or one as large as they ask for.
posix_spawn,syscall()and IOKit's registry calls that name a plane or a class work.- Faster messages to Cocoa. Each thread keeps the i386 encodings of the selectors it sends: a message's lookup takes 35 ns instead of about 200, and 17 ns instead of 5.6 µs for one only a protocol declares.
32-bit Wine for macOS
WineHQ's i386 Wine 5.0 for macOS, what a Cider port carries inside, creates its prefix and runs cmd /c "echo hi && ver" in about four seconds, one second once the prefix exists. Windowed Windows programs are not tested yet.
Fixes
- Lost signals. A signal sent to a thread just as it blocked in
read(),poll()or a sleep was held until the call ended; Wine suspends threads that way. Such a thread is now interrupted, and a call whose handler asked forSA_RESTARTis made again, in cache mode and in native mode. - Callbacks. In native mode a callback from macOS into translated code costs about 105 ns instead of 536.
- Repeated faults. A program that takes the same fault again and again on purpose, as a garbage collector's write barrier does, is no longer stopped as a fault loop outside Wine.
Install
CURL_SSL_BACKEND=secure-transport curl -fsSL https://mont127.github.io/AArchX/install.sh | shCURL_SSL_BACKEND=secure-transport makes macOS's curl use Apple's TLS, which distrusts the eight eMudhra intermediate certificates whose keys became public in October 2026; the LibreSSL curl uses by default still accepts certificates they sign. The installer does the same for its own downloads since 9838c31, after this release was published.
or brew upgrade --cask aarchx, or AArchX.zip below. The installer and the cask update an existing install in place.
The installer and the cask now also fetch the 32-bit C++ and X11 libraries some games need, about 15 MB of MacPorts' universal packages, each pinned by SHA-256. Xcode's tools are no longer needed for them. Set AARCHX_NO_32BIT_RUNTIMES=1 to skip them; tools/fetch_guest32_cxx.sh and tools/fetch_guest32_x11.sh in the install fetch them later. AArchX.zip does not carry them.
Downloads
aarchx-macos-arm64.tar.gz:ocerz, its runtime files and AArchX.app. SHA-256:305d0cd1617daf301d58534f48e92c816ba64682cb2d4297c3e4372d8d7aa135AArchX.zip: the self-contained app. SHA-256:6ff39a4cfc78a5954fdb1db8072fd99532e74970c71fd594ca75977f948ed728
Both are built for Apple silicon against the macOS 26.5 SDK, with macOS 12 as the minimum. They have been tested on macOS 27 only. No file in either names a path on the machine that built it.
Testing
- Full gate on this release's code (MacBook Air M5, macOS 27):
- unit tests pass,
test_m32683 checks and its Carbon run 522; - guest 134/134 under both engines;
- differential 100/100;
- diff32 40,044/40,044 in each memory layout;
- dynamic 410/410;
- native 88/88;
- m32 79/79, plus the Steam bridge test, which skips without a Steam interface database.
- unit tests pass,
- The games above were each run as their fixes landed, muted and in a scratch home directory, with the 32-bit runtimes as the fetch scripts laid them out before this release. The new layout was tested with the C++ tests below, not with each game again.
- The prebuilt itself: installed with the one-line installer into a scratch home, which fetched the 32-bit runtimes; the 32-bit C++ tests (GCC's library, its exceptions, LLVM's libc++) pass with the installed
ocerz; a CoreFoundation program runs in cache and native mode; installing again keeps the runtimes; uninstalling leaves nothing behind.
Not yet
- Windowed programs under 32-bit Wine, and the Cider ports themselves.
- Everything listed under "Not yet" in 0.8 still applies.
AArchX 0.8
AArchX 0.8 runs 32-bit Mac programs, which macOS has not run since 10.15: games from Steam, games and apps from the 2000s, and the Carbon and Cocoa they were written against. 32-bit games also got faster, and threads in translated loops now receive their signals.
32-bit Mac games
- From Steam. Left 4 Dead 2 and Call of Duty: Modern Warfare 3 play, beside Batman: Arkham Asylum. This is Jfishin's work in MacShack, brought into AArchX. Steam games need a one-time setup from a source checkout (the Steam interface database and the 32-bit C++ runtimes); 32-bit Mac games from Steam walks through it.
- Free games, as they were released. AssaultCube 1.1, SuperTuxKart 0.6.2a, Neverball and Neverputt 1.5.3, Battle for Wesnoth 1.8.6, Teeworlds 0.5.2 and Nexuiz 2.5.2 play, with their sound. On the way: SDL 1.2's keyboard, cursors and audio, GLUT, audio units through the Component Manager (Mac OS X 10.1's descriptions included), the system OpenAL in place of a bundled one, images linked with
-flat_namespace, OpenGL vertex and index buffers, and calls through function pointers, which DarkPlaces makes for all of OpenGL. Games built with GCC's C++ library, such as Teeworlds, also need the 32-bit C++ runtimetools/fetch_guest32_cxx.shfetches from MacPorts, which no release carries. - Started from the
.app.ocerz Nexuiz.appworks even though Nexuiz's executable is a shell script that starts the game: such a script now runs on its interpreter's x86_64 slice under ocerz, so the program it starts is translated too.
32-bit apps
- Adium 1.4.5 opens its setup assistant, its contact list and Sparkle's update prompt. Objective-C 1 grew to carry it: an object's guest copy lives exactly as long as the object, nib outlets reach guest instance variables,
NSNotFound,NSError **andNSRangepointers cross between the 32- and 64-bit layouts in both directions, AppKit's tracking-rectangle and tool-tip tags fit in 32 bits, and frameworks load in dependency order. - Carbon. QuickDraw ports and drawing on CoreGraphics, AGL, the menu and control calls Apple silicon dropped (rebuilt from what it keeps), ATSUI on CoreText, and the Apple Event, OSA and other calls whose structures Carbon packs to 2 bytes. Kega Fusion 3.63 and VLC 1.1.12 open their windows.
- Older programs. UPX-packed programs, which unpack themselves, and programs linked for Mac OS X 10.4 start.
Native mode
Older Intel apps open their windows in native mode: Transmission, Sequel Pro, HandBrake and VLC 2.2.
Faster
- 32-bit code. Nexuiz's benchmark demo takes about 30% fewer cycles:
- a call through a function pointer to a system function stays in translated code, where it used to leave for the run loop every time;
- a 32-bit program runs in native mode by default, which spares two FPCR writes (about 17 ns) on every call into macOS;
- an
adcorsbbinto memory, i386's way of adding a 64-bit value, is translated instead of run one instruction at a time.
- Branches. A superblock whose branches the profile keeps as they are is translated again without its probes.
bench brtakes 5% fewer cycles, and xbench'sstrwent from 1.07 times Rosetta's time to 0.97.
Fixes
- Signals in tight loops. A thread spinning in a translated loop used to receive a signal only when it left the loop; it is now stopped and gets the signal between two steps. 0.7.3 listed this under "Not yet".
Changes in behaviour
- 32-bit programs run in native mode unless
-cacheorOCERZ_MODE=cachesays otherwise. They never used the x86 shared cache, so nothing else changes.
Install
curl -fsSL https://mont127.github.io/AArchX/install.sh | shor brew upgrade --cask aarchx, or AArchX.zip below. The installer and the cask update an existing install in place.
Downloads
aarchx-macos-arm64.tar.gz:ocerz, its runtime files and AArchX.app. SHA-256:961f1346ba6004c839ac56c0836fb3fadadbffb1827fb0611807420f0e85bcdcAArchX.zip: the self-contained app. SHA-256:d778a73b8710ef876e2dcfc2c1ee5ab63aa24056d8a5fe7f60eeadb78c8f6230
Both are built for Apple silicon against the macOS 26.5 SDK, with macOS 12 as the minimum. They have been tested on macOS 27 only. No file in either names a path on the machine that built it.
Testing
- Full gate on this release's code (MacBook Air M5, macOS 27):
- unit tests pass;
- guest 134/134 under both engines;
- differential 100/100;
- diff32 40,044/40,044 in each memory layout;
- dynamic 406/406;
- native 87/87;
- m32 79/79, plus the Steam bridge test, which skips without a Steam interface database.
- The games and apps above were each run as their fixes landed, muted and in a scratch home directory; Left 4 Dead 2 and Modern Warfare 3 were tested by Jfishin.
- The prebuilt itself:
- with its own runtime files: native 87/87, and m32 76/76 plus four expected skips (three need the 32-bit C++ runtimes, one the Steam interface database);
- Nexuiz's benchmark demo played all 3,104 frames from the extracted archive, and Teeworlds opened its window once given the 32-bit C++ runtime;
- installed with the one-liner into an empty home directory,
ocerz0.8 ran a CoreFoundation program in cache and native mode, AArchX.app landed in~/Applicationswith a valid signature, and--uninstallremoved both; AArchX.zippassescodesign --verify --deep --strict.
Not yet
- CrossOver's alternate loader under
ocerz. Fixing it would give a ticked bottle's programs their launcher's Dock icon andsteam://links back. - Firefox 3.6 draws its window but the window stays off screen, and the Carbon controls Apple silicon dropped entirely (sliders, tabs, Navigation Services) are not provided yet.
- A 32-bit game that needs GCC's C++ library, run without
tools/fetch_guest32_cxx.sh's runtime, crashes instead of saying what it is missing. - Everything listed under "Not yet" in 0.7 still applies.
Shoutout to @Jfishin
AArchX 0.7.3
Native mode can now fork from inside callbacks, so Qt apps such as ClipGrab run in native mode, and translation is faster.
Native mode: fork inside callbacks
Until now, native mode refused a fork() made inside a callback, meaning code that native macOS code calls back into, whenever translated code sat further down the stack. The reasoning was that the child process could not return into translations it had not inherited.
It can. The child forgets the JIT's bookkeeping but keeps the parent's translated code mapped, and it translates anything new into a fresh area of its own. The code those frames return into is therefore still there, unchanged.
Qt starts every QProcess this way, from an AppKit callback. ClipGrab in native mode used to stop with "_fork was called inside a callback"; it now finds and runs yt-dlp, as it does in cache mode (2d84323). Together with 0.7.2's two Qt fixes, ClipGrab now runs in native mode.
- Test.
sys_fork_callbackforks twice inside aqsortcomparator: one child exits, and the other returns all the way out throughqsortandmain. Both engines must now print exactly what the arm64 build prints. - Switch.
OCERZ_FORK_CALLBACK_REFUSE=1brings back the old refusal, and the test checks that it still works.
Faster translation
From the Studio session's profiling of how long the JIT takes to translate code:
- NZCV producers. A block's NZCV producers, the instructions whose flags later instructions read directly, are now worked out once per block, before its liveness pass. Before, each instruction asked up to nine of its successors, and every answer rescanned the window (213a741).
- Chain installs. Linking a new block to its neighbours makes all the patched branch words visible with one barrier pair, instead of one instruction-cache flush per word. On Apple silicon the barriers, not the cache lines, are what each flush costs (b81bcd1).
On perl and ruby starting in cache mode, with the translation cache off, the two together take translation time down by another 9 to 10%. They change how code is translated, not what it does: the guest, differential and dynamic suites also ran with a check that compares the new producer table against the old per-instruction search, and found no difference.
There is also a new self-modifying-code test, for an instruction rewritten past its first eight bytes (87b8217), and OCERZ_JITMEASURE now has its row in the reference (839f77d).
Install
curl -fsSL https://mont127.github.io/AArchX/install.sh | shor brew upgrade --cask aarchx, or AArchX.zip below. The installer and the cask update an existing install in place.
Downloads
aarchx-macos-arm64.tar.gz:ocerz, its runtime files and AArchX.app. SHA-256:884d74428559f288b2d7f5a8dcc80f60de7ff4a4309ed9774e033bbda921474cAArchX.zip: the self-contained app. SHA-256:80f8bec88dbc9e88aeabf61a0c206797a019a50247a3483f24eeb454a80af7fb
Both are built for Apple silicon against the macOS 26.5 SDK, with macOS 12 as the minimum. They have been tested on macOS 27 only. No file in either names a path on the machine that built it.
Testing
- Full gate on this release's code (MacBook Air M5, macOS 27):
- unit tests pass;
- guest 134/134 under both engines;
- differential 100/100;
- diff32 40,044/40,044;
- dynamic 404/404;
- native 87/87, including the reworked
sys_fork_callback; - the guest library, native C++, framework and format suites pass, and native Swift skips;
- m32 40/40, plus
high8andshiftmemagain with their speed switches off.
- ClipGrab in native mode, under that build, ran its
yt-dlpcheck and stayed up, with the same output as cache mode. - The prebuilt itself:
- with its own runtime files: native 87/87, and m32 37/37 plus the three expected skips;
- installed with the one-liner into an empty home directory,
ocerz0.7.3 ran a CoreFoundation program in cache and native mode, AArchX.app landed in~/Applicationswith a valid signature, and--uninstallremoved both; AArchX.zippassescodesign --verify --deep --strict.
Not yet
- Signals in tight loops. A thread spinning inside one self-looping translated block receives signals only when it leaves the block. Under Wine that can delay
SuspendThread, which Mono's garbage collector uses. - CrossOver's alternate loader under
ocerz. Fixing it would give a ticked bottle's programs their launcher's Dock icon andsteam://links back. - Everything listed under "Not yet" in 0.7 still applies.
AArchX 0.7.2
A wrong-result fix in the JIT that every release since 0.4 had, CrossOver programs fully off Rosetta, and two native-mode fixes for Qt apps.
Wrong floating-point results in tight loops
A loop that carried a double (or a float) in a register and ended in dec/inc followed by jnz/jz could give wrong results under the JIT. For example:
1: addsd -8(%rax,%rcx,8), %xmm0
mulsd %xmm1, %xmm0
dec %rcx
jne 1b- Cause. The JIT keeps such a value in an Arm vector register round the loop. The emitter for that particular loop ending neither wrote the register back nor restored it, so the loop's next trip started from a stale copy. The interpreter and plain C get the right answer.
- Who it affected. Every release from 0.4 to 0.7.1. The code came in with 782cb38 on 2026-08-25 and first shipped in 0.4.
- Fix. These loops now branch straight back to the loop body with the value in place, and every other path writes it back first (6c07ad5, by the Studio session). The same change makes such loops faster in the Wine layout. 7-Zip's LZ copy loop went from 0.69 to 0.29 ns per byte, level with the normal layout.
- Test.
tests/dynamic/incdec_loop.ccovers the shapes in both memory layouts: doubles and floats, multi-wordadc, 32-bit counters, a mid-loop exit, and a timer signal.
CrossOver programs fully under AArchX
In 0.7.1, ticking a bottle in AArchX.app ran Wine under AArchX, but a program started from its CrossOver launcher still had its window on Rosetta.
-
Why. CrossOver's launcher (the app in
~/Applications/CrossOver) is also Wine's alternate loader, so the program's window processes run inside that Intel launcher. -
What ticking does now. It also rewires the bottle's launchers. Each keeps its own executable beside it as
<name>.cx-original. A short script takes its place and runs the command CrossOver wrote for the program, without the alternate-loader option the launcher would add. -
Result. Windows Steam in a CrossOver 26.3 bottle, started from its CrossOver launcher, runs with every one of its processes, the window included, under AArchX.
-
Undoing it. Unticking renames every original back, byte for byte, and nothing inside CrossOver.app is changed.
-
Trade-offs while a bottle is ticked:
- its programs show Wine's own Dock icon instead of the launcher's;
steam://links from a browser do not reach the program through its launcher.
Running CrossOver's alternate loader itself under
ocerzwould remove both, and is not done yet.
Native mode and Qt
ClipGrab and other Qt apps got further in native mode (-native):
- Unused libraries. A library the Mac has, but no API database describes, no longer fails a native
dlopenwhen nothing imports from it. Qt's Cocoa plugin links Apple's old AGL framework and uses none of it, so every Qt app stopped with "Could not load the Qt platform plugin cocoa" (c2f6433). pthread_testcancelnow returns instead of stopping the thread.pthread_cancelis still refused, so a cancellation can never be pending (e19f176).
ClipGrab now stops later, when it starts yt-dlp with fork() from inside an AppKit callback, which native mode does not support yet. It runs normally in cache mode, the default.
Install
curl -fsSL https://mont127.github.io/AArchX/install.sh | shor brew upgrade --cask aarchx, or AArchX.zip below. The installer and the cask update an existing install in place.
Downloads
aarchx-macos-arm64.tar.gz:ocerz, its runtime files and AArchX.app, as in 0.7.1. SHA-256:30f479154e0a4a275718f860df34b278240e0296fed8469238bed216e2795bb3AArchX.zip: the self-contained app. SHA-256:65c214f5c9cdd901919ae5d587a7589b035438f529ab7abb410cea21fe5466ae
Both are built for Apple silicon against the macOS 26.5 SDK, with macOS 12 as the minimum. They have been tested on macOS 27 only. No file in either names a path on the machine that built it.
Testing
- Full gate on this release's code (MacBook Air M5, macOS 27):
- unit tests pass;
- guest 134/134 under both engines;
- differential 100/100;
- diff32 40,044/40,044;
- dynamic 404/404, including the new
incdec_loopcases in both memory layouts; - native 87/87;
- the guest library, native C++, framework and format suites pass, and native Swift skips;
- m32 40/40, plus
high8andshiftmemagain with their speed switches off.
- The floating-point fix was also gated on its own on the Studio's Mac: guest 136/136 both ways, differential 100, diff32 40,044, dynamic 404/404, native 87/87.
- The prebuilt itself:
- with its own runtime files: native 87/87, and m32 37/37 plus the three expected skips;
- installed with the one-liner into an empty home directory,
ocerz0.7.2 ran a CoreFoundation program in cache and native mode, AArchX.app landed in~/Applicationswith a valid signature, and--uninstallremoved both; AArchX.zippassescodesign --verify --deep --strict.
- CrossOver: Windows Steam in a CrossOver 26.3 bottle, started from its CrossOver launcher after ticking the bottle, ran with every process under
ocerz. In a test home directory, unticking a bottle restored its launcher byte for byte, and ticking it switched the launcher again.
Not yet
- Signals in tight loops. A thread spinning inside one self-looping translated block receives signals only when it leaves the block. Under Wine that can delay
SuspendThread, which Mono's garbage collector uses. This was found during this release and is being worked on. - Native mode
fork()from callbacks (ClipGrab'syt-dlp). - CrossOver's alternate loader under
ocerz(the Dock icon andsteam://trade-offs above). - Everything listed under "Not yet" in 0.7 still applies.
AArchX 0.7.1
AArchX.app: a library for running Intel Mac and Windows programs without Terminal, and CrossOver bottles that run under AArchX instead of Rosetta.
AArchX.app
AArchX.app is a separate wrapper around ocerz; the terminal version is unchanged and works the same with or without it.
The library. The app keeps a library of your programs, shown as tiles with each program's own icon and name and a one-word caption: Intel, 32-bit, Windows, or Running.
To add programs:
- drop them on the window: one program dropped is added and run, several are only added;
- click +;
- use Open With → AArchX in Finder.
To use a tile:
- double-click it to run the program;
- right-click it to run it in native mode, stop it, open its last log, show it in Finder or remove it.
Activity lists every run, with how it ended and when it started. Logs go to ~/Library/Logs/AArchX.
Windows programs. Windows .exe and .msi files run through Wine under ocerz:
- The bottom bar's menu picks the Wine build from the ones the app finds: WineHQ's Stable, Staging and Devel (including MacNdCheese's copies), Whisky's Wine, the Game Porting Toolkit, or a
winein Homebrew'sbin. - Programs run in a prefix of the app's own, so no other Wine setup is touched.
- For games, MacNdCheese is still the full environment.
Mode. Native mode is a checkbox. A 32-bit program goes to native mode by itself, since only m32 runs it, as does everything when Rosetta's x86-64 cache is missing. An Apple silicon app is turned away, since it runs by itself.
Icon. It has its own icon: AX.
CrossOver bottles under AArchX
With CrossOver installed, CrossOver Bottles… in the app's bottom bar lists its bottles, each with a box. A ticked bottle runs its Windows programs under AArchX instead of Rosetta, started from CrossOver's own window as usual.
- How it switches. The box sets the bottle's
[Wine] "BinPath", CrossOver's documented way to use another Wine. That puts two small scripts ahead of CrossOver's ownbin, and they run CrossOver'swineloaderandwineserverunderocerz. - What stays the same. Nothing inside CrossOver.app changes, so its signature and its updates are unaffected.
- Switching back. Unticking the box removes the line. If
ocerzis ever removed, the bottle quietly goes back to Rosetta. - What has been tried. A CrossOver 26.3 bottle created and run this way starts
cmd.exeunder AArchX. Games in CrossOver have not been tried yet.
Getting there took three fixes in ocerz itself, all generic:
@rpathdependencies of a dlopened image are found through the rpaths of the image that calleddlopenand then the main executable's, as dyld's load chain does. Cache mode looked only at the image's own, so CrossOver'scxcompatdb.solost its gnutls and every bottle crashed (99eeba0).- A leaf name passed to
dlopenis looked for in the same rpaths, afterDYLD_LIBRARY_PATH. Before this, CrossOver'swin32u.sofound no FreeType (99eeba0). - Wine's loader is recognised by its
WINE_RESERVEsegment, not only by being namedwine. CrossOver starts each process through a hard link named after the program, and itscmd.exedied with the wrong memory layout (5f71b1d).
tests/dynamic/dlopen_chain_rpath and tests/dynamic/wine_segment pin the fixes; Rosetta passes the first, and 0.7 failed both.
There are three ways to get the app:
-
The one-line installer, which now also puts AArchX.app in
~/Applications:curl -fsSL https://mont127.github.io/AArchX/install.sh | shRunning it again over 0.7 updates everything in place.
-
Homebrew, which puts the app in
/Applications:brew install --cask mont127/aarchx/aarchx
If you already have 0.7 from Homebrew, run
brew upgrade --cask aarchxinstead. -
AArchX.zip, below: the app withocerzand its runtime inside, for anyone who would rather not use Terminal at all. Unzip it and move AArchX.app to Applications. It is not notarized, so macOS refuses to open it the first time; click Open Anyway in System Settings → Privacy & Security, once.
make app builds it from app/, with no Xcode project; the icon is drawn by app/icon.m at build time. Getting started describes it in full.
Also since 0.7
-
No build-machine paths in the download. The prebuilt's
ocerzis stripped of its debug map, and the guest C++ runtime is built with-ffile-prefix-mapand without CMake's build-directory rpath, so no binary in the archive names a directory on the machine that built it (68f384d). The 0.7 archive was rebuilt the same way, so its checksum changed. -
Homebrew tap. mont127/homebrew-aarchx holds the cask.
-
Docs site. mont127.github.io/AArchX builds again (ea3b2fc). A
{{innative-mode.mdhad stopped every Pages build since 2026-10-03, so the site had been showing that day's pages.
Downloads
aarchx-macos-arm64.tar.gzis what the installer and the cask use. It has the same contents as 0.7's (ocerz,runtime/apis,runtime/apis32,runtime/guest, LICENSE, VERSION), plus a thin AArchX.app that findsocerzbeside it, in~/.aarchxor in Homebrew'sbin.
SHA-256:3f3bd4e6fa97844eec33ca2c9775b42f2d7e85705416bc25539b0f4602962458AArchX.zipis the self-contained app.
SHA-256:e341fa265c9c317669ee5eb858e373169beb252b087467d8aab802643482c286
Both are built for Apple silicon against the macOS 26.5 SDK, with macOS 12 as the minimum. They have been tested on macOS 27 only.
Testing
- Full gate on this release's code (MacBook Air M5, macOS 27):
- unit tests pass;
- guest 134/134 under both engines;
- differential 100/100;
- diff32 40,044/40,044;
- dynamic 398/398, including the new
dlopen_chain_rpathandwine_segment; - native 87/87;
- the guest library, native C++, framework and format suites pass, and native Swift skips;
- m32 40/40, plus
high8andshiftmemagain with their speed switches off.
- The prebuilt itself:
- With its own runtime files: native 87/87, and m32 37/37 plus the three expected skips.
- Installed with the one-liner into an empty home directory:
ocerz0.7.1 ran a CoreFoundation program in cache and native mode, AArchX.app landed in~/Applicationswith a valid signature, and--uninstallremoved both. AArchX.zippassescodesign --verify --deep --strict, and its ownocerzruns.- No file in either download names a path on the machine that built it.
- The app:
- It ran an Intel command-line program in cache mode.
- It switched a 32-bit program to native mode by itself.
- It turned away an Apple silicon app.
- It ran Wine's
hostname.exethrough Wine Staging 11.18 underocerz.
- CrossOver: a CrossOver 26.3 bottle switched with the box ran
cmd.exeunderocerz.
Not yet
- No notarization. AArchX has no Developer ID, so
AArchX.zipneeds the one-time Open Anyway. The installer and the cask avoid that prompt. - No arguments. The app runs programs without arguments. For anything that needs them, use
ocerzin Terminal. - Windows programs in the app are basic. There is one shared prefix, and no per-program Wine settings, DXVK or DXMT. MacNdCheese covers games.
- The CrossOver switch lives in AArchX.app, not in CrossOver. A picker inside CrossOver's own window would need CodeWeavers.
- Everything listed under "Not yet" in 0.7 still applies.
AArchX 0.7
32-bit Mac programs, faster launches, and a faster JIT under Wine. This is also the first release with a prebuilt download and a one-line installer, so you no longer need Xcode or the Command Line Tools to run AArchX.
Install
curl -fsSL https://mont127.github.io/AArchX/install.sh | shThe installer:
- downloads
aarchx-macos-arm64.tar.gzfrom this release; - checks its SHA-256;
- unpacks it into
~/.aarchx; - adds
~/.aarchx/bintoPATHin your shell profile.
It asks for no password. Because curl does not quarantine what it downloads, Gatekeeper does not stop the unsigned binary.
With Homebrew instead:
brew install --cask mont127/aarchx/aarchxOpen a new terminal window, then:
ocerz version # AArchX 0.7
ocerz /Applications/Some.appRunning the script again updates the install, and curl -fsSL https://mont127.github.io/AArchX/install.sh | sh -s -- --uninstall removes it. Getting started lists the options.
The archive holds:
ocerz, built for Apple silicon;- the API databases that native mode and m32 read (
runtime/apis,runtime/apis32), generated from the macOS 26.5 SDK; - the guest C++ runtime (
runtime/guest); - the LICENSE.
To use it without the installer:
- Unpack it anywhere, and keep
runtime/next toocerz. - If you downloaded it with a browser, remove the quarantine flag first:
xattr -dr com.apple.quarantine aarchx.
Before you start:
- Rosetta. Cache mode needs it installed, because Rosetta's package is what ships Apple's x86-64 shared cache. AArchX never runs Rosetta's translator. Native mode (
-native) needs nothing from Rosetta. - 32-bit Steam games. The prebuilt has no Steam bridge database (
make steam32) and no 32-bit GNU C++ runtime (tools/fetch_guest32_cxx.sh). Both are generated or fetched on your own Mac, so these games still need a source build: see 32-bit Mac games from Steam. - macOS version. The prebuilt has been tested on macOS 27 only. AArchX is developed on macOS 26 and 27.
SHA-256 of aarchx-macos-arm64.tar.gz: ee703276f7ecd7cc8f27b42ee120eaed6f3c97b62e3508dfa080c0c4a851bdd6
32-bit Mac programs (m32)
macOS stopped running i386 programs in Catalina, and Rosetta never ran them. m32 runs them under AArchX in native mode, against the Mac's own arm64 frameworks. It comes from Jfishin's MacShack (#8, #9, #11, #12; a74ac4c).
- First game. Batman: Arkham Asylum plays: title screen, menus and gameplay, with sound. Its New Game opening runs at about 61 fps on an M4 Max. The 32-bit JIT work in #9 took it there from 31: shifts by cl, MMX,
shld/shrdand the high-byte registers are translated, and small libc, CGL and OpenGL exports are called directly. - The Steam bridge (#11). It connects a 32-bit game's own
libsteam_apito the Steam client you have installed, so the game signs in and reaches the Steam API. Nothing of Valve's is bundled.make steam32builds the interface database the bridge reads, from the Steamworks headers in Valve's Proton repository. Step by step: 32-bit Mac games from Steam. bootstrap_look_upandbootstrap_check_inreach the host from i386 code (299319c).- Off switches. Each piece of the 32-bit speed work has one (
OCERZ_NO_HIGH8_ARITH,OCERZ_NO_SHIFT_MEM_IMMand others), listed indocs/m32.md.
Jfishin's native-mode fixes from MacShack for Intel games are also in (2e734d5):
- a non-PIE main executable is slid into the arena;
- lazy binds wait until the main executable's dependencies load;
dlsymanddlopenfollow rpaths and virtual libraries;pthread_exitruns guest cleanup handlers;- several missing library records were added.
Launch
Cache-mode launches spent most of their time on dyld semantics AArchX had wrong. Measured on the same MacBook Air, on either side of the changes:
| program | before | after | Rosetta |
|---|---|---|---|
| empty CoreFoundation program | 133 ms | 42 ms | 9 ms |
ffmpeg -version |
0.71 s | 0.11 s | |
deno --version |
3.4 s | 0.25 s | 0.19 s |
The fixes:
- Delayed-init links (b1b3cc2). AArchX loaded and initialized every delayed link: 505 images for a CoreFoundation program, against Rosetta's 346. They now stay out of the launch, as dyld keeps them, and the image list matches Rosetta's.
- The eager set's pointer scan (ebed191) skips cache images whose links all resolved.
- Weak definitions (93d32e1, bb1951a). Lookups ask the main executable first, the weak-bind stream is read as one, and the name filter is kept beside the translation store. A program's own
operator newnow wins, as it does under Rosetta. ptestis translated and fused with the branch that reads it (58932ea).
Speed
Measured on a MacBook Air M5 with macOS 27, in seconds unless noted. "Before" and "after" were measured on either side of each change, when it landed.
| workload | before | after | Rosetta |
|---|---|---|---|
| SQLite, one thread | 0.578 | 0.417 | 0.308 |
| zlib | 1.17 | 0.90 | 0.83 |
| SHA-256 | 1.08 | 0.87 | 0.74 |
| memcpy, Wine layout | 1.274 | 0.429 | 0.563 |
| leaf-call loop (cycles) | 1.40 G | 0.73 G | 1.31 G |
| 7-Zip 25.01 decompress under Wine (KiB/s, higher is better) | 58,600 | 91,100 | 123,100 |
-
Ordered memory. Most programs ran in ordered memory for no reason: libsystem_trace maps a private buffer at startup, and AArchX treated every such mapping as shared. Private mappings now leave memory plain (e5d9309).
-
Lazy rsp (1ccb8c4). A spliced call leaves the stack pointer's register alone and gives each push and pop an offset, so the leaf-call loop is no longer one long chain of stack-pointer updates.
-
Flags (85cc82e, a1caa8d, 325847e, 2c9819b, e079d3c). Several changes keep a compare's flags in NZCV and stop rebuilding them:
- a compare's flags reach consumers up to 8 instructions on, past loads,
cmovsiblings and 32-bitleas; adcandsbbof an immediate become a singlecsinc;- the flags live at a loop's own start are computed as a fixed point, instead of assuming every flag is live.
Together they took 7-Zip's decompression from 58.6k to 91.1k KiB/s.
- a compare's flags reach consumers up to 8 instructions on, past loads,
-
The Wine layout (c2b93c4, 141f452, deba545, f7cfbd0, 54e17f0 and others):
- a 12 GB limit lives in x29, and each guard lays out the side its address was on;
- rsp's register holds the host address the stack pointer points at;
- a base register is guarded once per block;
- hoisted indexed operands are a single register-offset access;
- the in-place string and memory routines run in the Wine layout too.
-
Smaller wins:
- a block that stops at the length cap chains to the next one;
rdtsc, the fences, 16-bit rotates,sidt,adc/sbbfrom memory and shifts by cl with live flags are translated;- pushes and pops go two at a time, and a run of them moves rsp once;
- x87 runs on a known TOP keep their registers in lanes;
- a jump through a rip-relative slot is chained to where the slot points;
- translation-cache records are checked with CRC32C.
Fixes
- 32-bit shifts by zero. A 32-bit shift or rotate by a count of 0 now clears the register's upper half, as hardware and Rosetta do (73bf93c).
- Self-modifying code (35ce79e, 89b4e8e, 05996e5):
- a write to a spliced callee's code retires the callers it was spliced into;
- chains through veneers are cut when their target retires;
- retiring a block also cuts the chained calls into it.
- JIT pages after a fault. A fault recovered while translating left the thread's JIT pages executable-only, and the next write to them raised SIGBUS. They are now writable again (13ea6c6).
fist/fistpread the rounding control as they convert, and set C1 when they round away from zero (a05279e, 744fb6f).- Fixed lanes. A fixed-lane restore waited on the wrong register (657bfec).
Testing
- Full gate on this release's code (MacBook Air M5, macOS 27):
- unit tests pass;
- guest 134/134 under both engines;
- differential 100/100;
- diff32 40,044/40,044;
- dynamic 394/394;
- native 87/87;
- the guest library, native C++, framework and format suites pass, and native Swift skips (no guest Swift runtime);
- m32 40/40, plus
high8andshiftmemagain with their speed switches off.
- The prebuilt itself (macOS 26.5 SDK, minimum macOS 12):
- with its own runtime files, native 87/87 and m32 37/37. The three m32 tests that need the 32-bit C++ runtime or the Steam database skip, as they will for anyone using the prebuilt.
- installed with the one-liner into an empty home directory, it ran a CoreFoundation program in cache and native mode.
Not yet
- 7-Zip under Wine decompresses at 0.74x Rosetta's speed and compresses at 0.70x. What remains is the Wine layout's memory guards on indexed operands.
- Steam for Windows (through MacNdCheese) opens its window in about 44 s, against Rosetta's 34 s.
- Deep recursion (fib) takes 1.44x Rosetta's cycles.
- m32 is young.
- It has been tested mostly with Batman.
SteamUser020'sTrackAppUsageEventslot is read with SDK 1.45's layout, which is wrong for games built with SDK 1.47 or 1.48a.
docs/AArchX-Deep-Dive.pdfstill describes 0.3-dev.
AArchX 0.6
32-bit Windows games, the x87 FPU and the 32-bit JIT. This release closes issue #6.
x87
- New interpreter (f238f09). The x87 unit was rewritten against Rosetta. The old one computed
fadd %st(i),%stas st0+st0, wrote the wrong register for the DC forms, reported everyfcomias equal, and madefcmovdo nothing. FXAM, FYL2XP1, FRSTOR and FNSAVE were mis-decoded, and FICOM read its integer as a float. FISTTP, FBLD, FBSTP and FXTRACT did not decode at all. - Exact loads and stores. Registers compute in doubles, but each keeps an exact 80-bit image of what was loaded into it. Delphi's
fild/fistp qwordmemory copy,long doublecopies, denormals and NaN payloads pass through loads, stores and context switches bit for bit. FXSAVE and XSAVE now store real 80-bit registers. - The JIT translates x87 (a6f05c4, a7a4501, 2f4f72b). Covered: loads, stores, arithmetic in every form, square root, compares,
fcmov,fxchand integer conversions, in 64-bit and 32-bit blocks, plus thefild/fistpqword copy as one exact 8-byte move. The results match the interpreter bit for bit, exception flags included. NaNs and other edge cases go to the interpreter once per run of x87 instructions.
x87 speed against Rosetta (Mac Studio, macOS 26.7.1):
| kernel | Rosetta | AArchX 0.6 | AArchX 0.5 |
|---|---|---|---|
| arithmetic (Horner, integrator) | 0.454 s | 0.064 s | 0.907 s |
| compare and fcmov | 0.161 s | 0.036 s | 0.355 s |
| float to int | 0.045 s | 0.010 s | 0.097 s |
| qword copy | 0.026 s | 0.009 s | 0.090 s |
On xbench's x87 kernel AArchX takes 0.16 s, against 1.15 s for 0.5 and 0.73 s for Rosetta.
32-bit JIT
The following now run inline in 32-bit blocks instead of through the interpreter:
fs:/gs:operands, which carry SEH frames and TEB/TLS access;- memory push/pop;
xchg,xadd,cmpxchgandcmpxchg8b;- memory read-modify-write;
bt/bts/btr/btc.
The cmp/test+jcc and pair fusions are back on, and 32-bit stack operations are fast in the Wine layout.
In diff32's timed loops, an SEH frame went from 75 to 8.9 ns, a TEB read from 40 to 8.7 ns, xadd from 25 to 3.9 ns and bt from 49 to 0.8 ns.
Three flag bugs that also affected 64-bit code are fixed:
- the carry after
add; inc; - a hang in blocks with more than 32 out-of-line arms;
- stale flags in a
cmovwith an address-size prefix.
Testing
- tests/dynamic/x87_*.c: 1,759 lines of Rosetta's output, under both engines and in both layouts.
- The 32-bit differential (diff32) translates for real again. It had compared mostly the interpreter with itself since September. It now runs 40,044 sequences, 20,000 of them x87, in both memory layouts, and passed 200,000-case soaks with two seeds.
- Full gate on a Mac Studio with macOS 26.7.1:
- guest 136/136 under both engines;
- differential 100/100;
- dynamic 231 passed, 5 failed: the 5 failures specific to that host;
- native 87/87;
- all native suites pass.
Not yet
- x87 transcendentals, BCD, 80-bit memory operands and rounding other than to nearest still run in the interpreter.
- Unmasked x87 exceptions do not trap.
- String ops in 32-bit blocks are not translated yet.
- OMSI 2, the reference title of #6, has not been run.
- The comparison against Rosetta with rosettax87_jit could not run on the test machine: its loader fails with "PT_THUPDATE failed".
docs/AArchX-Deep-Dive.pdfstill describes 0.3-dev.
AArchX 0.5
Native mode now runs Wine. MacNdCheese's wine-unified 11.16 runs under ocerz -native, which is the aarchx translator in MacNdCheese. Under it:
- every Wine service starts with msync;
- Notepad shows its window;
- Steam logs on and shows its client;
- R.E.P.O. reaches its main menu on DXMT and on D3DMetal (Game Porting Toolkit 4);
- Counter-Strike 2 runs on D3DMetal, through its menu and into an offline map with bots.
Changes since 0.4
- Wine's memory below 12 GB (09a53b3):
- Readable host memory below 12 GB is aliased into the low shadow and pinned. The free host space left there is reserved, so guest and host see the same pages.
mach_vm_mapclaims anonymous fixed maps above 12 GB, searches from 12 GB up for anywhere maps, and drops hints above user space.__blockvariables on Wine stacks forward to their host alias.
- The calls Wine, Steam and R.E.P.O. make (1d95319):
__ulock_waitand__ulock_wait2are specials. The unstick monitor interrupts them once a guest signal is pending, so msync no longer loses the SIGUSR1 APC that completes a service's first RPC.thread_get_stateanswers the x86 debug-register flavors.AudioUnitSetPropertyconverts render callbacks.- iovec and msghdr calls copy their vectors.
- The bridge gains several records and better diagnostics.
- Real-time thread policies go to the kernel in host ticks (92dce0f). CoreAudio's I/O thread now starts, and Wine's audio driver, and FMOD through it, play sound.
- D3DMetal (d309b1c), and Apple's typed
operator newanddeletein the guest C++ runtime (938365a). - Carbon's
IsWindowVisible(c0b60c3). Steam's webhelper no longer dies when its window is dragged. - The null-page pass-through applies to native mode only (5a66c6c).
- tests/win: cross-process waits, shared memory and first RPCs (aab985f).
- sdkgen includes the two headers the macOS 27 SDK added, where they exist (2ab590e).
Testing
The full gate ran at d309b1c on a Mac Studio with macOS 26.7.1. Results:
- guest: 135/135 under the interpreter and under the JIT;
- x86-64 differential: 99/99;
- i386 differential: 20,033/20,033;
- native: 87/87;
- dynamic: 211 of 216 passed;
- the unit tests and the guest-library, native C++, native framework, native format and native Swift suites pass.
The five dynamic failures are specific to that host and predate these changes: ddlopen_image_list ×2, ddlopen_cryptex ×2 and dyldslots. The 0.5 commit itself was checked with the unit tests and the native framework suite.
Known limits
docs/AArchX-Deep-Dive.pdfstill describes commit 9169680 (0.3-dev). An update will follow.- Under
-native, libgnutls does not load, because libffi has no API database. - CS2 uses about 6-7 GB and about 380% CPU.
- MacNdCheese's bundled AArchX has none of these fixes until it is rebuilt from this tag.