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(x: B); |
36 | else |
37 | Seg.ZeroFillBlocks.push_back(x: 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(C&: Seg.ContentBlocks, Comp: CompareBlocks); |
55 | llvm::sort(C&: Seg.ZeroFillBlocks, Comp: CompareBlocks); |
56 | |
57 | for (auto *B : Seg.ContentBlocks) { |
58 | Seg.ContentSize = alignToBlock(Addr: Seg.ContentSize, B: *B); |
59 | Seg.ContentSize += B->getSize(); |
60 | Seg.Alignment = std::max(a: Seg.Alignment, b: Align(B->getAlignment())); |
61 | } |
62 | |
63 | uint64_t SegEndOffset = Seg.ContentSize; |
64 | for (auto *B : Seg.ZeroFillBlocks) { |
65 | SegEndOffset = alignToBlock(Addr: SegEndOffset, B: *B); |
66 | SegEndOffset += B->getSize(); |
67 | Seg.Alignment = std::max(a: Seg.Alignment, b: 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>(Args: "Segment alignment greater than page size" , |
90 | Args: inconvertibleErrorCode()); |
91 | |
92 | uint64_t SegSize = alignTo(Value: Seg.ContentSize + Seg.ZeroFillSize, Align: 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(Addr: Seg.Addr, B: *B); |
112 | Seg.NextWorkingMemOffset = alignToBlock(Addr: Seg.NextWorkingMemOffset, B: *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(dest: Seg.WorkingMem + Seg.NextWorkingMemOffset, src: B->getContent().data(), |
121 | n: 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(Addr: Seg.Addr, B: *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>(args: "" , args: std::move(SSP), args: std::move(TT), |
162 | args: SubtargetFeatures(), args&: 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(Name: AGSectionName, Prot: AG.getMemProt()); |
178 | Sec.setMemLifetime(AG.getMemLifetime()); |
179 | |
180 | if (Seg.ContentSize != 0) { |
181 | NextAddr = |
182 | orc::ExecutorAddr(alignTo(Size: NextAddr.getValue(), A: Seg.ContentAlign)); |
183 | auto &B = |
184 | G->createMutableContentBlock(Parent&: Sec, MutableContent: G->allocateBuffer(Size: Seg.ContentSize), |
185 | Address: NextAddr, Alignment: Seg.ContentAlign.value(), AlignmentOffset: 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, G&: GRef, |
194 | OnAllocated: [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, SSP: std::move(SSP), TT: std::move(TT), JD, Segments: std::move(Segments), |
212 | OnCreated: [&](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(G: AG); |
226 | if (I != ContentBlocks.end()) { |
227 | auto &B = *I->second; |
228 | return {.Addr: B.getAddress(), .WorkingMem: 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 | AAs&: G->allocActions(), |
266 | OnComplete: [this, OnFinalized = std::move(OnFinalized)]( |
267 | Expected<std::vector<WrapperFunctionCall>> DeallocActions) mutable { |
268 | completeFinalization(OnFinalized: std::move(OnFinalized), |
269 | DeallocActions: std::move(DeallocActions)); |
270 | }); |
271 | } |
272 | |
273 | void abandon(OnAbandonedFunction OnAbandoned) override { |
274 | Error Err = Error::success(); |
275 | if (auto EC = sys::Memory::releaseMappedMemory(Block&: FinalizationSegments)) |
276 | Err = joinErrors(E1: std::move(Err), E2: errorCodeToError(EC)); |
277 | if (auto EC = sys::Memory::releaseMappedMemory(Block&: StandardSegments)) |
278 | Err = joinErrors(E1: std::move(Err), E2: 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(Block&: 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(StandardSegments: std::move(StandardSegments), |
313 | DeallocActions: 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(MP: AG.getMemProt()); |
322 | |
323 | uint64_t SegSize = |
324 | alignTo(Value: Seg.ContentSize + Seg.ZeroFillSize, Align: MemMgr.PageSize); |
325 | sys::MemoryBlock MB(Seg.WorkingMem, SegSize); |
326 | if (auto EC = sys::Memory::protectMappedMemory(Block: MB, Flags: Prot)) |
327 | return errorCodeToError(EC); |
328 | if (Prot & sys::Memory::MF_EXEC) |
329 | sys::Memory::InvalidateInstructionCache(Addr: MB.base(), Len: 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(Value: (uint64_t)*PageSize)) |
346 | return make_error<StringError>( |
347 | Args: "Could not create InProcessMemoryManager: Page size " + |
348 | Twine(*PageSize) + " is not a power of 2" , |
349 | Args: inconvertibleErrorCode()); |
350 | |
351 | return std::make_unique<InProcessMemoryManager>(args&: *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 | Args: "Total requested size " + formatv(Fmt: "{0:x}" , Vals: 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(NumBytes: SegsSizes->total(), NearBlock: nullptr, |
395 | Flags: ReadWrite, EC); |
396 | |
397 | if (EC) { |
398 | OnAllocated(errorCodeToError(EC)); |
399 | return; |
400 | } |
401 | |
402 | // Zero-fill the whole slab up-front. |
403 | memset(s: Slab.base(), c: 0, n: 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(Ptr: StandardSegsMem.base()); |
412 | auto NextFinalizeSegAddr = orc::ExecutorAddr::fromPtr(Ptr: 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(Value: Seg.ContentSize + Seg.ZeroFillSize, Align: PageSize); |
443 | } |
444 | |
445 | if (auto Err = BL.apply()) { |
446 | OnAllocated(std::move(Err)); |
447 | return; |
448 | } |
449 | |
450 | OnAllocated(std::make_unique<IPInFlightAlloc>(args&: *this, args&: G, args: std::move(BL), |
451 | args: std::move(StandardSegsMem), |
452 | args: 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(x: std::move(FA->StandardSegments)); |
465 | DeallocActionsList.push_back(x: std::move(FA->DeallocActions)); |
466 | FA->~FinalizedAllocInfo(); |
467 | FinalizedAllocInfos.Deallocate(E: 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(E1: std::move(DeallocErr), E2: std::move(Err)); |
481 | DeallocActions.pop_back(); |
482 | } |
483 | |
484 | /// Release the standard segments slab. |
485 | if (auto EC = sys::Memory::releaseMappedMemory(Block&: StandardSegments)) |
486 | DeallocErr = joinErrors(E1: std::move(DeallocErr), E2: 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 | {.StandardSegments: std::move(StandardSegments), .DeallocActions: std::move(DeallocActions)}); |
503 | return FinalizedAlloc(orc::ExecutorAddr::fromPtr(Ptr: FA)); |
504 | } |
505 | |
506 | } // end namespace jitlink |
507 | } // end namespace llvm |
508 | |