Repository navigation
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.