Skip to content

AArchX 0.7

Choose a tag to compare

@mont127 mont127 released this 13 Oct 15:29
· 323 commits to main since this release

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 | sh

The installer:

  • downloads aarchx-macos-arm64.tar.gz from this release;
  • checks its SHA-256;
  • unpacks it into ~/.aarchx;
  • adds ~/.aarchx/bin to PATH in 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/aarchx

Open a new terminal window, then:

ocerz version                    # AArchX 0.7
ocerz /Applications/Some.app

Running 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:

  1. Unpack it anywhere, and keep runtime/ next to ocerz.
  2. 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/shrd and 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_api to the Steam client you have installed, so the game signs in and reaches the Steam API. Nothing of Valve's is bundled. make steam32 builds 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_up and bootstrap_check_in reach 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_IMM and others), listed in docs/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;
  • dlsym and dlopen follow rpaths and virtual libraries;
  • pthread_exit runs 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 new now wins, as it does under Rosetta.
  • ptest is 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, cmov siblings and 32-bit leas;
    • adc and sbb of an immediate become a single csinc;
    • 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.

  • 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/sbb from 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/fistp read 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 high8 and shiftmem again 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's TrackAppUsageEvent slot 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.pdf still describes 0.3-dev.