[llvm] [ORC] Add opt-in per-JITDylib colocating slab allocator (PR #207970)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 05:41:44 PDT 2026
================
@@ -179,4 +179,184 @@ TEST(MapperJITLinkMemoryManagerTest, Coalescing) {
EXPECT_THAT_ERROR(std::move(Err4), Succeeded());
}
+// A colocating (slab) allocator places all objects inside a single reservation,
+// so any two of them are close enough for a 32-bit PC-relative cross-object
+// reference: an x86-64 REL32 is a signed 32-bit displacement, i.e. the target
+// must be within +/-2GB of the reference. The default InProcessMemoryManager
+// does NOT guarantee this -- it allocates each object independently, so two
+// objects can land more than 2GB apart and a direct cross-object call/branch
+// would fail to relocate ("out of range"). This test verifies that the
+// Mapper-based slab allocator keeps separately-allocated objects within REL32
+// range.
+TEST(MapperJITLinkMemoryManagerTest, Colocation) {
+ auto Mapper = cantFail(InProcessMemoryMapper::Create());
+ auto MemMgr = std::make_unique<MapperJITLinkMemoryManager>(16 * 1024 * 1024,
+ std::move(Mapper));
+ auto SSP = std::make_shared<SymbolStringPool>();
+
+ // Allocate several "objects", the way separate input files would be added.
+ constexpr unsigned NumObjects = 8;
+ SmallVector<JITLinkMemoryManager::FinalizedAlloc> Allocs;
+ uint64_t MinAddr = ~uint64_t(0), MaxAddr = 0;
+
+ for (unsigned I = 0; I != NumObjects; ++I) {
+ auto SSA = jitlink::SimpleSegmentAlloc::Create(
+ *MemMgr, SSP, Triple("x86_64-apple-darwin"), nullptr,
+ {{MemProt::Read, {4096, Align(16)}}});
+ EXPECT_THAT_EXPECTED(SSA, Succeeded());
+
+ uint64_t Addr =
+ ExecutorAddr(SSA->getSegInfo(MemProt::Read).Addr).getValue();
+ if (Addr < MinAddr)
+ MinAddr = Addr;
+ if (Addr > MaxAddr)
+ MaxAddr = Addr;
+
+ auto FA = SSA->finalize();
+ EXPECT_THAT_EXPECTED(FA, Succeeded());
+ Allocs.push_back(std::move(*FA));
+ }
+
+ // All objects sit inside the one ~16MB reservation, so the span between the
+ // lowest and highest is far below the 2GB REL32 limit.
+ constexpr uint64_t REL32Limit = uint64_t(1) << 31; // 2GB
+ EXPECT_LT(MaxAddr - MinAddr, REL32Limit);
+
+ for (auto &FA : Allocs)
+ EXPECT_THAT_ERROR(MemMgr->deallocate(std::move(FA)), Succeeded());
+}
+
+// With per-JITDylib colocation enabled, each JITDylib draws from its own pool
+// of reservations: a second JITDylib's allocation does NOT reuse the first
+// JITDylib's leftover space, so it triggers a fresh reservation. With the
+// default (single shared pool) the second JITDylib reuses the first's leftover,
+// so only one reservation is made. Counting reservations is a deterministic way
+// to observe that objects are being grouped per-JITDylib.
+TEST(MapperJITLinkMemoryManagerTest, ColocationPerJITDylib) {
+ JITLinkDylib JDA("A"), JDB("B");
+ auto SSP = std::make_shared<SymbolStringPool>();
+
+ // Allocate a small object in the given JITDylib and finalize it.
+ auto AllocSmall = [&](MapperJITLinkMemoryManager &MemMgr,
+ const JITLinkDylib *JD) {
+ auto SSA = jitlink::SimpleSegmentAlloc::Create(
+ MemMgr, SSP, Triple("x86_64-apple-darwin"), JD,
+ {{MemProt::Read, {4096, Align(16)}}});
+ EXPECT_THAT_EXPECTED(SSA, Succeeded());
+ auto FA = SSA->finalize();
+ EXPECT_THAT_EXPECTED(FA, Succeeded());
+ return cantFail(std::move(FA));
+ };
+
+ // Per-JITDylib colocation ON: JD B cannot reuse JD A's reservation, so a
+ // second reservation is made.
+ {
+ auto Mapper = std::make_unique<CounterMapper>(
+ cantFail(InProcessMemoryMapper::Create()));
+ auto *Counter = static_cast<CounterMapper *>(Mapper.get());
+ MapperJITLinkMemoryManager MemMgr(16 * 1024 * 1024, std::move(Mapper),
+ /*ColocatePerJITDylib=*/true);
+
+ auto FA_A = AllocSmall(MemMgr, &JDA);
+ EXPECT_EQ(Counter->ReserveCount, 1);
+ auto FA_B = AllocSmall(MemMgr, &JDB);
+ EXPECT_EQ(Counter->ReserveCount, 2); // separate reservation for JD B
+
+ EXPECT_THAT_ERROR(MemMgr.deallocate(std::move(FA_A)), Succeeded());
+ EXPECT_THAT_ERROR(MemMgr.deallocate(std::move(FA_B)), Succeeded());
+ }
+
+ // Default (single shared pool): JD B reuses JD A's leftover, so only one
+ // reservation is made.
+ {
+ auto Mapper = std::make_unique<CounterMapper>(
+ cantFail(InProcessMemoryMapper::Create()));
+ auto *Counter = static_cast<CounterMapper *>(Mapper.get());
+ MapperJITLinkMemoryManager MemMgr(16 * 1024 * 1024, std::move(Mapper));
+
+ auto FA_A = AllocSmall(MemMgr, &JDA);
+ EXPECT_EQ(Counter->ReserveCount, 1);
+ auto FA_B = AllocSmall(MemMgr, &JDB);
+ EXPECT_EQ(Counter->ReserveCount, 1); // reused JD A's reservation
----------------
lhames wrote:
Ditto here: assert that allocated addresses are within `reservationUnits()` of one another.
https://github.com/llvm/llvm-project/pull/207970
More information about the llvm-commits
mailing list