xref: /llvm-project/llvm/lib/ExecutionEngine/JITLink/JITLinkMemoryManager.cpp (revision 2ccf7ed277df28651b94bbee9fccefdf22fb074f)
1 //===--- JITLinkMemoryManager.cpp - JITLinkMemoryManager implementation ---===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
10 #include "llvm/ExecutionEngine/JITLink/JITLink.h"
11 #include "llvm/Support/FormatVariadic.h"
12 #include "llvm/Support/Process.h"
13 
14 #define DEBUG_TYPE "jitlink"
15 
16 using namespace llvm;
17 
18 namespace llvm {
19 namespace jitlink {
20 
21 JITLinkMemoryManager::~JITLinkMemoryManager() = default;
22 JITLinkMemoryManager::InFlightAlloc::~InFlightAlloc() = default;
23 
24 BasicLayout::BasicLayout(LinkGraph &G) : G(G) {
25 
26   for (auto &Sec : G.sections()) {
27     // Skip empty sections, and sections with NoAlloc lifetime policies.
28     if (Sec.blocks().empty() ||
29         Sec.getMemLifetime() == orc::MemLifetime::NoAlloc)
30       continue;
31 
32     auto &Seg = Segments[{Sec.getMemProt(), Sec.getMemLifetime()}];
33     for (auto *B : Sec.blocks())
34       if (LLVM_LIKELY(!B->isZeroFill()))
35         Seg.ContentBlocks.push_back(B);
36       else
37         Seg.ZeroFillBlocks.push_back(B);
38   }
39 
40   // Build Segments map.
41   auto CompareBlocks = [](const Block *LHS, const Block *RHS) {
42     // Sort by section, address and size
43     if (LHS->getSection().getOrdinal() != RHS->getSection().getOrdinal())
44       return LHS->getSection().getOrdinal() < RHS->getSection().getOrdinal();
45     if (LHS->getAddress() != RHS->getAddress())
46       return LHS->getAddress() < RHS->getAddress();
47     return LHS->getSize() < RHS->getSize();
48   };
49 
50   LLVM_DEBUG(dbgs() << "Generated BasicLayout for " << G.getName() << ":\n");
51   for (auto &KV : Segments) {
52     auto &Seg = KV.second;
53 
54     llvm::sort(Seg.ContentBlocks, CompareBlocks);
55     llvm::sort(Seg.ZeroFillBlocks, CompareBlocks);
56 
57     for (auto *B : Seg.ContentBlocks) {
58       Seg.ContentSize = alignToBlock(Seg.ContentSize, *B);
59       Seg.ContentSize += B->getSize();
60       Seg.Alignment = std::max(Seg.Alignment, Align(B->getAlignment()));
61     }
62 
63     uint64_t SegEndOffset = Seg.ContentSize;
64     for (auto *B : Seg.ZeroFillBlocks) {
65       SegEndOffset = alignToBlock(SegEndOffset, *B);
66       SegEndOffset += B->getSize();
67       Seg.Alignment = std::max(Seg.Alignment, Align(B->getAlignment()));
68     }
69     Seg.ZeroFillSize = SegEndOffset - Seg.ContentSize;
70 
71     LLVM_DEBUG({
72       dbgs() << "  Seg " << KV.first
73              << ": content-size=" << formatv("{0:x}", Seg.ContentSize)
74              << ", zero-fill-size=" << formatv("{0:x}", Seg.ZeroFillSize)
75              << ", align=" << formatv("{0:x}", Seg.Alignment.value()) << "\n";
76     });
77   }
78 }
79 
80 Expected<BasicLayout::ContiguousPageBasedLayoutSizes>
81 BasicLayout::getContiguousPageBasedLayoutSizes(uint64_t PageSize) {
82   ContiguousPageBasedLayoutSizes SegsSizes;
83 
84   for (auto &KV : segments()) {
85     auto &AG = KV.first;
86     auto &Seg = KV.second;
87 
88     if (Seg.Alignment > PageSize)
89       return make_error<StringError>("Segment alignment greater than page size",
90                                      inconvertibleErrorCode());
91 
92     uint64_t SegSize = alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize);
93     if (AG.getMemLifetime() == orc::MemLifetime::Standard)
94       SegsSizes.StandardSegs += SegSize;
95     else
96       SegsSizes.FinalizeSegs += SegSize;
97   }
98 
99   return SegsSizes;
100 }
101 
102 Error BasicLayout::apply() {
103   for (auto &KV : Segments) {
104     auto &Seg = KV.second;
105 
106     assert(!(Seg.ContentBlocks.empty() && Seg.ZeroFillBlocks.empty()) &&
107            "Empty section recorded?");
108 
109     for (auto *B : Seg.ContentBlocks) {
110       // Align addr and working-mem-offset.
111       Seg.Addr = alignToBlock(Seg.Addr, *B);
112       Seg.NextWorkingMemOffset = alignToBlock(Seg.NextWorkingMemOffset, *B);
113 
114       // Update block addr.
115       B->setAddress(Seg.Addr);
116       Seg.Addr += B->getSize();
117 
118       // Copy content to working memory, then update content to point at working
119       // memory.
120       memcpy(Seg.WorkingMem + Seg.NextWorkingMemOffset, B->getContent().data(),
121              B->getSize());
122       B->setMutableContent(
123           {Seg.WorkingMem + Seg.NextWorkingMemOffset, B->getSize()});
124       Seg.NextWorkingMemOffset += B->getSize();
125     }
126 
127     for (auto *B : Seg.ZeroFillBlocks) {
128       // Align addr.
129       Seg.Addr = alignToBlock(Seg.Addr, *B);
130       // Update block addr.
131       B->setAddress(Seg.Addr);
132       Seg.Addr += B->getSize();
133     }
134 
135     Seg.ContentBlocks.clear();
136     Seg.ZeroFillBlocks.clear();
137   }
138 
139   return Error::success();
140 }
141 
142 orc::shared::AllocActions &BasicLayout::graphAllocActions() {
143   return G.allocActions();
144 }
145 
146 void SimpleSegmentAlloc::Create(JITLinkMemoryManager &MemMgr,
147                                 std::shared_ptr<orc::SymbolStringPool> SSP,
148                                 const JITLinkDylib *JD, SegmentMap Segments,
149                                 OnCreatedFunction OnCreated) {
150 
151   static_assert(orc::AllocGroup::NumGroups == 32,
152                 "AllocGroup has changed. Section names below must be updated");
153   StringRef AGSectionNames[] = {
154       "__---.standard", "__R--.standard", "__-W-.standard", "__RW-.standard",
155       "__--X.standard", "__R-X.standard", "__-WX.standard", "__RWX.standard",
156       "__---.finalize", "__R--.finalize", "__-W-.finalize", "__RW-.finalize",
157       "__--X.finalize", "__R-X.finalize", "__-WX.finalize", "__RWX.finalize"};
158 
159   auto G = std::make_unique<LinkGraph>("", std::move(SSP), Triple(), 0,
160                                        llvm::endianness::native, nullptr);
161   orc::AllocGroupSmallMap<Block *> ContentBlocks;
162 
163   orc::ExecutorAddr NextAddr(0x100000);
164   for (auto &KV : Segments) {
165     auto &AG = KV.first;
166     auto &Seg = KV.second;
167 
168     assert(AG.getMemLifetime() != orc::MemLifetime::NoAlloc &&
169            "NoAlloc segments are not supported by SimpleSegmentAlloc");
170 
171     auto AGSectionName =
172         AGSectionNames[static_cast<unsigned>(AG.getMemProt()) |
173                        static_cast<bool>(AG.getMemLifetime()) << 3];
174 
175     auto &Sec = G->createSection(AGSectionName, AG.getMemProt());
176     Sec.setMemLifetime(AG.getMemLifetime());
177 
178     if (Seg.ContentSize != 0) {
179       NextAddr =
180           orc::ExecutorAddr(alignTo(NextAddr.getValue(), Seg.ContentAlign));
181       auto &B =
182           G->createMutableContentBlock(Sec, G->allocateBuffer(Seg.ContentSize),
183                                        NextAddr, Seg.ContentAlign.value(), 0);
184       ContentBlocks[AG] = &B;
185       NextAddr += Seg.ContentSize;
186     }
187   }
188 
189   // GRef declared separately since order-of-argument-eval isn't specified.
190   auto &GRef = *G;
191   MemMgr.allocate(JD, GRef,
192                   [G = std::move(G), ContentBlocks = std::move(ContentBlocks),
193                    OnCreated = std::move(OnCreated)](
194                       JITLinkMemoryManager::AllocResult Alloc) mutable {
195                     if (!Alloc)
196                       OnCreated(Alloc.takeError());
197                     else
198                       OnCreated(SimpleSegmentAlloc(std::move(G),
199                                                    std::move(ContentBlocks),
200                                                    std::move(*Alloc)));
201                   });
202 }
203 
204 Expected<SimpleSegmentAlloc>
205 SimpleSegmentAlloc::Create(JITLinkMemoryManager &MemMgr,
206                            std::shared_ptr<orc::SymbolStringPool> SSP,
207                            const JITLinkDylib *JD, SegmentMap Segments) {
208   std::promise<MSVCPExpected<SimpleSegmentAlloc>> AllocP;
209   auto AllocF = AllocP.get_future();
210   Create(MemMgr, std::move(SSP), JD, std::move(Segments),
211          [&](Expected<SimpleSegmentAlloc> Result) {
212            AllocP.set_value(std::move(Result));
213          });
214   return AllocF.get();
215 }
216 
217 SimpleSegmentAlloc::SimpleSegmentAlloc(SimpleSegmentAlloc &&) = default;
218 SimpleSegmentAlloc &
219 SimpleSegmentAlloc::operator=(SimpleSegmentAlloc &&) = default;
220 SimpleSegmentAlloc::~SimpleSegmentAlloc() = default;
221 
222 SimpleSegmentAlloc::SegmentInfo
223 SimpleSegmentAlloc::getSegInfo(orc::AllocGroup AG) {
224   auto I = ContentBlocks.find(AG);
225   if (I != ContentBlocks.end()) {
226     auto &B = *I->second;
227     return {B.getAddress(), B.getAlreadyMutableContent()};
228   }
229   return {};
230 }
231 
232 SimpleSegmentAlloc::SimpleSegmentAlloc(
233     std::unique_ptr<LinkGraph> G,
234     orc::AllocGroupSmallMap<Block *> ContentBlocks,
235     std::unique_ptr<JITLinkMemoryManager::InFlightAlloc> Alloc)
236     : G(std::move(G)), ContentBlocks(std::move(ContentBlocks)),
237       Alloc(std::move(Alloc)) {}
238 
239 class InProcessMemoryManager::IPInFlightAlloc
240     : public JITLinkMemoryManager::InFlightAlloc {
241 public:
242   IPInFlightAlloc(InProcessMemoryManager &MemMgr, LinkGraph &G, BasicLayout BL,
243                   sys::MemoryBlock StandardSegments,
244                   sys::MemoryBlock FinalizationSegments)
245       : MemMgr(MemMgr), G(&G), BL(std::move(BL)),
246         StandardSegments(std::move(StandardSegments)),
247         FinalizationSegments(std::move(FinalizationSegments)) {}
248 
249   ~IPInFlightAlloc() {
250     assert(!G && "InFlight alloc neither abandoned nor finalized");
251   }
252 
253   void finalize(OnFinalizedFunction OnFinalized) override {
254 
255     // Apply memory protections to all segments.
256     if (auto Err = applyProtections()) {
257       OnFinalized(std::move(Err));
258       return;
259     }
260 
261     // Run finalization actions.
262     auto DeallocActions = runFinalizeActions(G->allocActions());
263     if (!DeallocActions) {
264       OnFinalized(DeallocActions.takeError());
265       return;
266     }
267 
268     // Release the finalize segments slab.
269     if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments)) {
270       OnFinalized(errorCodeToError(EC));
271       return;
272     }
273 
274 #ifndef NDEBUG
275     // Set 'G' to null to flag that we've been successfully finalized.
276     // This allows us to assert at destruction time that a call has been made
277     // to either finalize or abandon.
278     G = nullptr;
279 #endif
280 
281     // Continue with finalized allocation.
282     OnFinalized(MemMgr.createFinalizedAlloc(std::move(StandardSegments),
283                                             std::move(*DeallocActions)));
284   }
285 
286   void abandon(OnAbandonedFunction OnAbandoned) override {
287     Error Err = Error::success();
288     if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments))
289       Err = joinErrors(std::move(Err), errorCodeToError(EC));
290     if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))
291       Err = joinErrors(std::move(Err), errorCodeToError(EC));
292 
293 #ifndef NDEBUG
294     // Set 'G' to null to flag that we've been successfully finalized.
295     // This allows us to assert at destruction time that a call has been made
296     // to either finalize or abandon.
297     G = nullptr;
298 #endif
299 
300     OnAbandoned(std::move(Err));
301   }
302 
303 private:
304   Error applyProtections() {
305     for (auto &KV : BL.segments()) {
306       const auto &AG = KV.first;
307       auto &Seg = KV.second;
308 
309       auto Prot = toSysMemoryProtectionFlags(AG.getMemProt());
310 
311       uint64_t SegSize =
312           alignTo(Seg.ContentSize + Seg.ZeroFillSize, MemMgr.PageSize);
313       sys::MemoryBlock MB(Seg.WorkingMem, SegSize);
314       if (auto EC = sys::Memory::protectMappedMemory(MB, Prot))
315         return errorCodeToError(EC);
316       if (Prot & sys::Memory::MF_EXEC)
317         sys::Memory::InvalidateInstructionCache(MB.base(), MB.allocatedSize());
318     }
319     return Error::success();
320   }
321 
322   InProcessMemoryManager &MemMgr;
323   LinkGraph *G;
324   BasicLayout BL;
325   sys::MemoryBlock StandardSegments;
326   sys::MemoryBlock FinalizationSegments;
327 };
328 
329 Expected<std::unique_ptr<InProcessMemoryManager>>
330 InProcessMemoryManager::Create() {
331   if (auto PageSize = sys::Process::getPageSize()) {
332     // FIXME: Just check this once on startup.
333     if (!isPowerOf2_64((uint64_t)*PageSize))
334       return make_error<StringError>(
335           "Could not create InProcessMemoryManager: Page size " +
336               Twine(*PageSize) + " is not a power of 2",
337           inconvertibleErrorCode());
338 
339     return std::make_unique<InProcessMemoryManager>(*PageSize);
340   } else
341     return PageSize.takeError();
342 }
343 
344 void InProcessMemoryManager::allocate(const JITLinkDylib *JD, LinkGraph &G,
345                                       OnAllocatedFunction OnAllocated) {
346   BasicLayout BL(G);
347 
348   /// Scan the request and calculate the group and total sizes.
349   /// Check that segment size is no larger than a page.
350   auto SegsSizes = BL.getContiguousPageBasedLayoutSizes(PageSize);
351   if (!SegsSizes) {
352     OnAllocated(SegsSizes.takeError());
353     return;
354   }
355 
356   /// Check that the total size requested (including zero fill) is not larger
357   /// than a size_t.
358   if (SegsSizes->total() > std::numeric_limits<size_t>::max()) {
359     OnAllocated(make_error<JITLinkError>(
360         "Total requested size " + formatv("{0:x}", SegsSizes->total()) +
361         " for graph " + G.getName() + " exceeds address space"));
362     return;
363   }
364 
365   // Allocate one slab for the whole thing (to make sure everything is
366   // in-range), then partition into standard and finalization blocks.
367   //
368   // FIXME: Make two separate allocations in the future to reduce
369   // fragmentation: finalization segments will usually be a single page, and
370   // standard segments are likely to be more than one page. Where multiple
371   // allocations are in-flight at once (likely) the current approach will leave
372   // a lot of single-page holes.
373   sys::MemoryBlock Slab;
374   sys::MemoryBlock StandardSegsMem;
375   sys::MemoryBlock FinalizeSegsMem;
376   {
377     const sys::Memory::ProtectionFlags ReadWrite =
378         static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ |
379                                                   sys::Memory::MF_WRITE);
380 
381     std::error_code EC;
382     Slab = sys::Memory::allocateMappedMemory(SegsSizes->total(), nullptr,
383                                              ReadWrite, EC);
384 
385     if (EC) {
386       OnAllocated(errorCodeToError(EC));
387       return;
388     }
389 
390     // Zero-fill the whole slab up-front.
391     memset(Slab.base(), 0, Slab.allocatedSize());
392 
393     StandardSegsMem = {Slab.base(),
394                        static_cast<size_t>(SegsSizes->StandardSegs)};
395     FinalizeSegsMem = {(void *)((char *)Slab.base() + SegsSizes->StandardSegs),
396                        static_cast<size_t>(SegsSizes->FinalizeSegs)};
397   }
398 
399   auto NextStandardSegAddr = orc::ExecutorAddr::fromPtr(StandardSegsMem.base());
400   auto NextFinalizeSegAddr = orc::ExecutorAddr::fromPtr(FinalizeSegsMem.base());
401 
402   LLVM_DEBUG({
403     dbgs() << "InProcessMemoryManager allocated:\n";
404     if (SegsSizes->StandardSegs)
405       dbgs() << formatv("  [ {0:x16} -- {1:x16} ]", NextStandardSegAddr,
406                         NextStandardSegAddr + StandardSegsMem.allocatedSize())
407              << " to stardard segs\n";
408     else
409       dbgs() << "  no standard segs\n";
410     if (SegsSizes->FinalizeSegs)
411       dbgs() << formatv("  [ {0:x16} -- {1:x16} ]", NextFinalizeSegAddr,
412                         NextFinalizeSegAddr + FinalizeSegsMem.allocatedSize())
413              << " to finalize segs\n";
414     else
415       dbgs() << "  no finalize segs\n";
416   });
417 
418   // Build ProtMap, assign addresses.
419   for (auto &KV : BL.segments()) {
420     auto &AG = KV.first;
421     auto &Seg = KV.second;
422 
423     auto &SegAddr = (AG.getMemLifetime() == orc::MemLifetime::Standard)
424                         ? NextStandardSegAddr
425                         : NextFinalizeSegAddr;
426 
427     Seg.WorkingMem = SegAddr.toPtr<char *>();
428     Seg.Addr = SegAddr;
429 
430     SegAddr += alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize);
431   }
432 
433   if (auto Err = BL.apply()) {
434     OnAllocated(std::move(Err));
435     return;
436   }
437 
438   OnAllocated(std::make_unique<IPInFlightAlloc>(*this, G, std::move(BL),
439                                                 std::move(StandardSegsMem),
440                                                 std::move(FinalizeSegsMem)));
441 }
442 
443 void InProcessMemoryManager::deallocate(std::vector<FinalizedAlloc> Allocs,
444                                         OnDeallocatedFunction OnDeallocated) {
445   std::vector<sys::MemoryBlock> StandardSegmentsList;
446   std::vector<std::vector<orc::shared::WrapperFunctionCall>> DeallocActionsList;
447 
448   {
449     std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
450     for (auto &Alloc : Allocs) {
451       auto *FA = Alloc.release().toPtr<FinalizedAllocInfo *>();
452       StandardSegmentsList.push_back(std::move(FA->StandardSegments));
453       DeallocActionsList.push_back(std::move(FA->DeallocActions));
454       FA->~FinalizedAllocInfo();
455       FinalizedAllocInfos.Deallocate(FA);
456     }
457   }
458 
459   Error DeallocErr = Error::success();
460 
461   while (!DeallocActionsList.empty()) {
462     auto &DeallocActions = DeallocActionsList.back();
463     auto &StandardSegments = StandardSegmentsList.back();
464 
465     /// Run any deallocate calls.
466     while (!DeallocActions.empty()) {
467       if (auto Err = DeallocActions.back().runWithSPSRetErrorMerged())
468         DeallocErr = joinErrors(std::move(DeallocErr), std::move(Err));
469       DeallocActions.pop_back();
470     }
471 
472     /// Release the standard segments slab.
473     if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))
474       DeallocErr = joinErrors(std::move(DeallocErr), errorCodeToError(EC));
475 
476     DeallocActionsList.pop_back();
477     StandardSegmentsList.pop_back();
478   }
479 
480   OnDeallocated(std::move(DeallocErr));
481 }
482 
483 JITLinkMemoryManager::FinalizedAlloc
484 InProcessMemoryManager::createFinalizedAlloc(
485     sys::MemoryBlock StandardSegments,
486     std::vector<orc::shared::WrapperFunctionCall> DeallocActions) {
487   std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
488   auto *FA = FinalizedAllocInfos.Allocate<FinalizedAllocInfo>();
489   new (FA) FinalizedAllocInfo(
490       {std::move(StandardSegments), std::move(DeallocActions)});
491   return FinalizedAlloc(orc::ExecutorAddr::fromPtr(FA));
492 }
493 
494 } // end namespace jitlink
495 } // end namespace llvm
496