Skip to content

Commit 1922c6c

Browse files
committed
Implement reserveAllocationSpace for SectionMemoryManager
Implements `reserveAllocationSpace` and provides an option to enable `needsToReserveAllocationSpace` for large-memory environments with AArch64. The [AArch64 ABI](https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst) has limits on the distance between sections as the instructions to reference them are limited to 2 or 4GB. Allocating sections in multiple blocks can result in distances greater than that on systems with lots of memory. In those environments several projects using SectionMemoryManager with MCJIT have run across assertion failures for the R_AARCH64_ADR_PREL_PG_HI21 instruction as it attempts to address across distances greater than 2GB (an int32). Fixes #71963 by allocating all sections in a single contiguous memory allocation, limiting the distance required for instruction offsets similar to how pre-compiled binaries would be loaded into memory. Does not change the default behavior of SectionMemoryManager.
1 parent a3bd87b commit 1922c6c

File tree

3 files changed

+363
-15
lines changed

3 files changed

+363
-15
lines changed

llvm/include/llvm/ExecutionEngine/SectionMemoryManager.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,24 @@ class SectionMemoryManager : public RTDyldMemoryManager {
104104
/// Creates a SectionMemoryManager instance with \p MM as the associated
105105
/// memory mapper. If \p MM is nullptr then a default memory mapper is used
106106
/// that directly calls into the operating system.
107-
SectionMemoryManager(MemoryMapper *MM = nullptr);
107+
///
108+
/// If \p ReserveAlloc is true all memory will be pre-allocated, and any
109+
/// attempts to allocate beyond pre-allocated memory will fail.
110+
SectionMemoryManager(MemoryMapper *MM = nullptr, bool ReserveAlloc = false);
108111
SectionMemoryManager(const SectionMemoryManager &) = delete;
109112
void operator=(const SectionMemoryManager &) = delete;
110113
~SectionMemoryManager() override;
111114

115+
/// Enable reserveAllocationSpace when requested.
116+
bool needsToReserveAllocationSpace() override { return ReserveAllocation; }
117+
118+
/// Implements allocating all memory in a single block. This is required to
119+
/// limit memory offsets to fit the ARM ABI; large memory systems may
120+
/// otherwise allocate separate sections too far apart.
121+
void reserveAllocationSpace(uintptr_t CodeSize, Align CodeAlign,
122+
uintptr_t RODataSize, Align RODataAlign,
123+
uintptr_t RWDataSize, Align RWDataAlign) override;
124+
112125
/// Allocates a memory block of (at least) the given size suitable for
113126
/// executable code.
114127
///
@@ -180,13 +193,16 @@ class SectionMemoryManager : public RTDyldMemoryManager {
180193
std::error_code applyMemoryGroupPermissions(MemoryGroup &MemGroup,
181194
unsigned Permissions);
182195

196+
bool hasSpace(const MemoryGroup &MemGroup, uintptr_t Size) const;
197+
183198
void anchor() override;
184199

185200
MemoryGroup CodeMem;
186201
MemoryGroup RWDataMem;
187202
MemoryGroup RODataMem;
188203
MemoryMapper *MMapper;
189204
std::unique_ptr<MemoryMapper> OwnedMMapper;
205+
bool ReserveAllocation;
190206
};
191207

192208
} // end namespace llvm

llvm/lib/ExecutionEngine/SectionMemoryManager.cpp

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,97 @@
1818

1919
namespace llvm {
2020

21+
bool SectionMemoryManager::hasSpace(const MemoryGroup &MemGroup,
22+
uintptr_t Size) const {
23+
for (const FreeMemBlock &FreeMB : MemGroup.FreeMem) {
24+
if (FreeMB.Free.allocatedSize() >= Size)
25+
return true;
26+
}
27+
return false;
28+
}
29+
30+
void SectionMemoryManager::reserveAllocationSpace(
31+
uintptr_t CodeSize, Align CodeAlign, uintptr_t RODataSize,
32+
Align RODataAlign, uintptr_t RWDataSize, Align RWDataAlign) {
33+
if (CodeSize == 0 && RODataSize == 0 && RWDataSize == 0)
34+
return;
35+
36+
static const size_t PageSize = sys::Process::getPageSizeEstimate();
37+
38+
// Code alignment needs to be at least the stub alignment - however, we
39+
// don't have an easy way to get that here so as a workaround, we assume
40+
// it's 8, which is the largest value I observed across all platforms.
41+
constexpr uint64_t StubAlign = 8;
42+
CodeAlign = Align(std::max(CodeAlign.value(), StubAlign));
43+
RODataAlign = Align(std::max(RODataAlign.value(), StubAlign));
44+
RWDataAlign = Align(std::max(RWDataAlign.value(), StubAlign));
45+
46+
// Get space required for each section. Use the same calculation as
47+
// allocateSection because we need to be able to satisfy it.
48+
uint64_t RequiredCodeSize = alignTo(CodeSize, CodeAlign) + CodeAlign.value();
49+
uint64_t RequiredRODataSize =
50+
alignTo(RODataSize, RODataAlign) + RODataAlign.value();
51+
uint64_t RequiredRWDataSize =
52+
alignTo(RWDataSize, RWDataAlign) + RWDataAlign.value();
53+
54+
if (hasSpace(CodeMem, RequiredCodeSize) &&
55+
hasSpace(RODataMem, RequiredRODataSize) &&
56+
hasSpace(RWDataMem, RequiredRWDataSize)) {
57+
// Sufficient space in contiguous block already available.
58+
return;
59+
}
60+
61+
// MemoryManager does not have functions for releasing memory after it's
62+
// allocated. Normally it tries to use any excess blocks that were allocated
63+
// due to page alignment, but if we have insufficient free memory for the
64+
// request this can lead to allocating disparate memory that can violate the
65+
// ARM ABI. Clear free memory so only the new allocations are used, but do
66+
// not release allocated memory as it may still be in-use.
67+
CodeMem.FreeMem.clear();
68+
RODataMem.FreeMem.clear();
69+
RWDataMem.FreeMem.clear();
70+
71+
// Round up to the nearest page size. Blocks must be page-aligned.
72+
RequiredCodeSize = alignTo(RequiredCodeSize, PageSize);
73+
RequiredRODataSize = alignTo(RequiredRODataSize, PageSize);
74+
RequiredRWDataSize = alignTo(RequiredRWDataSize, PageSize);
75+
uint64_t RequiredSize =
76+
RequiredCodeSize + RequiredRODataSize + RequiredRWDataSize;
77+
78+
std::error_code ec;
79+
sys::MemoryBlock MB = MMapper->allocateMappedMemory(
80+
AllocationPurpose::RWData, RequiredSize, nullptr,
81+
sys::Memory::MF_READ | sys::Memory::MF_WRITE, ec);
82+
if (ec) {
83+
return;
84+
}
85+
// CodeMem will arbitrarily own this MemoryBlock to handle cleanup.
86+
CodeMem.AllocatedMem.push_back(MB);
87+
uintptr_t Addr = (uintptr_t)MB.base();
88+
FreeMemBlock FreeMB;
89+
FreeMB.PendingPrefixIndex = (unsigned)-1;
90+
91+
if (CodeSize > 0) {
92+
assert(isAddrAligned(CodeAlign, (void *)Addr));
93+
FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredCodeSize);
94+
CodeMem.FreeMem.push_back(FreeMB);
95+
Addr += RequiredCodeSize;
96+
}
97+
98+
if (RODataSize > 0) {
99+
assert(isAddrAligned(RODataAlign, (void *)Addr));
100+
FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRODataSize);
101+
RODataMem.FreeMem.push_back(FreeMB);
102+
Addr += RequiredRODataSize;
103+
}
104+
105+
if (RWDataSize > 0) {
106+
assert(isAddrAligned(RWDataAlign, (void *)Addr));
107+
FreeMB.Free = sys::MemoryBlock((void *)Addr, RequiredRWDataSize);
108+
RWDataMem.FreeMem.push_back(FreeMB);
109+
}
110+
}
111+
21112
uint8_t *SectionMemoryManager::allocateDataSection(uintptr_t Size,
22113
unsigned Alignment,
23114
unsigned SectionID,
@@ -265,8 +356,10 @@ class DefaultMMapper final : public SectionMemoryManager::MemoryMapper {
265356
};
266357
} // namespace
267358

268-
SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM)
269-
: MMapper(UnownedMM), OwnedMMapper(nullptr) {
359+
SectionMemoryManager::SectionMemoryManager(MemoryMapper *UnownedMM,
360+
bool ReserveAlloc)
361+
: MMapper(UnownedMM), OwnedMMapper(nullptr),
362+
ReserveAllocation(ReserveAlloc) {
270363
if (!MMapper) {
271364
OwnedMMapper = std::make_unique<DefaultMMapper>();
272365
MMapper = OwnedMMapper.get();

0 commit comments

Comments
 (0)