diff --git a/src/core/compiler.cc b/src/core/compiler.cc index 73a51179c0..eee7ffabc8 100644 --- a/src/core/compiler.cc +++ b/src/core/compiler.cc @@ -200,8 +200,8 @@ void initializer_functions_invoke() { } // These functions are used to access a runtime module's literals vector. -// The vector is a T_sp[]. This could possibly be done with some normal -// CFFI accessor instead. +// The vector is a T_sp[]. See also CODE-LITERAL in llvmo/code.cc, +// which does the same but through the object file rather than a raw pointer. CL_DEFUN T_sp core__literals_vref(Pointer_sp lvec, size_t index) { return ((T_sp*)(lvec->ptr()))[index]; @@ -210,7 +210,12 @@ CL_DEFUN T_sp core__literals_vref(Pointer_sp lvec, size_t index) { CL_LISPIFY_NAME("core:literals_vref"); CL_DEFUN_SETF T_sp core__literals_vset(T_sp val, Pointer_sp lvec, size_t index) { + // The literals vector lives in JIT (MAP_JIT) memory, which on Apple Silicon + // is write-protected (execute mode) by default; we must switch this thread to + // write mode around the store or it faults with a SIGBUS (KERN_PROTECTION_FAILURE). + llvmo::JITDataReadWriteMaybeExecute(); ((T_sp*)(lvec->ptr()))[index] = val; + llvmo::JITDataReadExecute(); return val; } diff --git a/src/core/loadltv.cc b/src/core/loadltv.cc index eb42aed138..4c6250985c 100644 --- a/src/core/loadltv.cc +++ b/src/core/loadltv.cc @@ -946,12 +946,16 @@ struct loadltv { // bit paranoid, but beats the alternative (closure with // incompatible function that segfaults or worse when called). bool seen = false; + // lits[] is in write-protected JIT (MAP_JIT) memory on Apple Silicon; + // switch this thread to write mode just for the store. + llvmo::JITDataReadWriteMaybeExecute(); for (size_t i = 0; i < nlits; ++i) { if (lits[i] == function.raw_()) { lits[i] = scf.raw_(); seen = true; } } + llvmo::JITDataReadExecute(); if (!seen) SIMPLE_ERROR("While loading native module: Could not find bytecode function {} in modules constants", _rep_(function)); } @@ -1147,9 +1151,21 @@ struct loadltv { module_id); } else { T_O** lits = (T_O**)vlits; - for (size_t i = 0; i < nlits; ++i) { - lits[i] = get_ltv(read_index()).raw_(); - } + // lits is in write-protected JIT memory (MAP_JIT) on Apple Silicon, so we + // have to arrange things a bit for safety. We shouldn't do a simple loop of + // get_ltv read_index within JIT un-protection, since both those functions can + // signal errors and thereby execute arbitrary Lisp code. + // Instead we read all the T_O* into a temporary buffer (on the heap, since + // there may be a lot), and then copy those into lits while JIT-unprotected. + // This is probably faster than deprotecting and reprotecting for every literal. + T_O** tlits = (T_O**)calloc(nlits, sizeof(T_O*)); + for (size_t i = 0; i < nlits; ++i) tlits[i] = get_ltv(read_index()).raw_(); + + llvmo::JITDataReadWriteMaybeExecute(); + memcpy(lits, tlits, nlits * sizeof(T_O*)); + llvmo::JITDataReadExecute(); + + free(tlits); /* uint16_t nfuns = read_u16(); for (size_t j = 0; j < nfuns; ++j) { diff --git a/src/gctools/snapshotSaveLoad.cc b/src/gctools/snapshotSaveLoad.cc index 022193618b..fdf60d28cf 100644 --- a/src/gctools/snapshotSaveLoad.cc +++ b/src/gctools/snapshotSaveLoad.cc @@ -374,20 +374,19 @@ void SymbolLookup::addAllLibraries(FILE* fout) { namespace snapshotSaveLoad { -void decodeRelocation_(uintptr_t codedAddress, uint8_t& firstByte, uintptr_t& libindex, uintptr_t& offset) { +void decodeRelocation_(uintptr_t codedAddress, uintptr_t& libindex, uintptr_t& offset) { offset = (uintptr_t)codedAddress & (((uintptr_t)1 << 32) - 1); libindex = ((uintptr_t)codedAddress >> 32) & (uintptr_t)0xff; - firstByte = (uint8_t)(((uintptr_t)codedAddress >> 48) & 0xff); } -uintptr_t encodeRelocation_(uint8_t firstByte, size_t libraryIndex, size_t relocationOrOffset) { +uintptr_t encodeRelocation_(size_t libraryIndex, size_t relocationOrOffset) { if ((relocationOrOffset & (((uintptr_t)1 << 32) - 1)) != relocationOrOffset) { ISL_ERROR("relocationOrOffset %lu is too large", relocationOrOffset); } if (libraryIndex > 256) { ISL_ERROR("libraryIndex %lu is too large", libraryIndex); } - uintptr_t result = ((uintptr_t)1 << 56 | (uintptr_t)firstByte << 48) | (libraryIndex << 32) | relocationOrOffset; + uintptr_t result = ((uintptr_t)1 << 56) | (libraryIndex << 32) | relocationOrOffset; return result; } @@ -418,22 +417,16 @@ void Fixup::registerFunctionPointer(size_t libraryIndex, uintptr_t* functionPtrP uintptr_t Fixup::fixedAddress(bool functionP, uintptr_t* ptrptr, const char* addressName) { - uint8_t firstByte; uintptr_t libidx; uintptr_t pointerIndex; if (virtualMethodP(ptrptr)) return *ptrptr; uintptr_t codedAddress = *ptrptr; - decodeRelocation_(codedAddress, firstByte, libidx, pointerIndex); + decodeRelocation_(codedAddress, libidx, pointerIndex); 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); - } + (void)functionP; return ptr; } @@ -471,13 +464,13 @@ size_t Fixup::ensureLibraryRegistered(uintptr_t address) { return idx; }; -uintptr_t encodeEntryPointValue(uint8_t firstByte, uint8_t epType, uintptr_t offset) { - uintptr_t result = encodeRelocation_(firstByte, epType, offset); +uintptr_t encodeEntryPointValue(uint8_t epType, uintptr_t offset) { + uintptr_t result = encodeRelocation_(epType, offset); return result; } -void decodeEntryPointValue(uintptr_t value, uint8_t& firstByte, uintptr_t& epType, uintptr_t& offset) { - decodeRelocation_(value, firstByte, epType, offset); +void decodeEntryPointValue(uintptr_t value, uintptr_t& epType, uintptr_t& offset) { + decodeRelocation_(value, epType, offset); }; uintptr_t decodeEntryPointAddress(uintptr_t offset, uintptr_t codeStart, uintptr_t codeEnd, core::T_sp code) { @@ -530,34 +523,26 @@ bool encodeEntryPointForCompiledCode(Fixup* fixup, uintptr_t* ptrptr, llvmo::Obj if (address < 65536) { printf("%s:%d:%s address @%p is %p and is small\n", __FILE__, __LINE__, __FUNCTION__, ptrptr, (void*)address); } - uint8_t firstByte = *(uint8_t*)address; uintptr_t codeStart = (uintptr_t)code->codeStart(); uintptr_t codeEnd = (uintptr_t)code->codeEnd(); if (address < codeStart || codeEnd <= address) return false; uintptr_t offset = encodeEntryPointOffset(address, codeStart, codeEnd, code); - uintptr_t result = encodeEntryPointValue(firstByte, CODE_LIBRARY_ID, offset); + uintptr_t result = encodeEntryPointValue(CODE_LIBRARY_ID, offset); *ptrptr = result; return true; } bool decodeEntryPointForCompiledCode(Fixup* fixup, uintptr_t* ptrptr, llvmo::ObjectFile_sp code) { uintptr_t vaddress = *ptrptr; - uint8_t firstByte; uintptr_t epType; uintptr_t offset; uintptr_t codeStart = code->codeStart(); uintptr_t codeEnd = code->codeEnd(); - decodeEntryPointValue(vaddress, firstByte, epType, offset); + decodeEntryPointValue(vaddress, epType, offset); 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); - } *ptrptr = result; return true; } @@ -1547,9 +1532,7 @@ void prepareRelocationTableForSave(Fixup* fixup, SymbolLookup& symbolLookup) { } // Now encode the relocation void** addr = (void**)curLib._InternalPointers[ii]._ptrptr; - uint8_t* uint8ptr = (uint8_t*)*addr; - uint8_t firstByte = *uint8ptr; - *curLib._InternalPointers[ii]._ptrptr = encodeRelocation_(firstByte, idx, groupPointerIdx); + *curLib._InternalPointers[ii]._ptrptr = encodeRelocation_(idx, groupPointerIdx); } core::lisp_write(fmt::format("{} unique pointers need to be passed to dladdr\n", curLib._GroupedPointers.size())); SaveSymbolCallback thing(curLib); @@ -1904,9 +1887,16 @@ 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 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 std::cout << "Link command:" << std::endl << std::flush; @@ -2626,7 +2616,11 @@ void snapshot_load(void* maybeStartOfSnapshot, void* maybeEndOfSnapshot, const s // if (oldCodeClient->literalsSize() != 0 && (newCodeDataStart <= newCodeLiteralsStart && newCodeLiteralsEnd <= newCodeDataEnd)) { + // newCodeLiteralsStart is in write-protected JIT (MAP_JIT) memory on + // Apple Silicon; switch this thread to write mode around the copy. + llvmo::JITDataReadWriteMaybeExecute(); memcpy((void*)newCodeLiteralsStart, (void*)oldCodeClient->literalsStart(), newCodeClient->literalsSize()); + llvmo::JITDataReadExecute(); } // // Now set the forwarding pointer from the oldCode object to the newCodeClient @@ -2660,7 +2654,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 @@ -2678,7 +2677,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 diff --git a/src/koga/units.lisp b/src/koga/units.lisp index 3e5605b322..e5522a71d8 100644 --- a/src/koga/units.lisp +++ b/src/koga/units.lisp @@ -219,6 +219,11 @@ (append-cflags configuration "-std=gnu++20" :type :cxxflags) #+darwin (append-cflags configuration "-stdlib=libc++" :type :cxxflags) #+darwin (append-cflags configuration "-I/usr/local/include") + ;; Apple Silicon Homebrew installs headers (e.g. boost, which ships no + ;; pkg-config file) under /opt/homebrew rather than the Intel /usr/local + ;; prefix. Add it to the search path when present. + #+darwin (when (probe-file #P"/opt/homebrew/include/") + (append-cflags configuration "-I/opt/homebrew/include")) #+linux (append-cflags configuration "-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fno-stack-protector -stdlib=libstdc++" :type :cxxflags) #+linux (append-cflags configuration "-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fno-stack-protector" @@ -317,7 +322,11 @@ has not been set." (message :emph "Configuring non-reproducible build") (loop for variant in (variants configuration) do (append-ldflags variant - (format nil "-Wl,-rpath,~a" + ;; Quote the path: the build directory may + ;; contain spaces, and ninja passes ldflags + ;; through /bin/sh which would otherwise split + ;; the rpath at the space. + (format nil "-Wl,-rpath,\"~a\"" (normalize-directory (uiop:ensure-absolute-pathname (merge-pathnames diff --git a/src/llvmo/code.cc b/src/llvmo/code.cc index 3de4110af1..7e04ec5b89 100644 --- a/src/llvmo/code.cc +++ b/src/llvmo/code.cc @@ -237,7 +237,11 @@ CL_LISPIFY_NAME("CODE-LITERAL"); CL_DEFUN_SETF core::T_sp code_literal_set(core::T_sp lit, ObjectFile_sp code, size_t idx) { core::T_O** literals = (core::T_O**)(code->literalsStart()); + // The literals vector lives in write-protected JIT (MAP_JIT) memory on Apple + // Silicon; switch this thread to write mode around the store to avoid a SIGBUS. + JITDataReadWriteMaybeExecute(); literals[idx] = lit.raw_(); + JITDataReadExecute(); return lit; } diff --git a/src/llvmo/llvmoPackage.cc b/src/llvmo/llvmoPackage.cc index 589d77da60..0e69fb7921 100644 --- a/src/llvmo/llvmoPackage.cc +++ b/src/llvmo/llvmoPackage.cc @@ -55,6 +55,7 @@ THE SOFTWARE. #include #include #include +#include // JITDataReadWriteMaybeExecute / JITDataReadExecute (W^X) #include #include #include @@ -690,7 +691,15 @@ CL_DEFUN_SETF core::T_sp setf_jit_lookup_t(core::T_sp value, JITDylib_sp dylib, if (!found) SIMPLE_ERROR("Could not find pointer for name |{}|", name); core::T_O** tptr = (core::T_O**)ptr; + // The JIT'd global (e.g. an FFI callback's `callback-lisp-function-N`) lives in + // MAP_JIT memory, which on Apple Silicon is write-protected (execute mode) by + // default; switch this thread to write mode around the store or it faults with a + // SIGBUS (KERN_PROTECTION_FAILURE). This is the W^X store site that make-callback + // / %defcallback hits (the defcallback-native regression test); the other literal + // write sites are wrapped in compiler.cc / loadltv.cc. + JITDataReadWriteMaybeExecute(); *tptr = value.raw_(); + JITDataReadExecute(); return value; }