xref: /freebsd/contrib/llvm-project/llvm/lib/ExecutionEngine/JITLink/JITLinkMemoryManager.cpp (revision 770cf0a5f02dc8983a89c6568d741fbc25baa999)
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     using WrapperFunctionCall = orc::shared::WrapperFunctionCall;
264     runFinalizeActions(
265         G->allocActions(),
266         [this, OnFinalized = std::move(OnFinalized)](
267             Expected<std::vector<WrapperFunctionCall>> DeallocActions) mutable {
268           completeFinalization(std::move(OnFinalized),
269                                std::move(DeallocActions));
270         });
271   }
272 
273   void abandon(OnAbandonedFunction OnAbandoned) override {
274     Error Err = Error::success();
275     if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments))
276       Err = joinErrors(std::move(Err), errorCodeToError(EC));
277     if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))
278       Err = joinErrors(std::move(Err), errorCodeToError(EC));
279 
280 #ifndef NDEBUG
281     // Set 'G' to null to flag that we've been successfully finalized.
282     // This allows us to assert at destruction time that a call has been made
283     // to either finalize or abandon.
284     G = nullptr;
285 #endif
286 
287     OnAbandoned(std::move(Err));
288   }
289 
290 private:
291   void completeFinalization(
292       OnFinalizedFunction OnFinalized,
293       Expected<std::vector<orc::shared::WrapperFunctionCall>> DeallocActions) {
294 
295     if (!DeallocActions)
296       return OnFinalized(DeallocActions.takeError());
297 
298     // Release the finalize segments slab.
299     if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments)) {
300       OnFinalized(errorCodeToError(EC));
301       return;
302     }
303 
304 #ifndef NDEBUG
305     // Set 'G' to null to flag that we've been successfully finalized.
306     // This allows us to assert at destruction time that a call has been made
307     // to either finalize or abandon.
308     G = nullptr;
309 #endif
310 
311     // Continue with finalized allocation.
312     OnFinalized(MemMgr.createFinalizedAlloc(std::move(StandardSegments),
313                                             std::move(*DeallocActions)));
314   }
315 
316   Error applyProtections() {
317     for (auto &KV : BL.segments()) {
318       const auto &AG = KV.first;
319       auto &Seg = KV.second;
320 
321       auto Prot = toSysMemoryProtectionFlags(AG.getMemProt());
322 
323       uint64_t SegSize =
324           alignTo(Seg.ContentSize + Seg.ZeroFillSize, MemMgr.PageSize);
325       sys::MemoryBlock MB(Seg.WorkingMem, SegSize);
326       if (auto EC = sys::Memory::protectMappedMemory(MB, Prot))
327         return errorCodeToError(EC);
328       if (Prot & sys::Memory::MF_EXEC)
329         sys::Memory::InvalidateInstructionCache(MB.base(), MB.allocatedSize());
330     }
331     return Error::success();
332   }
333 
334   InProcessMemoryManager &MemMgr;
335   LinkGraph *G;
336   BasicLayout BL;
337   sys::MemoryBlock StandardSegments;
338   sys::MemoryBlock FinalizationSegments;
339 };
340 
341 Expected<std::unique_ptr<InProcessMemoryManager>>
342 InProcessMemoryManager::Create() {
343   if (auto PageSize = sys::Process::getPageSize()) {
344     // FIXME: Just check this once on startup.
345     if (!isPowerOf2_64((uint64_t)*PageSize))
346       return make_error<StringError>(
347           "Could not create InProcessMemoryManager: Page size " +
348               Twine(*PageSize) + " is not a power of 2",
349           inconvertibleErrorCode());
350 
351     return std::make_unique<InProcessMemoryManager>(*PageSize);
352   } else
353     return PageSize.takeError();
354 }
355 
356 void InProcessMemoryManager::allocate(const JITLinkDylib *JD, LinkGraph &G,
357                                       OnAllocatedFunction OnAllocated) {
358   BasicLayout BL(G);
359 
360   /// Scan the request and calculate the group and total sizes.
361   /// Check that segment size is no larger than a page.
362   auto SegsSizes = BL.getContiguousPageBasedLayoutSizes(PageSize);
363   if (!SegsSizes) {
364     OnAllocated(SegsSizes.takeError());
365     return;
366   }
367 
368   /// Check that the total size requested (including zero fill) is not larger
369   /// than a size_t.
370   if (SegsSizes->total() > std::numeric_limits<size_t>::max()) {
371     OnAllocated(make_error<JITLinkError>(
372         "Total requested size " + formatv("{0:x}", SegsSizes->total()) +
373         " for graph " + G.getName() + " exceeds address space"));
374     return;
375   }
376 
377   // Allocate one slab for the whole thing (to make sure everything is
378   // in-range), then partition into standard and finalization blocks.
379   //
380   // FIXME: Make two separate allocations in the future to reduce
381   // fragmentation: finalization segments will usually be a single page, and
382   // standard segments are likely to be more than one page. Where multiple
383   // allocations are in-flight at once (likely) the current approach will leave
384   // a lot of single-page holes.
385   sys::MemoryBlock Slab;
386   sys::MemoryBlock StandardSegsMem;
387   sys::MemoryBlock FinalizeSegsMem;
388   {
389     const sys::Memory::ProtectionFlags ReadWrite =
390         static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ |
391                                                   sys::Memory::MF_WRITE);
392 
393     std::error_code EC;
394     Slab = sys::Memory::allocateMappedMemory(SegsSizes->total(), nullptr,
395                                              ReadWrite, EC);
396 
397     if (EC) {
398       OnAllocated(errorCodeToError(EC));
399       return;
400     }
401 
402     // Zero-fill the whole slab up-front.
403     memset(Slab.base(), 0, Slab.allocatedSize());
404 
405     StandardSegsMem = {Slab.base(),
406                        static_cast<size_t>(SegsSizes->StandardSegs)};
407     FinalizeSegsMem = {(void *)((char *)Slab.base() + SegsSizes->StandardSegs),
408                        static_cast<size_t>(SegsSizes->FinalizeSegs)};
409   }
410 
411   auto NextStandardSegAddr = orc::ExecutorAddr::fromPtr(StandardSegsMem.base());
412   auto NextFinalizeSegAddr = orc::ExecutorAddr::fromPtr(FinalizeSegsMem.base());
413 
414   LLVM_DEBUG({
415     dbgs() << "InProcessMemoryManager allocated:\n";
416     if (SegsSizes->StandardSegs)
417       dbgs() << formatv("  [ {0:x16} -- {1:x16} ]", NextStandardSegAddr,
418                         NextStandardSegAddr + StandardSegsMem.allocatedSize())
419              << " to stardard segs\n";
420     else
421       dbgs() << "  no standard segs\n";
422     if (SegsSizes->FinalizeSegs)
423       dbgs() << formatv("  [ {0:x16} -- {1:x16} ]", NextFinalizeSegAddr,
424                         NextFinalizeSegAddr + FinalizeSegsMem.allocatedSize())
425              << " to finalize segs\n";
426     else
427       dbgs() << "  no finalize segs\n";
428   });
429 
430   // Build ProtMap, assign addresses.
431   for (auto &KV : BL.segments()) {
432     auto &AG = KV.first;
433     auto &Seg = KV.second;
434 
435     auto &SegAddr = (AG.getMemLifetime() == orc::MemLifetime::Standard)
436                         ? NextStandardSegAddr
437                         : NextFinalizeSegAddr;
438 
439     Seg.WorkingMem = SegAddr.toPtr<char *>();
440     Seg.Addr = SegAddr;
441 
442     SegAddr += alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize);
443   }
444 
445   if (auto Err = BL.apply()) {
446     OnAllocated(std::move(Err));
447     return;
448   }
449 
450   OnAllocated(std::make_unique<IPInFlightAlloc>(*this, G, std::move(BL),
451                                                 std::move(StandardSegsMem),
452                                                 std::move(FinalizeSegsMem)));
453 }
454 
455 void InProcessMemoryManager::deallocate(std::vector<FinalizedAlloc> Allocs,
456                                         OnDeallocatedFunction OnDeallocated) {
457   std::vector<sys::MemoryBlock> StandardSegmentsList;
458   std::vector<std::vector<orc::shared::WrapperFunctionCall>> DeallocActionsList;
459 
460   {
461     std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
462     for (auto &Alloc : Allocs) {
463       auto *FA = Alloc.release().toPtr<FinalizedAllocInfo *>();
464       StandardSegmentsList.push_back(std::move(FA->StandardSegments));
465       DeallocActionsList.push_back(std::move(FA->DeallocActions));
466       FA->~FinalizedAllocInfo();
467       FinalizedAllocInfos.Deallocate(FA);
468     }
469   }
470 
471   Error DeallocErr = Error::success();
472 
473   while (!DeallocActionsList.empty()) {
474     auto &DeallocActions = DeallocActionsList.back();
475     auto &StandardSegments = StandardSegmentsList.back();
476 
477     /// Run any deallocate calls.
478     while (!DeallocActions.empty()) {
479       if (auto Err = DeallocActions.back().runWithSPSRetErrorMerged())
480         DeallocErr = joinErrors(std::move(DeallocErr), std::move(Err));
481       DeallocActions.pop_back();
482     }
483 
484     /// Release the standard segments slab.
485     if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))
486       DeallocErr = joinErrors(std::move(DeallocErr), errorCodeToError(EC));
487 
488     DeallocActionsList.pop_back();
489     StandardSegmentsList.pop_back();
490   }
491 
492   OnDeallocated(std::move(DeallocErr));
493 }
494 
495 JITLinkMemoryManager::FinalizedAlloc
496 InProcessMemoryManager::createFinalizedAlloc(
497     sys::MemoryBlock StandardSegments,
498     std::vector<orc::shared::WrapperFunctionCall> DeallocActions) {
499   std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);
500   auto *FA = FinalizedAllocInfos.Allocate<FinalizedAllocInfo>();
501   new (FA) FinalizedAllocInfo(
502       {std::move(StandardSegments), std::move(DeallocActions)});
503   return FinalizedAlloc(orc::ExecutorAddr::fromPtr(FA));
504 }
505 
506 } // end namespace jitlink
507 } // end namespace llvm
508