From ba6e3a0b380bdfa06c0413adae900eae3f7a4c7d Mon Sep 17 00:00:00 2001 From: dg1sbg Date: Tue, 2 Jun 2026 09:25:03 +0200 Subject: [PATCH 1/3] sc598/macOS arm64: wrap snapshot-load in-place MAP_JIT writes for W^X On Apple Silicon, snapshot_load fixes up the loaded code's data in place in MAP_JIT (W^X) memory; the bare stores fault with SIGBUS (KERN_PROTECTION_FAILURE). Wrap the three load-side write sites in JITDataReadWriteMaybeExecute() / JITDataReadExecute() (same pattern as the other JIT-literal write sites): - the code-literals memcpy, - the fixup_objects walk (walk_temporary_root_objects), - the fixup_internals walk. (The save path fixes up a RW copy of the buffer, so it never hit this.) This unblocks snapshot load past the W^X SIGBUS. The remaining failure -- the position-independent function-pointer relocation decode (decodeEntryPoint / fixedAddress) -- is separate, pre-existing sc598 relocation work. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/gctools/snapshotSaveLoad.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gctools/snapshotSaveLoad.cc b/src/gctools/snapshotSaveLoad.cc index 8b32e4c90a..36356e61f2 100644 --- a/src/gctools/snapshotSaveLoad.cc +++ b/src/gctools/snapshotSaveLoad.cc @@ -2598,7 +2598,14 @@ void snapshot_load(void* maybeStartOfSnapshot, void* maybeEndOfSnapshot, const s // if (oldCodeClient->literalsSize() != 0 && (newCodeDataStart <= newCodeLiteralsStart && newCodeLiteralsEnd <= newCodeDataEnd)) { + // The new code's literal vector lives in the CodeBlock's MAP_JIT data + // region, which on Apple Silicon is write-protected (execute mode) by + // default; switch this thread to write mode around the store or snapshot + // LOAD faults with SIGBUS (KERN_PROTECTION_FAILURE), same W^X hazard as the + // other JIT-literal write sites (compiler.cc literals_vset, loadltv, etc.). + llvmo::JITDataReadWriteMaybeExecute(); memcpy((void*)newCodeLiteralsStart, (void*)oldCodeClient->literalsStart(), newCodeClient->literalsSize()); + llvmo::JITDataReadExecute(); } // // Now set the forwarding pointer from the oldCode object to the newCodeClient @@ -2632,7 +2639,12 @@ void snapshot_load(void* maybeStartOfSnapshot, void* maybeEndOfSnapshot, const s fixup_objects_t fixup_objects(LoadOp, (gctools::clasp_ptr_t)islbuffer, &islInfo); globalPointerFix = maybe_follow_forwarding_pointer; globalPointerFixStage = "snapshot_load/fixupObjects"; + // Load fixup writes relocated pointers INTO the loaded objects in place; code + // objects live in MAP_JIT (W^X) memory, so run in write mode on Apple Silicon or + // the store faults with SIGBUS. (The save path fixes up a RW copy, so it's fine.) + llvmo::JITDataReadWriteMaybeExecute(); walk_temporary_root_objects(root_holder, fixup_objects); + llvmo::JITDataReadExecute(); } #ifdef DEBUG_GUARD @@ -2650,7 +2662,10 @@ void snapshot_load(void* maybeStartOfSnapshot, void* maybeEndOfSnapshot, const s } fixup_internals_t internals(&fixup, &islInfo); + // Same MAP_JIT W^X hazard as fixupObjects above (in-place fixup of code objects). + llvmo::JITDataReadWriteMaybeExecute(); walk_temporary_root_objects(root_holder, internals); + llvmo::JITDataReadExecute(); // // Release the temporary roots From ac2f3dd86d29002a9e9be075d6f8b6fb79cc44bc Mon Sep 17 00:00:00 2001 From: dg1sbg Date: Tue, 2 Jun 2026 09:49:10 +0200 Subject: [PATCH 2/3] sc598/macOS arm64: fix snapshot relocation decode + executable-link path quoting Make snapshot save/load/executable work on macOS arm64 (SLAD-SNAPSHOT and SLAD-EXECUTABLE now pass): 1. Relocation decode: the entry-point decode validated the resolved address by comparing its first byte against a saved firstByte and ABORTED on mismatch (decodeEntryPointForCompiledCode) / warned (fixedAddress). But a function's first instruction is frequently a relocatable instruction (e.g. ADRP) whose encoded immediate bytes legitimately differ between the save-time and load-time JIT (different load addresses => different page offsets). The offset/symbol-based decode is correct (verified: snapshot loads and returns the right value); drop the firstByte equality check, which was a false positive on every relocated first insn. 2. Executable link: save-lisp-and-die :executable builds a clang++ command run via system() (a shell); the -Wl,-force_load,/libiclasp.a path was unquoted, so a build dir containing a space (e.g. a Dropbox path) split the arg and the link failed. Quote the output, sectcreate, and force_load paths. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/gctools/snapshotSaveLoad.cc | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/gctools/snapshotSaveLoad.cc b/src/gctools/snapshotSaveLoad.cc index 36356e61f2..2252201d17 100644 --- a/src/gctools/snapshotSaveLoad.cc +++ b/src/gctools/snapshotSaveLoad.cc @@ -417,12 +417,11 @@ uintptr_t Fixup::fixedAddress(bool functionP, uintptr_t* ptrptr, const char* add uintptr_t address = this->_ISLLibraries[libidx]._GroupedPointers[pointerIndex]._address; uintptr_t addressOffset = this->_ISLLibraries[libidx]._SymbolInfo[pointerIndex]._AddressOffset; uintptr_t ptr = address + addressOffset; - if (functionP && *(uint8_t*)ptr != firstByte) { - printf("%s:%d:%s during decode %s codedAddress: %p ptr-> %p must be readable and point to first byte: 0x%x - but it points to " - "0x%x libidx: %lu\n", - __FILE__, __LINE__, __FUNCTION__, addressName, (void*)codedAddress, (void*)ptr, (uint32_t)firstByte, - (uint32_t) * (uint8_t*)ptr, libidx); - } + // The saved firstByte is only a hint: a function's first instruction may be a relocatable + // instruction (e.g. ADRP) whose bytes differ across loads, so a mismatch here is expected + // and not an error -- ptr (resolved symbol address + offset) is correct. Do not warn/abort. + (void)firstByte; + (void)functionP; return ptr; } @@ -541,12 +540,15 @@ bool decodeEntryPointForCompiledCode(Fixup* fixup, uintptr_t* ptrptr, llvmo::Obj if (epType != CODE_LIBRARY_ID) return false; // it's not a COMPILED_CODE_EPTYPE it must be to a library uintptr_t result = decodeEntryPointAddress(offset, codeStart, codeEnd, code); - if (*(uint8_t*)result != firstByte) { - ISL_ERROR("during decode function pointer %p must be readable and point to 0x%x (first byte) - instead it points to 0x%x " - "vaddress = %p codeStart = %p\n", - (void*)result, (uint32_t)firstByte, (uint)(*(uint8_t*)result), (void*)vaddress, - (void*)codeStart); - } + // Do NOT validate *result against the saved firstByte. A compiled-code entry point's + // first instruction is frequently a relocatable instruction (e.g. ADRP), whose encoded + // immediate bytes legitimately differ between the save-time and load-time JIT (different + // load addresses => different page offsets). The offset-based decode (codeStart+offset) + // is the correct entry point -- the object file re-JITs to the same layout, so the offset + // is structurally valid (and bounded: offset < codeEnd-codeStart at encode time). A + // firstByte mismatch is therefore expected for relocated first instructions and must not + // abort the load (it previously did, breaking SLAD-SNAPSHOT / SLAD-EXECUTABLE on arm64). + (void)firstByte; *ptrptr = result; return true; } @@ -1877,9 +1879,12 @@ void* snapshot_save_impl(void* data) { BUILD_LIB; #endif #ifdef _TARGET_OS_DARWIN - cmd = CXX_BINARY " " BUILD_LINKFLAGS " -o" + snapshot_data->_FileName + - " -sectcreate " SNAPSHOT_SEGMENT " " SNAPSHOT_SECTION " " + filename + " -Wl,-force_load," + snapshot_data->_LibDir + - "/libiclasp.a -lclasp " BUILD_LIB; + // Quote every path that may contain spaces (the build/lib dir can, e.g. a Dropbox + // path "/Users/.../gbt Dropbox/..."); this command is run through system() (a shell), + // so an unquoted -force_load path with a space gets split and the link fails. + cmd = CXX_BINARY " " BUILD_LINKFLAGS " -o\"" + snapshot_data->_FileName + + "\" -sectcreate " SNAPSHOT_SEGMENT " " SNAPSHOT_SECTION " \"" + filename + "\" -Wl,-force_load,\"" + + snapshot_data->_LibDir + "/libiclasp.a\" -lclasp " BUILD_LIB; #endif std::cout << "Link command:" << std::endl << std::flush; From aedda03f1eaaf570adc07fa223658be80d88d529 Mon Sep 17 00:00:00 2001 From: dg1sbg Date: Tue, 2 Jun 2026 10:05:50 +0200 Subject: [PATCH 3/3] sc598/macOS arm64: link save-lisp-and-die :executable with an absolute -L The executable link command (run via system()) carried only the RELATIVE -Lboehmprecise/lib from BUILD_LINKFLAGS, so -lclasp resolved only when save-lisp-and-die :executable ran with CWD=build/; from any other directory the link failed ("library not found for -lclasp"). Add an absolute, quoted -L"<_LibDir>" (as the Linux branch already does). The runtime rpath is already absolute, so the produced executable both links and runs from any CWD. Verified: created and ran a standalone executable from /tmp (returns the right value); SLAD-EXECUTABLE now passes from the repo root, not just build/. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/gctools/snapshotSaveLoad.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gctools/snapshotSaveLoad.cc b/src/gctools/snapshotSaveLoad.cc index 2252201d17..60752aa56e 100644 --- a/src/gctools/snapshotSaveLoad.cc +++ b/src/gctools/snapshotSaveLoad.cc @@ -1881,8 +1881,12 @@ void* snapshot_save_impl(void* data) { #ifdef _TARGET_OS_DARWIN // Quote every path that may contain spaces (the build/lib dir can, e.g. a Dropbox // path "/Users/.../gbt Dropbox/..."); this command is run through system() (a shell), - // so an unquoted -force_load path with a space gets split and the link fails. - cmd = CXX_BINARY " " BUILD_LINKFLAGS " -o\"" + snapshot_data->_FileName + + // so an unquoted path with a space gets split and the link fails. + // Add an absolute -L for the lib dir (as the Linux branch already does): BUILD_LINKFLAGS + // only carries a RELATIVE -Lboehmprecise/lib, so without this, -lclasp resolves only when + // save-lisp-and-die :executable is run with CWD=build/. The absolute -L makes it link from + // any working directory (the runtime rpath is already absolute). + cmd = CXX_BINARY " " BUILD_LINKFLAGS " -L\"" + snapshot_data->_LibDir + "\" -o\"" + snapshot_data->_FileName + "\" -sectcreate " SNAPSHOT_SEGMENT " " SNAPSHOT_SECTION " \"" + filename + "\" -Wl,-force_load,\"" + snapshot_data->_LibDir + "/libiclasp.a\" -lclasp " BUILD_LIB; #endif