Skip to content

Commit 332d9f3

Browse files
committed
jitlayers: replace sharedbytes intern pool with one that respects alignment (#52182)
The llvm optimizations may increase alignment beyond the initial MAX_ALIGN. This pool's alignment was previously only `sizeof(struct { atomic<int> RefCount; size_t Length; char Data[]; })` however, potentially resulting in segfaults at runtime. Fixes #52118. Should make CI much happier. (cherry picked from commit a65bc9a)
1 parent 0e96c9c commit 332d9f3

File tree

4 files changed

+108
-32
lines changed

4 files changed

+108
-32
lines changed

src/gc.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3854,8 +3854,7 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o
38543854
errno = last_errno;
38553855
jl_may_leak(base);
38563856
assert(align > 0);
3857-
unsigned diff = (offset - (uintptr_t)base) % align;
3858-
return (void*)((char*)base + diff);
3857+
return (void*)(LLT_ALIGN((uintptr_t)base + offset, (uintptr_t)align) - offset);
38593858
}
38603859

38613860
STATIC_INLINE void *gc_try_perm_alloc_pool(size_t sz, unsigned align, unsigned offset) JL_NOTSAFEPOINT

src/jitlayers.cpp

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,69 @@ namespace {
13011301

13021302
JuliaOJIT::ResourcePool<std::unique_ptr<TargetMachine>> TMs;
13031303
};
1304+
1305+
struct JITPointersT {
1306+
1307+
JITPointersT(SharedBytesT &SharedBytes, std::mutex &Lock) JL_NOTSAFEPOINT
1308+
: SharedBytes(SharedBytes), Lock(Lock) {}
1309+
1310+
void operator()(Module &M) JL_NOTSAFEPOINT {
1311+
std::lock_guard<std::mutex> locked(Lock);
1312+
for (auto &GV : make_early_inc_range(M.globals())) {
1313+
if (auto *Shared = getSharedBytes(GV)) {
1314+
++InternedGlobals;
1315+
GV.replaceAllUsesWith(Shared);
1316+
GV.eraseFromParent();
1317+
}
1318+
}
1319+
1320+
// Windows needs some inline asm to help
1321+
// build unwind tables
1322+
jl_decorate_module(M);
1323+
}
1324+
1325+
private:
1326+
// optimize memory by turning long strings into memoized copies, instead of
1327+
// making a copy per object file of output.
1328+
// we memoize them using a StringSet with a custom-alignment allocator
1329+
// to ensure they are properly aligned
1330+
Constant *getSharedBytes(GlobalVariable &GV) JL_NOTSAFEPOINT {
1331+
// We could probably technically get away with
1332+
// interning even external linkage globals,
1333+
// as long as they have global unnamedaddr,
1334+
// but currently we shouldn't be emitting those
1335+
// except in imaging mode, and we don't want to
1336+
// do this optimization there.
1337+
if (GV.hasExternalLinkage() || !GV.hasGlobalUnnamedAddr()) {
1338+
return nullptr;
1339+
}
1340+
if (!GV.hasInitializer()) {
1341+
return nullptr;
1342+
}
1343+
if (!GV.isConstant()) {
1344+
return nullptr;
1345+
}
1346+
auto CDS = dyn_cast<ConstantDataSequential>(GV.getInitializer());
1347+
if (!CDS) {
1348+
return nullptr;
1349+
}
1350+
StringRef Data = CDS->getRawDataValues();
1351+
if (Data.size() < 16) {
1352+
// Cutoff, since we don't want to intern small strings
1353+
return nullptr;
1354+
}
1355+
Align Required = GV.getAlign().valueOrOne();
1356+
Align Preferred = MaxAlignedAlloc::alignment(Data.size());
1357+
if (Required > Preferred)
1358+
return nullptr;
1359+
StringRef Interned = SharedBytes.insert(Data).first->getKey();
1360+
assert(llvm::isAddrAligned(Preferred, Interned.data()));
1361+
return literal_static_pointer_val(Interned.data(), GV.getType());
1362+
}
1363+
1364+
SharedBytesT &SharedBytes;
1365+
std::mutex &Lock;
1366+
};
13041367
}
13051368

13061369
llvm::DataLayout jl_create_datalayout(TargetMachine &TM) {
@@ -1493,8 +1556,7 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM)
14931556
++ModulesAdded;
14941557
orc::SymbolLookupSet NewExports;
14951558
TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT {
1496-
jl_decorate_module(M);
1497-
shareStrings(M);
1559+
JITPointersT(SharedBytes, RLST_mutex)(M);
14981560
for (auto &F : M.global_values()) {
14991561
if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) {
15001562
auto Name = ES.intern(getMangledName(F.getName()));
@@ -1820,32 +1882,6 @@ void jl_merge_module(orc::ThreadSafeModule &destTSM, orc::ThreadSafeModule srcTS
18201882
});
18211883
}
18221884

1823-
// optimize memory by turning long strings into memoized copies, instead of
1824-
// making a copy per object file of output.
1825-
void JuliaOJIT::shareStrings(Module &M)
1826-
{
1827-
++InternedGlobals;
1828-
std::vector<GlobalVariable*> erase;
1829-
for (auto &GV : M.globals()) {
1830-
if (!GV.hasInitializer() || !GV.isConstant())
1831-
continue;
1832-
ConstantDataSequential *CDS = dyn_cast<ConstantDataSequential>(GV.getInitializer());
1833-
if (CDS == nullptr)
1834-
continue;
1835-
StringRef data = CDS->getRawDataValues();
1836-
if (data.size() > 16) { // only for long strings: keep short ones as values
1837-
Type *T_size = Type::getIntNTy(GV.getContext(), sizeof(void*) * 8);
1838-
Constant *v = ConstantExpr::getIntToPtr(
1839-
ConstantInt::get(T_size, (uintptr_t)(*ES.intern(data)).data()),
1840-
GV.getType());
1841-
GV.replaceAllUsesWith(v);
1842-
erase.push_back(&GV);
1843-
}
1844-
}
1845-
for (auto GV : erase)
1846-
GV->eraseFromParent();
1847-
}
1848-
18491885
//TargetMachine pass-through methods
18501886

18511887
std::unique_ptr<TargetMachine> JuliaOJIT::cloneTargetMachine() const

src/jitlayers.h

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// This file is a part of Julia. License is MIT: https://julialang.org/license
22

33
#include <llvm/ADT/MapVector.h>
4+
#include <llvm/ADT/StringSet.h>
5+
#include <llvm/Support/AllocatorBase.h>
46

57
#include <llvm/IR/LLVMContext.h>
68
#include <llvm/IR/Constants.h>
@@ -298,6 +300,44 @@ static const inline char *name_from_method_instance(jl_method_instance_t *li) JL
298300
return jl_is_method(li->def.method) ? jl_symbol_name(li->def.method->name) : "top-level scope";
299301
}
300302

303+
template <size_t offset = 0>
304+
class MaxAlignedAllocImpl
305+
: public AllocatorBase<MaxAlignedAllocImpl<offset>> {
306+
307+
public:
308+
MaxAlignedAllocImpl() JL_NOTSAFEPOINT = default;
309+
310+
static Align alignment(size_t Size) JL_NOTSAFEPOINT {
311+
// Define the maximum alignment we expect to require, from offset bytes off
312+
// the returned pointer, this is >= alignof(std::max_align_t), which is too
313+
// small often to actually use.
314+
const size_t MaxAlignment = JL_CACHE_BYTE_ALIGNMENT;
315+
return Align(std::min((size_t)llvm::PowerOf2Ceil(Size), MaxAlignment));
316+
}
317+
318+
LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, Align Alignment) {
319+
Align MaxAlign = alignment(Size);
320+
assert(Alignment < MaxAlign); (void)Alignment;
321+
return jl_gc_perm_alloc(Size, 0, MaxAlign.value(), offset);
322+
}
323+
324+
inline LLVM_ATTRIBUTE_RETURNS_NONNULL
325+
void * Allocate(size_t Size, size_t Alignment) {
326+
return Allocate(Size, Align(Alignment));
327+
}
328+
329+
// Pull in base class overloads.
330+
using AllocatorBase<MaxAlignedAllocImpl>::Allocate;
331+
332+
void Deallocate(const void *Ptr, size_t Size, size_t /*Alignment*/) { abort(); }
333+
334+
// Pull in base class overloads.
335+
using AllocatorBase<MaxAlignedAllocImpl>::Deallocate;
336+
337+
private:
338+
};
339+
using MaxAlignedAlloc = MaxAlignedAllocImpl<>;
340+
301341
typedef JITSymbol JL_JITSymbol;
302342
// The type that is similar to SymbolInfo on LLVM 4.0 is actually
303343
// `JITEvaluatedSymbol`. However, we only use this type when a JITSymbol
@@ -306,6 +346,7 @@ typedef JITSymbol JL_SymbolInfo;
306346

307347
using CompilerResultT = Expected<std::unique_ptr<llvm::MemoryBuffer>>;
308348
using OptimizerResultT = Expected<orc::ThreadSafeModule>;
349+
using SharedBytesT = StringSet<MaxAlignedAllocImpl<sizeof(StringSet<>::MapEntryTy)>>;
309350

310351
class JuliaOJIT {
311352
public:
@@ -538,7 +579,6 @@ class JuliaOJIT {
538579
private:
539580
std::string getMangledName(StringRef Name) JL_NOTSAFEPOINT;
540581
std::string getMangledName(const GlobalValue *GV) JL_NOTSAFEPOINT;
541-
void shareStrings(Module &M) JL_NOTSAFEPOINT;
542582

543583
const std::unique_ptr<TargetMachine> TM;
544584
const DataLayout DL;
@@ -551,6 +591,7 @@ class JuliaOJIT {
551591
std::mutex RLST_mutex{};
552592
int RLST_inc = 0;
553593
DenseMap<void*, std::string> ReverseLocalSymbolTable;
594+
SharedBytesT SharedBytes;
554595

555596
//Compilation streams
556597
jl_locked_stream dump_emitted_mi_name_stream;

src/julia_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize) JL_NOTSAFEPOINT;
351351
extern uv_mutex_t gc_perm_lock;
352352
void *jl_gc_perm_alloc_nolock(size_t sz, int zero,
353353
unsigned align, unsigned offset) JL_NOTSAFEPOINT;
354-
void *jl_gc_perm_alloc(size_t sz, int zero,
354+
JL_DLLEXPORT void *jl_gc_perm_alloc(size_t sz, int zero,
355355
unsigned align, unsigned offset) JL_NOTSAFEPOINT;
356356
void gc_sweep_sysimg(void);
357357

0 commit comments

Comments
 (0)