The original MemDeallocPolicy had two options:
* Standard: allocated memory lives until deallocated or abandoned.
* Finalize: allocated memory lives until all finalize actions have been run,
then is destroyed.
This patch introduces a new 'NoAlloc' option. NoAlloc indicates that the
section should be ignored by the JITLinkMemoryManager -- the memory manager
should allocate neither working memory nor executor address space to blocks in
NoAlloc sections. The NoAlloc option is intended to support metadata sections
(e.g. debug info) that we want to keep in the graph and have fixed up if
necessary, but don't want allocated or transmitted to the executor (or we want
that allocation and transmission to be managed manually by plugins).
Since NoAlloc blocks are ignored by the JITLinkMemoryManager they will not have
working memory allocated to them by default post-allocation. Clients wishing to
modify the content of a block in a NoAlloc section should call
`Block::getMutableMemory(LinkGraph&)` to get writable memory allocated on the
LinkGraph's allocator (this memory will exist for the lifetime of the graph).
If no client requests mutable memory prior to the fixup phase then the generic
link algorithm will do so when it encounters the first edge in any given block.
Addresses of blocks in NoAlloc sections are initialized by the LinkGraph
creator (a LinkGraphBuilder, if the graph is generated from an object file),
and should not be modified by the JITLinkMemoryManager. Plugins are responsible
for updating addresses if they add/remove content from these sections. The
meaning of addresses in NoAlloc-sections is backend/plugin defined, but for
fixup purposes they will be treated the same as addresses in Standard/Finalize
sections. References from Standard/Finalize sections to NoAlloc sections are
expected to be common (these represent metadata tracking executor addresses).
References from NoAlloc sections to Standard/Finalize sections are expected to
be rare/non-existent (they would represent JIT'd code / data tracking metadata
in the controller, which would be surprising). LinkGraphBuilders and specific
backends may impose additional constraints on edges between Standard/Finalize
and NoAlloc sections where required for correctness.
Differential Revision: https://reviews.llvm.org/D146183
263 lines
9.2 KiB
C++
263 lines
9.2 KiB
C++
//===- SimpleExecuorMemoryManagare.cpp - Simple executor-side memory mgmt -===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ExecutionEngine/Orc/TargetProcess/SimpleExecutorMemoryManager.h"
|
|
|
|
#include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
|
|
#define DEBUG_TYPE "orc"
|
|
|
|
namespace llvm {
|
|
namespace orc {
|
|
namespace rt_bootstrap {
|
|
|
|
SimpleExecutorMemoryManager::~SimpleExecutorMemoryManager() {
|
|
assert(Allocations.empty() && "shutdown not called?");
|
|
}
|
|
|
|
Expected<ExecutorAddr> SimpleExecutorMemoryManager::allocate(uint64_t Size) {
|
|
std::error_code EC;
|
|
auto MB = sys::Memory::allocateMappedMemory(
|
|
Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
|
|
if (EC)
|
|
return errorCodeToError(EC);
|
|
std::lock_guard<std::mutex> Lock(M);
|
|
assert(!Allocations.count(MB.base()) && "Duplicate allocation addr");
|
|
Allocations[MB.base()].Size = Size;
|
|
return ExecutorAddr::fromPtr(MB.base());
|
|
}
|
|
|
|
Error SimpleExecutorMemoryManager::finalize(tpctypes::FinalizeRequest &FR) {
|
|
ExecutorAddr Base(~0ULL);
|
|
std::vector<shared::WrapperFunctionCall> DeallocationActions;
|
|
size_t SuccessfulFinalizationActions = 0;
|
|
|
|
if (FR.Segments.empty()) {
|
|
// NOTE: Finalizing nothing is currently a no-op. Should it be an error?
|
|
if (FR.Actions.empty())
|
|
return Error::success();
|
|
else
|
|
return make_error<StringError>("Finalization actions attached to empty "
|
|
"finalization request",
|
|
inconvertibleErrorCode());
|
|
}
|
|
|
|
for (auto &Seg : FR.Segments)
|
|
Base = std::min(Base, Seg.Addr);
|
|
|
|
for (auto &ActPair : FR.Actions)
|
|
if (ActPair.Dealloc)
|
|
DeallocationActions.push_back(ActPair.Dealloc);
|
|
|
|
// Get the Allocation for this finalization.
|
|
size_t AllocSize = 0;
|
|
{
|
|
std::lock_guard<std::mutex> Lock(M);
|
|
auto I = Allocations.find(Base.toPtr<void *>());
|
|
if (I == Allocations.end())
|
|
return make_error<StringError>("Attempt to finalize unrecognized "
|
|
"allocation " +
|
|
formatv("{0:x}", Base.getValue()),
|
|
inconvertibleErrorCode());
|
|
AllocSize = I->second.Size;
|
|
I->second.DeallocationActions = std::move(DeallocationActions);
|
|
}
|
|
ExecutorAddr AllocEnd = Base + ExecutorAddrDiff(AllocSize);
|
|
|
|
// Bail-out function: this will run deallocation actions corresponding to any
|
|
// completed finalization actions, then deallocate memory.
|
|
auto BailOut = [&](Error Err) {
|
|
std::pair<void *, Allocation> AllocToDestroy;
|
|
|
|
// Get allocation to destory.
|
|
{
|
|
std::lock_guard<std::mutex> Lock(M);
|
|
auto I = Allocations.find(Base.toPtr<void *>());
|
|
|
|
// Check for missing allocation (effective a double free).
|
|
if (I == Allocations.end())
|
|
return joinErrors(
|
|
std::move(Err),
|
|
make_error<StringError>("No allocation entry found "
|
|
"for " +
|
|
formatv("{0:x}", Base.getValue()),
|
|
inconvertibleErrorCode()));
|
|
AllocToDestroy = std::move(*I);
|
|
Allocations.erase(I);
|
|
}
|
|
|
|
// Run deallocation actions for all completed finalization actions.
|
|
while (SuccessfulFinalizationActions)
|
|
Err =
|
|
joinErrors(std::move(Err), FR.Actions[--SuccessfulFinalizationActions]
|
|
.Dealloc.runWithSPSRetErrorMerged());
|
|
|
|
// Deallocate memory.
|
|
sys::MemoryBlock MB(AllocToDestroy.first, AllocToDestroy.second.Size);
|
|
if (auto EC = sys::Memory::releaseMappedMemory(MB))
|
|
Err = joinErrors(std::move(Err), errorCodeToError(EC));
|
|
|
|
return Err;
|
|
};
|
|
|
|
// Copy content and apply permissions.
|
|
for (auto &Seg : FR.Segments) {
|
|
|
|
// Check segment ranges.
|
|
if (LLVM_UNLIKELY(Seg.Size < Seg.Content.size()))
|
|
return BailOut(make_error<StringError>(
|
|
formatv("Segment {0:x} content size ({1:x} bytes) "
|
|
"exceeds segment size ({2:x} bytes)",
|
|
Seg.Addr.getValue(), Seg.Content.size(), Seg.Size),
|
|
inconvertibleErrorCode()));
|
|
ExecutorAddr SegEnd = Seg.Addr + ExecutorAddrDiff(Seg.Size);
|
|
if (LLVM_UNLIKELY(Seg.Addr < Base || SegEnd > AllocEnd))
|
|
return BailOut(make_error<StringError>(
|
|
formatv("Segment {0:x} -- {1:x} crosses boundary of "
|
|
"allocation {2:x} -- {3:x}",
|
|
Seg.Addr.getValue(), SegEnd.getValue(), Base.getValue(),
|
|
AllocEnd.getValue()),
|
|
inconvertibleErrorCode()));
|
|
|
|
char *Mem = Seg.Addr.toPtr<char *>();
|
|
if (!Seg.Content.empty())
|
|
memcpy(Mem, Seg.Content.data(), Seg.Content.size());
|
|
memset(Mem + Seg.Content.size(), 0, Seg.Size - Seg.Content.size());
|
|
assert(Seg.Size <= std::numeric_limits<size_t>::max());
|
|
if (auto EC = sys::Memory::protectMappedMemory(
|
|
{Mem, static_cast<size_t>(Seg.Size)},
|
|
toSysMemoryProtectionFlags(Seg.RAG.Prot)))
|
|
return BailOut(errorCodeToError(EC));
|
|
if ((Seg.RAG.Prot & MemProt::Exec) == MemProt::Exec)
|
|
sys::Memory::InvalidateInstructionCache(Mem, Seg.Size);
|
|
}
|
|
|
|
// Run finalization actions.
|
|
for (auto &ActPair : FR.Actions) {
|
|
if (auto Err = ActPair.Finalize.runWithSPSRetErrorMerged())
|
|
return BailOut(std::move(Err));
|
|
++SuccessfulFinalizationActions;
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error SimpleExecutorMemoryManager::deallocate(
|
|
const std::vector<ExecutorAddr> &Bases) {
|
|
std::vector<std::pair<void *, Allocation>> AllocPairs;
|
|
AllocPairs.reserve(Bases.size());
|
|
|
|
// Get allocation to destory.
|
|
Error Err = Error::success();
|
|
{
|
|
std::lock_guard<std::mutex> Lock(M);
|
|
for (auto &Base : Bases) {
|
|
auto I = Allocations.find(Base.toPtr<void *>());
|
|
|
|
// Check for missing allocation (effective a double free).
|
|
if (I != Allocations.end()) {
|
|
AllocPairs.push_back(std::move(*I));
|
|
Allocations.erase(I);
|
|
} else
|
|
Err = joinErrors(
|
|
std::move(Err),
|
|
make_error<StringError>("No allocation entry found "
|
|
"for " +
|
|
formatv("{0:x}", Base.getValue()),
|
|
inconvertibleErrorCode()));
|
|
}
|
|
}
|
|
|
|
while (!AllocPairs.empty()) {
|
|
auto &P = AllocPairs.back();
|
|
Err = joinErrors(std::move(Err), deallocateImpl(P.first, P.second));
|
|
AllocPairs.pop_back();
|
|
}
|
|
|
|
return Err;
|
|
}
|
|
|
|
Error SimpleExecutorMemoryManager::shutdown() {
|
|
|
|
AllocationsMap AM;
|
|
{
|
|
std::lock_guard<std::mutex> Lock(M);
|
|
AM = std::move(Allocations);
|
|
}
|
|
|
|
Error Err = Error::success();
|
|
for (auto &KV : AM)
|
|
Err = joinErrors(std::move(Err), deallocateImpl(KV.first, KV.second));
|
|
return Err;
|
|
}
|
|
|
|
void SimpleExecutorMemoryManager::addBootstrapSymbols(
|
|
StringMap<ExecutorAddr> &M) {
|
|
M[rt::SimpleExecutorMemoryManagerInstanceName] = ExecutorAddr::fromPtr(this);
|
|
M[rt::SimpleExecutorMemoryManagerReserveWrapperName] =
|
|
ExecutorAddr::fromPtr(&reserveWrapper);
|
|
M[rt::SimpleExecutorMemoryManagerFinalizeWrapperName] =
|
|
ExecutorAddr::fromPtr(&finalizeWrapper);
|
|
M[rt::SimpleExecutorMemoryManagerDeallocateWrapperName] =
|
|
ExecutorAddr::fromPtr(&deallocateWrapper);
|
|
}
|
|
|
|
Error SimpleExecutorMemoryManager::deallocateImpl(void *Base, Allocation &A) {
|
|
Error Err = Error::success();
|
|
|
|
while (!A.DeallocationActions.empty()) {
|
|
Err = joinErrors(std::move(Err),
|
|
A.DeallocationActions.back().runWithSPSRetErrorMerged());
|
|
A.DeallocationActions.pop_back();
|
|
}
|
|
|
|
sys::MemoryBlock MB(Base, A.Size);
|
|
if (auto EC = sys::Memory::releaseMappedMemory(MB))
|
|
Err = joinErrors(std::move(Err), errorCodeToError(EC));
|
|
|
|
return Err;
|
|
}
|
|
|
|
llvm::orc::shared::CWrapperFunctionResult
|
|
SimpleExecutorMemoryManager::reserveWrapper(const char *ArgData,
|
|
size_t ArgSize) {
|
|
return shared::WrapperFunction<
|
|
rt::SPSSimpleExecutorMemoryManagerReserveSignature>::
|
|
handle(ArgData, ArgSize,
|
|
shared::makeMethodWrapperHandler(
|
|
&SimpleExecutorMemoryManager::allocate))
|
|
.release();
|
|
}
|
|
|
|
llvm::orc::shared::CWrapperFunctionResult
|
|
SimpleExecutorMemoryManager::finalizeWrapper(const char *ArgData,
|
|
size_t ArgSize) {
|
|
return shared::WrapperFunction<
|
|
rt::SPSSimpleExecutorMemoryManagerFinalizeSignature>::
|
|
handle(ArgData, ArgSize,
|
|
shared::makeMethodWrapperHandler(
|
|
&SimpleExecutorMemoryManager::finalize))
|
|
.release();
|
|
}
|
|
|
|
llvm::orc::shared::CWrapperFunctionResult
|
|
SimpleExecutorMemoryManager::deallocateWrapper(const char *ArgData,
|
|
size_t ArgSize) {
|
|
return shared::WrapperFunction<
|
|
rt::SPSSimpleExecutorMemoryManagerDeallocateSignature>::
|
|
handle(ArgData, ArgSize,
|
|
shared::makeMethodWrapperHandler(
|
|
&SimpleExecutorMemoryManager::deallocate))
|
|
.release();
|
|
}
|
|
|
|
} // namespace rt_bootstrap
|
|
} // end namespace orc
|
|
} // end namespace llvm
|