Skip to content
Merged
9 changes: 7 additions & 2 deletions src/core/compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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;
}

Expand Down
22 changes: 19 additions & 3 deletions src/core/loadltv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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) {
Expand Down
66 changes: 34 additions & 32 deletions src/gctools/snapshotSaveLoad.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
11 changes: 10 additions & 1 deletion src/koga/units.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/llvmo/code.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
9 changes: 9 additions & 0 deletions src/llvmo/llvmoPackage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ THE SOFTWARE.
#include <clasp/llvmo/debugInfoExpose.h>
#include <clasp/llvmo/intrinsics.h>
#include <clasp/llvmo/claspLinkPass.h>
#include <clasp/llvmo/code.h> // JITDataReadWriteMaybeExecute / JITDataReadExecute (W^X)
#include <clasp/core/bytecode.h>
#include <clasp/core/instance.h>
#include <clasp/core/funcallableInstance.h>
Expand Down Expand Up @@ -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;
}

Expand Down
Loading