Path: blob/main/contrib/llvm-project/llvm/lib/ExecutionEngine/JITLink/JITLinkMemoryManager.cpp
35271 views
//===--- JITLinkMemoryManager.cpp - JITLinkMemoryManager implementation ---===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"9#include "llvm/ExecutionEngine/JITLink/JITLink.h"10#include "llvm/Support/FormatVariadic.h"11#include "llvm/Support/Process.h"1213#define DEBUG_TYPE "jitlink"1415using namespace llvm;1617namespace llvm {18namespace jitlink {1920JITLinkMemoryManager::~JITLinkMemoryManager() = default;21JITLinkMemoryManager::InFlightAlloc::~InFlightAlloc() = default;2223BasicLayout::BasicLayout(LinkGraph &G) : G(G) {2425for (auto &Sec : G.sections()) {26// Skip empty sections, and sections with NoAlloc lifetime policies.27if (Sec.blocks().empty() ||28Sec.getMemLifetime() == orc::MemLifetime::NoAlloc)29continue;3031auto &Seg = Segments[{Sec.getMemProt(), Sec.getMemLifetime()}];32for (auto *B : Sec.blocks())33if (LLVM_LIKELY(!B->isZeroFill()))34Seg.ContentBlocks.push_back(B);35else36Seg.ZeroFillBlocks.push_back(B);37}3839// Build Segments map.40auto CompareBlocks = [](const Block *LHS, const Block *RHS) {41// Sort by section, address and size42if (LHS->getSection().getOrdinal() != RHS->getSection().getOrdinal())43return LHS->getSection().getOrdinal() < RHS->getSection().getOrdinal();44if (LHS->getAddress() != RHS->getAddress())45return LHS->getAddress() < RHS->getAddress();46return LHS->getSize() < RHS->getSize();47};4849LLVM_DEBUG(dbgs() << "Generated BasicLayout for " << G.getName() << ":\n");50for (auto &KV : Segments) {51auto &Seg = KV.second;5253llvm::sort(Seg.ContentBlocks, CompareBlocks);54llvm::sort(Seg.ZeroFillBlocks, CompareBlocks);5556for (auto *B : Seg.ContentBlocks) {57Seg.ContentSize = alignToBlock(Seg.ContentSize, *B);58Seg.ContentSize += B->getSize();59Seg.Alignment = std::max(Seg.Alignment, Align(B->getAlignment()));60}6162uint64_t SegEndOffset = Seg.ContentSize;63for (auto *B : Seg.ZeroFillBlocks) {64SegEndOffset = alignToBlock(SegEndOffset, *B);65SegEndOffset += B->getSize();66Seg.Alignment = std::max(Seg.Alignment, Align(B->getAlignment()));67}68Seg.ZeroFillSize = SegEndOffset - Seg.ContentSize;6970LLVM_DEBUG({71dbgs() << " Seg " << KV.first72<< ": content-size=" << formatv("{0:x}", Seg.ContentSize)73<< ", zero-fill-size=" << formatv("{0:x}", Seg.ZeroFillSize)74<< ", align=" << formatv("{0:x}", Seg.Alignment.value()) << "\n";75});76}77}7879Expected<BasicLayout::ContiguousPageBasedLayoutSizes>80BasicLayout::getContiguousPageBasedLayoutSizes(uint64_t PageSize) {81ContiguousPageBasedLayoutSizes SegsSizes;8283for (auto &KV : segments()) {84auto &AG = KV.first;85auto &Seg = KV.second;8687if (Seg.Alignment > PageSize)88return make_error<StringError>("Segment alignment greater than page size",89inconvertibleErrorCode());9091uint64_t SegSize = alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize);92if (AG.getMemLifetime() == orc::MemLifetime::Standard)93SegsSizes.StandardSegs += SegSize;94else95SegsSizes.FinalizeSegs += SegSize;96}9798return SegsSizes;99}100101Error BasicLayout::apply() {102for (auto &KV : Segments) {103auto &Seg = KV.second;104105assert(!(Seg.ContentBlocks.empty() && Seg.ZeroFillBlocks.empty()) &&106"Empty section recorded?");107108for (auto *B : Seg.ContentBlocks) {109// Align addr and working-mem-offset.110Seg.Addr = alignToBlock(Seg.Addr, *B);111Seg.NextWorkingMemOffset = alignToBlock(Seg.NextWorkingMemOffset, *B);112113// Update block addr.114B->setAddress(Seg.Addr);115Seg.Addr += B->getSize();116117// Copy content to working memory, then update content to point at working118// memory.119memcpy(Seg.WorkingMem + Seg.NextWorkingMemOffset, B->getContent().data(),120B->getSize());121B->setMutableContent(122{Seg.WorkingMem + Seg.NextWorkingMemOffset, B->getSize()});123Seg.NextWorkingMemOffset += B->getSize();124}125126for (auto *B : Seg.ZeroFillBlocks) {127// Align addr.128Seg.Addr = alignToBlock(Seg.Addr, *B);129// Update block addr.130B->setAddress(Seg.Addr);131Seg.Addr += B->getSize();132}133134Seg.ContentBlocks.clear();135Seg.ZeroFillBlocks.clear();136}137138return Error::success();139}140141orc::shared::AllocActions &BasicLayout::graphAllocActions() {142return G.allocActions();143}144145void SimpleSegmentAlloc::Create(JITLinkMemoryManager &MemMgr,146const JITLinkDylib *JD, SegmentMap Segments,147OnCreatedFunction OnCreated) {148149static_assert(orc::AllocGroup::NumGroups == 32,150"AllocGroup has changed. Section names below must be updated");151StringRef AGSectionNames[] = {152"__---.standard", "__R--.standard", "__-W-.standard", "__RW-.standard",153"__--X.standard", "__R-X.standard", "__-WX.standard", "__RWX.standard",154"__---.finalize", "__R--.finalize", "__-W-.finalize", "__RW-.finalize",155"__--X.finalize", "__R-X.finalize", "__-WX.finalize", "__RWX.finalize"};156157auto G = std::make_unique<LinkGraph>("", Triple(), 0,158llvm::endianness::native, nullptr);159orc::AllocGroupSmallMap<Block *> ContentBlocks;160161orc::ExecutorAddr NextAddr(0x100000);162for (auto &KV : Segments) {163auto &AG = KV.first;164auto &Seg = KV.second;165166assert(AG.getMemLifetime() != orc::MemLifetime::NoAlloc &&167"NoAlloc segments are not supported by SimpleSegmentAlloc");168169auto AGSectionName =170AGSectionNames[static_cast<unsigned>(AG.getMemProt()) |171static_cast<bool>(AG.getMemLifetime()) << 3];172173auto &Sec = G->createSection(AGSectionName, AG.getMemProt());174Sec.setMemLifetime(AG.getMemLifetime());175176if (Seg.ContentSize != 0) {177NextAddr =178orc::ExecutorAddr(alignTo(NextAddr.getValue(), Seg.ContentAlign));179auto &B =180G->createMutableContentBlock(Sec, G->allocateBuffer(Seg.ContentSize),181NextAddr, Seg.ContentAlign.value(), 0);182ContentBlocks[AG] = &B;183NextAddr += Seg.ContentSize;184}185}186187// GRef declared separately since order-of-argument-eval isn't specified.188auto &GRef = *G;189MemMgr.allocate(JD, GRef,190[G = std::move(G), ContentBlocks = std::move(ContentBlocks),191OnCreated = std::move(OnCreated)](192JITLinkMemoryManager::AllocResult Alloc) mutable {193if (!Alloc)194OnCreated(Alloc.takeError());195else196OnCreated(SimpleSegmentAlloc(std::move(G),197std::move(ContentBlocks),198std::move(*Alloc)));199});200}201202Expected<SimpleSegmentAlloc>203SimpleSegmentAlloc::Create(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD,204SegmentMap Segments) {205std::promise<MSVCPExpected<SimpleSegmentAlloc>> AllocP;206auto AllocF = AllocP.get_future();207Create(MemMgr, JD, std::move(Segments),208[&](Expected<SimpleSegmentAlloc> Result) {209AllocP.set_value(std::move(Result));210});211return AllocF.get();212}213214SimpleSegmentAlloc::SimpleSegmentAlloc(SimpleSegmentAlloc &&) = default;215SimpleSegmentAlloc &216SimpleSegmentAlloc::operator=(SimpleSegmentAlloc &&) = default;217SimpleSegmentAlloc::~SimpleSegmentAlloc() = default;218219SimpleSegmentAlloc::SegmentInfo220SimpleSegmentAlloc::getSegInfo(orc::AllocGroup AG) {221auto I = ContentBlocks.find(AG);222if (I != ContentBlocks.end()) {223auto &B = *I->second;224return {B.getAddress(), B.getAlreadyMutableContent()};225}226return {};227}228229SimpleSegmentAlloc::SimpleSegmentAlloc(230std::unique_ptr<LinkGraph> G,231orc::AllocGroupSmallMap<Block *> ContentBlocks,232std::unique_ptr<JITLinkMemoryManager::InFlightAlloc> Alloc)233: G(std::move(G)), ContentBlocks(std::move(ContentBlocks)),234Alloc(std::move(Alloc)) {}235236class InProcessMemoryManager::IPInFlightAlloc237: public JITLinkMemoryManager::InFlightAlloc {238public:239IPInFlightAlloc(InProcessMemoryManager &MemMgr, LinkGraph &G, BasicLayout BL,240sys::MemoryBlock StandardSegments,241sys::MemoryBlock FinalizationSegments)242: MemMgr(MemMgr), G(&G), BL(std::move(BL)),243StandardSegments(std::move(StandardSegments)),244FinalizationSegments(std::move(FinalizationSegments)) {}245246~IPInFlightAlloc() {247assert(!G && "InFlight alloc neither abandoned nor finalized");248}249250void finalize(OnFinalizedFunction OnFinalized) override {251252// Apply memory protections to all segments.253if (auto Err = applyProtections()) {254OnFinalized(std::move(Err));255return;256}257258// Run finalization actions.259auto DeallocActions = runFinalizeActions(G->allocActions());260if (!DeallocActions) {261OnFinalized(DeallocActions.takeError());262return;263}264265// Release the finalize segments slab.266if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments)) {267OnFinalized(errorCodeToError(EC));268return;269}270271#ifndef NDEBUG272// Set 'G' to null to flag that we've been successfully finalized.273// This allows us to assert at destruction time that a call has been made274// to either finalize or abandon.275G = nullptr;276#endif277278// Continue with finalized allocation.279OnFinalized(MemMgr.createFinalizedAlloc(std::move(StandardSegments),280std::move(*DeallocActions)));281}282283void abandon(OnAbandonedFunction OnAbandoned) override {284Error Err = Error::success();285if (auto EC = sys::Memory::releaseMappedMemory(FinalizationSegments))286Err = joinErrors(std::move(Err), errorCodeToError(EC));287if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))288Err = joinErrors(std::move(Err), errorCodeToError(EC));289290#ifndef NDEBUG291// Set 'G' to null to flag that we've been successfully finalized.292// This allows us to assert at destruction time that a call has been made293// to either finalize or abandon.294G = nullptr;295#endif296297OnAbandoned(std::move(Err));298}299300private:301Error applyProtections() {302for (auto &KV : BL.segments()) {303const auto &AG = KV.first;304auto &Seg = KV.second;305306auto Prot = toSysMemoryProtectionFlags(AG.getMemProt());307308uint64_t SegSize =309alignTo(Seg.ContentSize + Seg.ZeroFillSize, MemMgr.PageSize);310sys::MemoryBlock MB(Seg.WorkingMem, SegSize);311if (auto EC = sys::Memory::protectMappedMemory(MB, Prot))312return errorCodeToError(EC);313if (Prot & sys::Memory::MF_EXEC)314sys::Memory::InvalidateInstructionCache(MB.base(), MB.allocatedSize());315}316return Error::success();317}318319InProcessMemoryManager &MemMgr;320LinkGraph *G;321BasicLayout BL;322sys::MemoryBlock StandardSegments;323sys::MemoryBlock FinalizationSegments;324};325326Expected<std::unique_ptr<InProcessMemoryManager>>327InProcessMemoryManager::Create() {328if (auto PageSize = sys::Process::getPageSize())329return std::make_unique<InProcessMemoryManager>(*PageSize);330else331return PageSize.takeError();332}333334void InProcessMemoryManager::allocate(const JITLinkDylib *JD, LinkGraph &G,335OnAllocatedFunction OnAllocated) {336337// FIXME: Just check this once on startup.338if (!isPowerOf2_64((uint64_t)PageSize)) {339OnAllocated(make_error<StringError>("Page size is not a power of 2",340inconvertibleErrorCode()));341return;342}343344BasicLayout BL(G);345346/// Scan the request and calculate the group and total sizes.347/// Check that segment size is no larger than a page.348auto SegsSizes = BL.getContiguousPageBasedLayoutSizes(PageSize);349if (!SegsSizes) {350OnAllocated(SegsSizes.takeError());351return;352}353354/// Check that the total size requested (including zero fill) is not larger355/// than a size_t.356if (SegsSizes->total() > std::numeric_limits<size_t>::max()) {357OnAllocated(make_error<JITLinkError>(358"Total requested size " + formatv("{0:x}", SegsSizes->total()) +359" for graph " + G.getName() + " exceeds address space"));360return;361}362363// Allocate one slab for the whole thing (to make sure everything is364// in-range), then partition into standard and finalization blocks.365//366// FIXME: Make two separate allocations in the future to reduce367// fragmentation: finalization segments will usually be a single page, and368// standard segments are likely to be more than one page. Where multiple369// allocations are in-flight at once (likely) the current approach will leave370// a lot of single-page holes.371sys::MemoryBlock Slab;372sys::MemoryBlock StandardSegsMem;373sys::MemoryBlock FinalizeSegsMem;374{375const sys::Memory::ProtectionFlags ReadWrite =376static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ |377sys::Memory::MF_WRITE);378379std::error_code EC;380Slab = sys::Memory::allocateMappedMemory(SegsSizes->total(), nullptr,381ReadWrite, EC);382383if (EC) {384OnAllocated(errorCodeToError(EC));385return;386}387388// Zero-fill the whole slab up-front.389memset(Slab.base(), 0, Slab.allocatedSize());390391StandardSegsMem = {Slab.base(),392static_cast<size_t>(SegsSizes->StandardSegs)};393FinalizeSegsMem = {(void *)((char *)Slab.base() + SegsSizes->StandardSegs),394static_cast<size_t>(SegsSizes->FinalizeSegs)};395}396397auto NextStandardSegAddr = orc::ExecutorAddr::fromPtr(StandardSegsMem.base());398auto NextFinalizeSegAddr = orc::ExecutorAddr::fromPtr(FinalizeSegsMem.base());399400LLVM_DEBUG({401dbgs() << "InProcessMemoryManager allocated:\n";402if (SegsSizes->StandardSegs)403dbgs() << formatv(" [ {0:x16} -- {1:x16} ]", NextStandardSegAddr,404NextStandardSegAddr + StandardSegsMem.allocatedSize())405<< " to stardard segs\n";406else407dbgs() << " no standard segs\n";408if (SegsSizes->FinalizeSegs)409dbgs() << formatv(" [ {0:x16} -- {1:x16} ]", NextFinalizeSegAddr,410NextFinalizeSegAddr + FinalizeSegsMem.allocatedSize())411<< " to finalize segs\n";412else413dbgs() << " no finalize segs\n";414});415416// Build ProtMap, assign addresses.417for (auto &KV : BL.segments()) {418auto &AG = KV.first;419auto &Seg = KV.second;420421auto &SegAddr = (AG.getMemLifetime() == orc::MemLifetime::Standard)422? NextStandardSegAddr423: NextFinalizeSegAddr;424425Seg.WorkingMem = SegAddr.toPtr<char *>();426Seg.Addr = SegAddr;427428SegAddr += alignTo(Seg.ContentSize + Seg.ZeroFillSize, PageSize);429}430431if (auto Err = BL.apply()) {432OnAllocated(std::move(Err));433return;434}435436OnAllocated(std::make_unique<IPInFlightAlloc>(*this, G, std::move(BL),437std::move(StandardSegsMem),438std::move(FinalizeSegsMem)));439}440441void InProcessMemoryManager::deallocate(std::vector<FinalizedAlloc> Allocs,442OnDeallocatedFunction OnDeallocated) {443std::vector<sys::MemoryBlock> StandardSegmentsList;444std::vector<std::vector<orc::shared::WrapperFunctionCall>> DeallocActionsList;445446{447std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);448for (auto &Alloc : Allocs) {449auto *FA = Alloc.release().toPtr<FinalizedAllocInfo *>();450StandardSegmentsList.push_back(std::move(FA->StandardSegments));451DeallocActionsList.push_back(std::move(FA->DeallocActions));452FA->~FinalizedAllocInfo();453FinalizedAllocInfos.Deallocate(FA);454}455}456457Error DeallocErr = Error::success();458459while (!DeallocActionsList.empty()) {460auto &DeallocActions = DeallocActionsList.back();461auto &StandardSegments = StandardSegmentsList.back();462463/// Run any deallocate calls.464while (!DeallocActions.empty()) {465if (auto Err = DeallocActions.back().runWithSPSRetErrorMerged())466DeallocErr = joinErrors(std::move(DeallocErr), std::move(Err));467DeallocActions.pop_back();468}469470/// Release the standard segments slab.471if (auto EC = sys::Memory::releaseMappedMemory(StandardSegments))472DeallocErr = joinErrors(std::move(DeallocErr), errorCodeToError(EC));473474DeallocActionsList.pop_back();475StandardSegmentsList.pop_back();476}477478OnDeallocated(std::move(DeallocErr));479}480481JITLinkMemoryManager::FinalizedAlloc482InProcessMemoryManager::createFinalizedAlloc(483sys::MemoryBlock StandardSegments,484std::vector<orc::shared::WrapperFunctionCall> DeallocActions) {485std::lock_guard<std::mutex> Lock(FinalizedAllocsMutex);486auto *FA = FinalizedAllocInfos.Allocate<FinalizedAllocInfo>();487new (FA) FinalizedAllocInfo(488{std::move(StandardSegments), std::move(DeallocActions)});489return FinalizedAlloc(orc::ExecutorAddr::fromPtr(FA));490}491492} // end namespace jitlink493} // end namespace llvm494495496