Skip to content

Releases: mont127/AArchX

AArchX 0.9

Choose a tag to compare

@mont127 mont127 released this 21 Oct 17:11

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::ofstream and std::fstream differently 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_s a game ships itself unwind.
  • Libraries missing where a program says fall back to their leaf names as dyld does.
  • Full screen. CGDisplayBestModeForParameters finds 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 for SA_RESTART is 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 | sh

CURL_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: 305d0cd1617daf301d58534f48e92c816ba64682cb2d4297c3e4372d8d7aa135
  • AArchX.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_m32 683 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.
  • 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

Choose a tag to compare

@mont127 mont127 released this 18 Oct 17:56

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++ runtime tools/fetch_guest32_cxx.sh fetches from MacPorts, which no release carries.
  • Started from the .app. ocerz Nexuiz.app works 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 ** and NSRange pointers 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 adc or sbb into 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 br takes 5% fewer cycles, and xbench's str went 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 -cache or OCERZ_MODE=cache says otherwise. They never used the x86 shared cache, so nothing else changes.

Install

curl -fsSL https://mont127.github.io/AArchX/install.sh | sh

or 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: 961f1346ba6004c839ac56c0836fb3fadadbffb1827fb0611807420f0e85bcdc
  • AArchX.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, ocerz 0.8 ran a CoreFoundation program in cache and native mode, AArchX.app landed in ~/Applications with a valid signature, and --uninstall removed both;
    • AArchX.zip passes codesign --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 and steam:// 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

Choose a tag to compare

@mont127 mont127 released this 14 Oct 09:32

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_callback forks twice inside a qsort comparator: one child exits, and the other returns all the way out through qsort and main. Both engines must now print exactly what the arm64 build prints.
  • Switch. OCERZ_FORK_CALLBACK_REFUSE=1 brings 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 | sh

or 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: 884d74428559f288b2d7f5a8dcc80f60de7ff4a4309ed9774e033bbda921474c
  • AArchX.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 high8 and shiftmem again with their speed switches off.
  • ClipGrab in native mode, under that build, ran its yt-dlp check 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, ocerz 0.7.3 ran a CoreFoundation program in cache and native mode, AArchX.app landed in ~/Applications with a valid signature, and --uninstall removed both;
    • AArchX.zip passes codesign --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 and steam:// links back.
  • Everything listed under "Not yet" in 0.7 still applies.

AArchX 0.7.2

Choose a tag to compare

@mont127 mont127 released this 13 Oct 18:28

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.c covers the shapes in both memory layouts: doubles and floats, multi-word adc, 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 ocerz would 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 dlopen when 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_testcancel now returns instead of stopping the thread. pthread_cancel is 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 | sh

or 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: 30f479154e0a4a275718f860df34b278240e0296fed8469238bed216e2795bb3
  • AArchX.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_loop cases 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 high8 and shiftmem again 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, ocerz 0.7.2 ran a CoreFoundation program in cache and native mode, AArchX.app landed in ~/Applications with a valid signature, and --uninstall removed both;
    • AArchX.zip passes codesign --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's yt-dlp).
  • CrossOver's alternate loader under ocerz (the Dock icon and steam:// trade-offs above).
  • Everything listed under "Not yet" in 0.7 still applies.

AArchX 0.7.1

Choose a tag to compare

@mont127 mont127 released this 13 Oct 16:54

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 wine in Homebrew's bin.
  • 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 own bin, and they run CrossOver's wineloader and wineserver under ocerz.
  • 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 ocerz is 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.exe under AArchX. Games in CrossOver have not been tried yet.

Getting there took three fixes in ocerz itself, all generic:

  • @rpath dependencies of a dlopened image are found through the rpaths of the image that called dlopen and then the main executable's, as dyld's load chain does. Cache mode looked only at the image's own, so CrossOver's cxcompatdb.so lost its gnutls and every bottle crashed (99eeba0).
  • A leaf name passed to dlopen is looked for in the same rpaths, after DYLD_LIBRARY_PATH. Before this, CrossOver's win32u.so found no FreeType (99eeba0).
  • Wine's loader is recognised by its WINE_RESERVE segment, not only by being named wine. CrossOver starts each process through a hard link named after the program, and its cmd.exe died 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 | sh

    Running 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 aarchx instead.

  • AArchX.zip, below: the app with ocerz and 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 ocerz is stripped of its debug map, and the guest C++ runtime is built with -ffile-prefix-map and 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 {{ in native-mode.md had stopped every Pages build since 2026-10-03, so the site had been showing that day's pages.

Downloads

  • aarchx-macos-arm64.tar.gz is 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 finds ocerz beside it, in ~/.aarchx or in Homebrew's bin.
    SHA-256: 3f3bd4e6fa97844eec33ca2c9775b42f2d7e85705416bc25539b0f4602962458
  • AArchX.zip is 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_rpath and wine_segment;
    • native 87/87;
    • the guest library, native C++, framework and format suites pass, and native Swift skips;
    • m32 40/40, plus high8 and shiftmem again 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: ocerz 0.7.1 ran a CoreFoundation program in cache and native mode, AArchX.app landed in ~/Applications with a valid signature, and --uninstall removed both.
    • AArchX.zip passes codesign --verify --deep --strict, and its own ocerz runs.
    • 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.exe through Wine Staging 11.18 under ocerz.
  • CrossOver: a CrossOver 26.3 bottle switched with the box ran cmd.exe under ocerz.

Not yet

  • No notarization. AArchX has no Developer ID, so AArchX.zip needs 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 ocerz in 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

Choose a tag to compare

@mont127 mont127 released this 13 Oct 15:29

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.

AArchX 0.6

Choose a tag to compare

@mont127 mont127 released this 07 Oct 13:10

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),%st as st0+st0, wrote the wrong register for the DC forms, reported every fcomi as equal, and made fcmov do 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 qword memory copy, long double copies, 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, fxch and integer conversions, in 64-bit and 32-bit blocks, plus the fild/fistp qword 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, cmpxchg and cmpxchg8b;
  • 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 cmov with 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.pdf still describes 0.3-dev.

AArchX 0.5

Choose a tag to compare

@mont127 mont127 released this 06 Oct 19:05

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_map claims anonymous fixed maps above 12 GB, searches from 12 GB up for anywhere maps, and drops hints above user space.
    • __block variables on Wine stacks forward to their host alias.
  • The calls Wine, Steam and R.E.P.O. make (1d95319):
    • __ulock_wait and __ulock_wait2 are 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_state answers the x86 debug-register flavors.
    • AudioUnitSetProperty converts 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 new and delete in 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.pdf still 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.