Skip to content

Fix macOS Apple Silicon build (Homebrew paths, rpath quoting, JIT W^X SIGBUS)#1768

Open
dg1sbg wants to merge 2 commits into
clasp-developers:mainfrom
dg1sbg:fix-apple-silicon-build
Open

Fix macOS Apple Silicon build (Homebrew paths, rpath quoting, JIT W^X SIGBUS)#1768
dg1sbg wants to merge 2 commits into
clasp-developers:mainfrom
dg1sbg:fix-apple-silicon-build

Conversation

@dg1sbg

@dg1sbg dg1sbg commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes ninja -C build work on a macOS Apple Silicon machine with a Homebrew LLVM 22 toolchain. Three independent problems are fixed; the build now compiles, links, completes the native-image bootstrap, and produces a working clasp.

1. koga: Homebrew include path (Apple Silicon)

boost (pulled in by clbind/config.h) ships no pkg-config file, so it isn't declared as a koga library; clasp relies on it being on the default include path. units.lisp only added the Intel Homebrew prefix /usr/local/include, so on Apple Silicon /opt/homebrew/include was never searched and the build failed at the first compile with 'boost/config.hpp' file not found. Now adds /opt/homebrew/include for darwin when present (probe-guarded — Intel and Linux unaffected).

2. koga: quote the rpath

The rpath was emitted as an unquoted -Wl,-rpath,<abs path>. When the build directory contains a space, ninja hands the flag to /bin/sh, which splits it at the space and the link fails with no such file or directory: '<tail>'. The path is now quoted.

3. SIGBUS writing native-module literals on Apple Silicon (JIT W^X)

The substantive one. During the Compiling Clasp native image step, loading a freshly-compiled native FASL crashed with:

Condition of type: BUS-ERROR
Bus error. Attempted to access invalid memory address #x10721c830.
... core::loadltv::attr_clasp_module_native(unsigned int)

lldb pinned it to a str (store) into an rwx page that still faulted with EXC_BAD_ACCESS code=2 (KERN_PROTECTION_FAILURE) — the signature of MAP_JIT memory under Apple Silicon hardware W^X. clasp writes Lisp pointers into each native module's literals vector, which lives in the LLVM ORC JITLink MAP_JIT slab; a thread must switch to write mode (pthread_jit_write_protect_np(false)) before writing. On x86-64/Linux the rwx page is genuinely writable, so the bug was latent there; LLVM 22's JITLink on macOS ARM64 exposes it.

The helpers llvmo::JITDataReadWriteMaybeExecute() / JITDataReadExecute() already existed for exactly this purpose but had no callers. This wires them around every write into JIT-resident literals memory:

  • core::core__literals_vset (compiler.cc)
  • llvmo::code_literal_set (code.cc)
  • loadltv::attr_clasp_module_native (loadltv.cc)
  • loadltv::attr_clasp_function_native_estranged (loadltv.cc)
  • snapshot-load literal relocation memcpy (snapshotSaveLoad.cc)

Reads are left untouched (MAP_JIT memory is readable in execute mode; only writes fault), and each write-mode window is kept to the bare store so no JIT code runs while the thread is in write mode. Each fix made the crash advance to the next write site, confirming a single root cause across these locations rather than guesswork.

Testing

  • ninja -C build → exit 0; re-run reports no work to do; zero FAILED:/Bus error/Condition of type lines in the log.
  • Smoke test on the resulting native image: (defun fib …)(compile 'fib)(fib 20) = 6765 (exercises Cleavir → native codegen → JIT → literals writes), (require :asdf) loads, clean exit.

Notes for reviewers

🤖 Generated with Claude Code

dg1sbg and others added 2 commits June 2, 2026 15:52
Two problems prevented `ninja -C build` from working on an Apple Silicon
Mac with a Homebrew toolchain:

* boost (used by clbind/config.h) ships no pkg-config file and is therefore
  not declared as a koga `library`; clasp relies on it being on the default
  include path. units.lisp only added the Intel Homebrew prefix
  `/usr/local/include`, so `/opt/homebrew/include` was never searched and the
  build failed immediately with "'boost/config.hpp' file not found". Add
  `/opt/homebrew/include` for darwin when it exists (probe-guarded, so Intel
  and Linux are unaffected).

* The rpath was emitted as an unquoted `-Wl,-rpath,<abs path>`. When the build
  directory contains a space, ninja passes the flag through /bin/sh which then
  splits it at the space, and the link fails with
  "no such file or directory: '<tail of path>'". Quote the path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On Apple Silicon, the LLVM ORC JITLink memory slab is mapped MAP_JIT and is
write-protected (execute mode) per-thread by default; a thread must call
pthread_jit_write_protect_np(false) before writing to it. clasp writes Lisp
object pointers into each native module's literals vector, which lives in that
JIT memory. Those writes were not bracketed by a switch to write mode, so on
Apple Silicon they fault with SIGBUS (EXC_BAD_ACCESS code=2,
KERN_PROTECTION_FAILURE) -- which manifested as a crash in
loadltv::attr_clasp_module_native while loading freshly compiled native FASLs
during the "Compiling Clasp native image" bootstrap step. (On x86-64 and Linux
the rwx page is genuinely writable, so the bug was latent there.)

The helpers JITDataReadWriteMaybeExecute()/JITDataReadExecute() already exist
for exactly this but had no callers. Bracket every write into JIT-resident
literals memory with them:

* core::core__literals_vset            (compiler.cc)
* llvmo::code_literal_set              (code.cc)
* loadltv::attr_clasp_module_native    (loadltv.cc)
* loadltv::attr_clasp_function_native_estranged (loadltv.cc)
* snapshot-load literal relocation memcpy (snapshotSaveLoad.cc)

Reads of the literals vector are left untouched: MAP_JIT memory is readable in
execute mode, only writes fault. The write-mode window is kept as small as
possible (the bare store) so no JIT code is executed while the thread is in
write mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant