xref: /llvm-project/llvm/lib/ExecutionEngine/JITLink/JITLinkMemoryManager.cpp (revision 4eaff6c58ae2f130ac8d63cf2c87bbb483114876)
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                                 Triple TT, const JITLinkDylib *JD,
149                                 SegmentMap Segments,
150                                 OnCreatedFunction OnCreated) {
151 
152   static_assert(orc::AllocGroup::NumGroups == 32,
153                 "AllocGroup has changed. Section names below must be updated");
154   StringRef AGSectionNames[] = {
155       "__---.standard", "__R--.standard", "__-W-.standard", "__RW-.standard",
156       "__--X.standard", "__R-X.standard", "__-WX.standard", "__RWX.standard",
157       "__---.finalize", "__R--.finalize", "__-W-.finalize", "__RW-.finalize",
158       "__--X.finalize", "__R-X.finalize", "__-WX.finalize", "__RWX.finalize"};
159 
160   auto G =
161       std::make_unique<LinkGraph>("", std::move(SSP), std::move(TT),
162                                   SubtargetFeatures(), getGenericEdgeKindName);
163   orc::AllocGroupSmallMap<Block *> ContentBlocks;
164 
165   orc::ExecutorAddr NextAddr(0x100000);
166   for (auto &KV : Segments) {
167     auto &AG = KV.first;
168     auto &Seg = KV.second;
169 
170     assert(AG.getMemLifetime() != orc::MemLifetime::NoAlloc &&
171            "NoAlloc segments are not supported by SimpleSegmentAlloc");
172 
173     auto AGSectionName =
174         AGSectionNames[static_cast<unsigned>(AG.getMemProt()) |
175                        static_cast<bool>(AG.getMemLifetime()) << 3];
176 
177     auto &Sec = G->createSection(AGSectionName, AG.getMemProt());
178     Sec.setMemLifetime(AG.getMemLifetime());
179 
180     if (Seg.ContentSize != 0) {
181       NextAddr =
182           orc::ExecutorAddr(alignTo(NextAddr.getValue(), Seg.ContentAlign));
183       auto &B =
184           G->createMutableContentBlock(Sec, G->allocateBuffer(Seg.ContentSize),
185                                        NextAddr, Seg.ContentAlign.value(), 0);
186       ContentBlocks[AG] = &B;
187       NextAddr += Seg.ContentSize;
188     }
189   }
190 
191   // GRef declared separately since order-of-argument-eval isn't specified.
192   auto &GRef = *G;
193   MemMgr.allocate(JD, GRef,
194                   [G = std::move(G), ContentBlocks = std::move(ContentBlocks),
195                    OnCreated = std::move(OnCreated)](
196                       JITLinkMemoryManager::AllocResult Alloc) mutable {
197                     if (!Alloc)
198                       OnCreated(Alloc.takeError());
199                     else
200                       OnCreated(SimpleSegmentAlloc(std::move(G),
201                                                    std::move(ContentBlocks),
202                                                    std::move(*Alloc)));
203                   });
204 }
205 
206 Expected<SimpleSegmentAlloc> SimpleSegmentAlloc::Create(
207     JITLinkMemoryManager &MemMgr, std::shared_ptr<orc::SymbolStringPool> SSP,
208     Triple TT, const JITLinkDylib *JD, SegmentMap Segments) {
209   std::promise<MSVCPExpected<SimpleSegmentAlloc>> AllocP;
210   auto AllocF = AllocP.get_future();
211   Create(MemMgr, std::move(SSP), std::move(TT), JD, std::move(Segments),
212          [&](Expected<SimpleSegmentAlloc> Result) {
213            AllocP.set_value(std::move(Result));
214          });
215   return AllocF.get();
216 }
217 
218 SimpleSegmentAlloc::SimpleSegmentAlloc(SimpleSegmentAlloc &&) = default;
219 SimpleSegmentAlloc &
220 SimpleSegmentAlloc::operator=(SimpleSegmentAlloc &&) = default;
221 SimpleSegmentAlloc::~SimpleSegmentAlloc() = default;
222 
223 SimpleSegmentAlloc::SegmentInfo
224 SimpleSegmentAlloc::getSegInfo(orc::AllocGroup AG) {
225   auto I = ContentBlocks.find(AG);
226   if (I != ContentBlocks.end()) {
227     auto &B = *I->second;
228     return {B.getAddress(), B.getAlreadyMutableContent()};
229   }
230   return {};
231 }
232 
233 SimpleSegmentAlloc::SimpleSegmentAlloc(
234     std::unique_ptr<LinkGraph> G,
235     orc::AllocGroupSmallMap<Block *> ContentBlocks,
236     std::unique_ptr<JITLinkMemoryManager::InFlightAlloc> Alloc)
237     : G(std::move(G)), ContentBlocks(std::move(ContentBlocks)),
238       Alloc(std::move(Alloc)) {}
239 
240 class InProcessMemoryManager::IPInFlightAlloc
241     : public JITLinkMemoryManager::InFlightAlloc {
242 public:
243   IPInFlightAlloc(InProcessMemoryManager &MemMgr, LinkGraph &G, BasicLayout BL,
244                   sys::MemoryBlock StandardSegments,
245                   sys::MemoryBlock FinalizationSegments)
246       : MemMgr(MemMgr), G(&G), BL(std::move(BL)),
247         StandardSegments(std::move(StandardSegments)),
248         FinalizationSegments(std::move(FinalizationSegments)) {}
249 
250   ~IPInFlightAlloc() {
251     assert(!G && "InFlight alloc neither abandoned nor finalized");
252   }
253 
254   void finalize(OnFinalizedFunction OnFinalized) override {
255 
256     // Apply memory protections to all segments.
257     if (auto Err = applyProtections()) {
258       OnFinalized(std::move(Err));
259       return;
260     }
261 
262     // Run finalization actions.
263     auto DeallocActions = runFinalizeActions(G->allocActions());
264     if (!DeallocActions) {
265       OnFinalized(DeallocActions.takeError());
266       return;
267     }
268 
269     // Release the finalize segments slab.
270     if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments)) {
271       OnFinalized(errorCodeToError(EC));
272       return;
273     }
274 
275 #ifndef NDEBUG
276     // Set 'G' to null to flag that we've been successfully finalized.
277     // This allows us to assert at destruction time that a call has been made
278     // to either finalize or abandon.
279     G = nullptr;
280 #endif
281 
282     // Continue with finalized allocation.
283     OnFinalized(MemMgr.createFinalizedAlloc(std::move(StandardSegments),
284                                             std::move(*DeallocActions)));
285   }
286 
287   void abandon(OnAbandonedFunction OnAbandoned) override {
288     Error Err = Error::success();
289     if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments))
290       Err = joinErrors(std::move(Err), errorCodeToError(EC));
291     if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))
292       Err = joinErrors(std::move(Err), errorCodeToError(EC));
293 
294 #ifndef NDEBUG
295     // Set 'G' to null to flag that we've been successfully finalized.
296     // This allows us to assert at destruction time that a call has been made
297     // to either finalize or abandon.
298     G = nullptr;
299 #endif
300 
301     OnAbandoned(std::move(Err));
302   }
303 
304 private:
305   Error applyProtections() {
306     for (auto &KV : BL.segments()) {
307       const auto &AG = KV.first;
308       auto &Seg = KV.second;
309 
310       auto Prot = toSysMemoryProtectionFlags(AG.getMemProt());
311 
312       uint64_t SegSize =
313           alignTo(Seg.ContentSize + Seg.ZeroFillSize, MemMgr.PageSize);
314       sys::MemoryBlock MB(Seg.WorkingMem, SegSize);
315       if (auto EC = sys::Memory::protectMappedMemory(MB, Prot))
316         return errorCodeToError(EC);
317       if (Prot & sys::Memory::MF_EXEC)
318         sys::Memory::InvalidateInstructionCache(MB.base(), MB.allocatedSize());
319     }
320     return Error::success();
321   }
322 
323   InProcessMemoryManager &MemMgr;
324   LinkGraph *G;
325   BasicLayout BL;
326   sys::MemoryBlock StandardSegments;
327   sys::MemoryBlock FinalizationSegments;
328 };
329 
330 Expected<std::unique_ptr<InProcessMemoryManager>>
331 InProcessMemoryManager::Create() {
332   if (auto PageSize = sys::Process::getPageSize()) {
333     // FIXME: Just check this once on startup.
334     if (!isPowerOf2_64((uint64_t)*PageSize))
335       return make_error<StringError>(
336           "Could not create InProcessMemoryManager: Page size " +
337               Twine(*PageSize) + " is not a power of 2",
338           inconvertibleErrorCode());
339 
340     return std::make_unique<InProcessMemoryManager>(*PageSize);
341   } else
342     return PageSize.takeError();
343 }
344 
345 void InProcessMemoryManager::allocate(const JITLinkDylib *JD, LinkGraph &G,
346                                       OnAllocatedFunction OnAllocated) {
347   BasicLayout BL(G);
348 
349   /// Scan the request and calculate the group and total sizes.
350   /// Check that segment size is no larger than a page.
351   auto SegsSizes = BL.getContiguousPageBasedLayoutSizes(PageSize);
352   if (!SegsSizes) {
353     OnAllocated(SegsSizes.takeError());
354     return;
355   }
356 
357   /// Check that the total size requested (including zero fill) is not larger
358   /// than a size_t.
359   if (SegsSizes->total() > std::numeric_limits<size_t>::max()) {
360     OnAllocated(make_error<JITLinkError>(
361         "Total requested size " + formatv("{0:x}", SegsSizes->total()) +
362         " for graph " + G.getName() + " exceeds address space"));
363     return;
364   }
365 
366   // Allocate one slab for the whole thing (to make sure everything is
367   // in-range), then partition into standard and finalization blocks.
368   //
369   // FIXME: Make two separate allocations in the future to reduce
370   // fragmentation: finalization segments will usually be a single page, and
371   // standard segments are likely to be more than one page. Where multiple
372   // allocations are in-flight at once (likely) the current approach will leave
373   // a lot of single-page holes.
374   sys::MemoryBlock Slab;
375   sys::MemoryBlock StandardSegsMem;
376   sys::MemoryBlock FinalizeSegsMem;
377   {
378     const sys::Memory::ProtectionFlags ReadWrite =
379         static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ |
380                                                   sys::Memory::MF_WRITE);
381 
382     std::error_code EC;
383     Slab = sys::Memory::allocateMappedMemory(SegsSizes->total(), nullptr,
384                                              ReadWrite, EC);
385 
386     if (EC) {
387       OnAllocated(errorCodeToError(EC));
388       return;
389     }
390 
391     // Zero-fill the whole slab up-front.
392     memset(Slab.base(), 0, Slab.allocatedSize());
393 
394     StandardSegsMem = {Slab.base(),
395                        static_cast<size_t>(SegsSizes->StandardSegs)};
396     FinalizeSegsMem = {(void *)((char *)Slab.base() + SegsSizes->StandardSegs),
397                        static_cast<size_t>(SegsSizes->FinalizeSegs)};
398   }
399 
400   auto NextStandardSegAddr = orc::ExecutorAddr::fromPtr(StandardSegsMem.base());
401   auto NextFinalizeSegAddr = orc::ExecutorAddr::fromPtr(FinalizeSegsMem.base());
402 
403   LLVM_DEBUG({
404     dbgs() << "InProcessMemoryManager allocated:\n";
405     if (SegsSizes->StandardSegs)
406       dbgs() << formatv("  [ {0:x16} -- {1:x16} ]", NextStandardSegAddr,
407                         NextStandardSegAddr + StandardSegsMem.allocatedSize())
408              << " to stardard segs\n";
409     else
410       dbgs() << "  no standard segs\n";
411     if (SegsSizes->FinalizeSegs)
412       dbgs() << formatv("  [ {0:x16} -- {1:x16} ]", NextFinalizeSegAddr,
413                         NextFinalizeSegAddr + FinalizeSegsMem.allocatedSize())
414              << " to finalize segs\n";
415     else
416       dbgs() << "  no finalize segs\n";
417   });
418 
419   // Build ProtMap, assign addresses.
420   for (auto &KV : BL.segments()) {
421     auto &AG = KV.first;
422     auto &Seg = KV.second;
423 
424     auto &SegAddr = (AG.getMemLifetime() == orc::MemLifetime::Standard)
425                         ? NextStandardSegAddr
426                         : NextFinalizeSegAddr;
427 
428     Seg.WorkingMem = SegAddr.toPtr<char *>();
429     Seg.Addr = SegAddr;
430 
431     SegAddr += alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize);
432   }
433 
434   if (auto Err = BL.apply()) {
435     OnAllocated(std::move(Err));
436     return;
437   }
438 
439   OnAllocated(std::make_unique<IPInFlightAlloc>(*this, G, std::move(BL),
440                                                 std::move(StandardSegsMem),
441                                                 std::move(FinalizeSegsMem)));
442 }
443 
444 void InProcessMemoryManager::deallocate(std::vector<FinalizedAlloc> Allocs,
445                                         OnDeallocatedFunction OnDeallocated) {
446   std::vector<sys::MemoryBlock> StandardSegmentsList;
447   std::vector<std::vector<orc::shared::WrapperFunctionCall>> DeallocActionsList;
448 
449   {
450     std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
451     for (auto &Alloc : Allocs) {
452       auto *FA = Alloc.release().toPtr<FinalizedAllocInfo *>();
453       StandardSegmentsList.push_back(std::move(FA->StandardSegments));
454       DeallocActionsList.push_back(std::move(FA->DeallocActions));
455       FA->~FinalizedAllocInfo();
456       FinalizedAllocInfos.Deallocate(FA);
457     }
458   }
459 
460   Error DeallocErr = Error::success();
461 
462   while (!DeallocActionsList.empty()) {
463     auto &DeallocActions = DeallocActionsList.back();
464     auto &StandardSegments = StandardSegmentsList.back();
465 
466     /// Run any deallocate calls.
467     while (!DeallocActions.empty()) {
468       if (auto Err = DeallocActions.back().runWithSPSRetErrorMerged())
469         DeallocErr = joinErrors(std::move(DeallocErr), std::move(Err));
470       DeallocActions.pop_back();
471     }
472 
473     /// Release the standard segments slab.
474     if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))
475       DeallocErr = joinErrors(std::move(DeallocErr), errorCodeToError(EC));
476 
477     DeallocActionsList.pop_back();
478     StandardSegmentsList.pop_back();
479   }
480 
481   OnDeallocated(std::move(DeallocErr));
482 }
483 
484 JITLinkMemoryManager::FinalizedAlloc
485 InProcessMemoryManager::createFinalizedAlloc(
486     sys::MemoryBlock StandardSegments,
487     std::vector<orc::shared::WrapperFunctionCall> DeallocActions) {
488   std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
489   auto *FA = FinalizedAllocInfos.Allocate<FinalizedAllocInfo>();
490   new (FA) FinalizedAllocInfo(
491       {std::move(StandardSegments), std::move(DeallocActions)});
492   return FinalizedAlloc(orc::ExecutorAddr::fromPtr(FA));
493 }
494 
495 } // end namespace jitlink
496 } // end namespace llvm
497