
Removes the MaterializationResponsibility::addDependencies and addDependenciesForAll methods, and transfers dependency registration to the notifyEmitted operation. The new dependency registration allows dependencies to be specified for arbitrary subsets of the MaterializationResponsibility's symbols (rather than just single symbols or all symbols) via an array of SymbolDependenceGroups (pairs of symbol sets and corresponding dependencies for that set). This patch aims to both improve emission performance and simplify dependence tracking. By eliminating some states (e.g. symbols having registered dependencies but not yet being resolved or emitted) we make some errors impossible by construction, and reduce the number of error cases that we need to check. NonOwningSymbolStringPtrs are used for dependence tracking under the session lock, which should reduce ref-counting operations, and intra-emit dependencies are resolved outside the session lock, which should provide better performance when JITing concurrently (since some dependence tracking can happen in parallel). The Orc C API is updated to account for this change, with the LLVMOrcMaterializationResponsibilityNotifyEmitted API being modified and the LLVMOrcMaterializationResponsibilityAddDependencies and LLVMOrcMaterializationResponsibilityAddDependenciesForAll operations being removed.
453 lines
16 KiB
C++
453 lines
16 KiB
C++
//===------ ResourceTrackerTest.cpp - Unit tests ResourceTracker API ------===//
|
|
//
|
|
// 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 "OrcTestCommon.h"
|
|
#include "llvm/ADT/FunctionExtras.h"
|
|
#include "llvm/Config/llvm-config.h"
|
|
#include "llvm/ExecutionEngine/Orc/Core.h"
|
|
#include "llvm/ExecutionEngine/Orc/Shared/OrcError.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::orc;
|
|
|
|
class ResourceTrackerStandardTest : public CoreAPIsBasedStandardTest {};
|
|
|
|
namespace {
|
|
|
|
template <typename ResourceT = unsigned>
|
|
class SimpleResourceManager : public ResourceManager {
|
|
public:
|
|
using HandleRemoveFunction =
|
|
unique_function<Error(JITDylib &JD, ResourceKey)>;
|
|
|
|
using HandleTransferFunction =
|
|
unique_function<void(JITDylib &JD, ResourceKey, ResourceKey)>;
|
|
|
|
using RecordedResourcesMap = DenseMap<ResourceKey, ResourceT>;
|
|
|
|
SimpleResourceManager(ExecutionSession &ES) : ES(ES) {
|
|
HandleRemove = [&](JITDylib &JD, ResourceKey K) -> Error {
|
|
ES.runSessionLocked([&] { removeResource(JD, K); });
|
|
return Error::success();
|
|
};
|
|
|
|
HandleTransfer = [this](JITDylib &JD, ResourceKey DstKey,
|
|
ResourceKey SrcKey) {
|
|
transferResources(JD, DstKey, SrcKey);
|
|
};
|
|
|
|
ES.registerResourceManager(*this);
|
|
}
|
|
|
|
SimpleResourceManager(const SimpleResourceManager &) = delete;
|
|
SimpleResourceManager &operator=(const SimpleResourceManager &) = delete;
|
|
SimpleResourceManager(SimpleResourceManager &&) = delete;
|
|
SimpleResourceManager &operator=(SimpleResourceManager &&) = delete;
|
|
|
|
~SimpleResourceManager() { ES.deregisterResourceManager(*this); }
|
|
|
|
/// Set the HandleRemove function object.
|
|
void setHandleRemove(HandleRemoveFunction HandleRemove) {
|
|
this->HandleRemove = std::move(HandleRemove);
|
|
}
|
|
|
|
/// Set the HandleTransfer function object.
|
|
void setHandleTransfer(HandleTransferFunction HandleTransfer) {
|
|
this->HandleTransfer = std::move(HandleTransfer);
|
|
}
|
|
|
|
/// Create an association between the given key and resource.
|
|
template <typename MergeOp = std::plus<ResourceT>>
|
|
void recordResource(ResourceKey K, ResourceT Val = ResourceT(),
|
|
MergeOp Merge = MergeOp()) {
|
|
auto Tmp = std::move(Resources[K]);
|
|
Resources[K] = Merge(std::move(Tmp), std::move(Val));
|
|
}
|
|
|
|
/// Remove the resource associated with K from the map if present.
|
|
void removeResource(JITDylib &JD, ResourceKey K) { Resources.erase(K); }
|
|
|
|
/// Transfer resources from DstKey to SrcKey.
|
|
template <typename MergeOp = std::plus<ResourceT>>
|
|
void transferResources(JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey,
|
|
MergeOp Merge = MergeOp()) {
|
|
auto &DstResourceRef = Resources[DstKey];
|
|
ResourceT DstResources;
|
|
std::swap(DstResourceRef, DstResources);
|
|
|
|
auto SI = Resources.find(SrcKey);
|
|
assert(SI != Resources.end() && "No resource associated with SrcKey");
|
|
|
|
DstResourceRef = Merge(std::move(DstResources), std::move(SI->second));
|
|
Resources.erase(SI);
|
|
}
|
|
|
|
/// Return a reference to the Resources map.
|
|
RecordedResourcesMap &getRecordedResources() { return Resources; }
|
|
const RecordedResourcesMap &getRecordedResources() const { return Resources; }
|
|
|
|
Error handleRemoveResources(JITDylib &JD, ResourceKey K) override {
|
|
return HandleRemove(JD, K);
|
|
}
|
|
|
|
void handleTransferResources(JITDylib &JD, ResourceKey DstKey,
|
|
ResourceKey SrcKey) override {
|
|
HandleTransfer(JD, DstKey, SrcKey);
|
|
}
|
|
|
|
static void transferNotAllowed(ResourceKey DstKey, ResourceKey SrcKey) {
|
|
llvm_unreachable("Resource transfer not allowed");
|
|
}
|
|
|
|
private:
|
|
ExecutionSession &ES;
|
|
HandleRemoveFunction HandleRemove;
|
|
HandleTransferFunction HandleTransfer;
|
|
RecordedResourcesMap Resources;
|
|
};
|
|
|
|
TEST_F(ResourceTrackerStandardTest,
|
|
BasicDefineAndRemoveAllBeforeMaterializing) {
|
|
|
|
bool ResourceManagerGotRemove = false;
|
|
SimpleResourceManager<> SRM(ES);
|
|
SRM.setHandleRemove([&](JITDylib &JD, ResourceKey K) -> Error {
|
|
ResourceManagerGotRemove = true;
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 0U)
|
|
<< "Unexpected resources recorded";
|
|
SRM.removeResource(JD, K);
|
|
return Error::success();
|
|
});
|
|
|
|
bool MaterializationUnitDestroyed = false;
|
|
auto MU = std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Foo, FooSym.getFlags()}}),
|
|
[&](std::unique_ptr<MaterializationResponsibility> R) {
|
|
llvm_unreachable("Never called");
|
|
},
|
|
nullptr, SimpleMaterializationUnit::DiscardFunction(),
|
|
[&]() { MaterializationUnitDestroyed = true; });
|
|
|
|
auto RT = JD.createResourceTracker();
|
|
cantFail(JD.define(std::move(MU), RT));
|
|
cantFail(RT->remove());
|
|
auto SymFlags = cantFail(ES.lookupFlags(
|
|
LookupKind::Static,
|
|
{{&JD, JITDylibLookupFlags::MatchExportedSymbolsOnly}},
|
|
SymbolLookupSet(Foo, SymbolLookupFlags::WeaklyReferencedSymbol)));
|
|
|
|
EXPECT_EQ(SymFlags.size(), 0U)
|
|
<< "Symbols should have been removed from the symbol table";
|
|
EXPECT_TRUE(ResourceManagerGotRemove)
|
|
<< "ResourceManager did not receive handleRemoveResources";
|
|
EXPECT_TRUE(MaterializationUnitDestroyed)
|
|
<< "MaterializationUnit not destroyed in response to removal";
|
|
}
|
|
|
|
TEST_F(ResourceTrackerStandardTest, BasicDefineAndRemoveAllAfterMaterializing) {
|
|
|
|
bool ResourceManagerGotRemove = false;
|
|
SimpleResourceManager<> SRM(ES);
|
|
SRM.setHandleRemove([&](JITDylib &JD, ResourceKey K) -> Error {
|
|
ResourceManagerGotRemove = true;
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 1U)
|
|
<< "Unexpected number of resources recorded";
|
|
EXPECT_EQ(SRM.getRecordedResources().count(K), 1U)
|
|
<< "Unexpected recorded resource";
|
|
SRM.removeResource(JD, K);
|
|
return Error::success();
|
|
});
|
|
|
|
auto MU = std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Foo, FooSym.getFlags()}}),
|
|
[&](std::unique_ptr<MaterializationResponsibility> R) {
|
|
cantFail(R->withResourceKeyDo(
|
|
[&](ResourceKey K) { SRM.recordResource(K); }));
|
|
cantFail(R->notifyResolved({{Foo, FooSym}}));
|
|
cantFail(R->notifyEmitted({}));
|
|
});
|
|
|
|
auto RT = JD.createResourceTracker();
|
|
cantFail(JD.define(std::move(MU), RT));
|
|
cantFail(ES.lookup({&JD}, Foo));
|
|
cantFail(RT->remove());
|
|
auto SymFlags = cantFail(ES.lookupFlags(
|
|
LookupKind::Static,
|
|
{{&JD, JITDylibLookupFlags::MatchExportedSymbolsOnly}},
|
|
SymbolLookupSet(Foo, SymbolLookupFlags::WeaklyReferencedSymbol)));
|
|
|
|
EXPECT_EQ(SymFlags.size(), 0U)
|
|
<< "Symbols should have been removed from the symbol table";
|
|
EXPECT_TRUE(ResourceManagerGotRemove)
|
|
<< "ResourceManager did not receive handleRemoveResources";
|
|
}
|
|
|
|
TEST_F(ResourceTrackerStandardTest, BasicDefineAndRemoveAllWhileMaterializing) {
|
|
|
|
bool ResourceManagerGotRemove = false;
|
|
SimpleResourceManager<> SRM(ES);
|
|
SRM.setHandleRemove([&](JITDylib &JD, ResourceKey K) -> Error {
|
|
ResourceManagerGotRemove = true;
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 0U)
|
|
<< "Unexpected resources recorded";
|
|
SRM.removeResource(JD, K);
|
|
return Error::success();
|
|
});
|
|
|
|
std::unique_ptr<MaterializationResponsibility> MR;
|
|
auto MU = std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Foo, FooSym.getFlags()}}),
|
|
[&](std::unique_ptr<MaterializationResponsibility> R) {
|
|
MR = std::move(R);
|
|
});
|
|
|
|
auto RT = JD.createResourceTracker();
|
|
cantFail(JD.define(std::move(MU), RT));
|
|
|
|
ES.lookup(
|
|
LookupKind::Static, makeJITDylibSearchOrder(&JD), SymbolLookupSet(Foo),
|
|
SymbolState::Ready,
|
|
[](Expected<SymbolMap> Result) {
|
|
EXPECT_THAT_EXPECTED(Result, Failed<FailedToMaterialize>())
|
|
<< "Lookup failed unexpectedly";
|
|
},
|
|
NoDependenciesToRegister);
|
|
|
|
cantFail(RT->remove());
|
|
auto SymFlags = cantFail(ES.lookupFlags(
|
|
LookupKind::Static,
|
|
{{&JD, JITDylibLookupFlags::MatchExportedSymbolsOnly}},
|
|
SymbolLookupSet(Foo, SymbolLookupFlags::WeaklyReferencedSymbol)));
|
|
|
|
EXPECT_EQ(SymFlags.size(), 0U)
|
|
<< "Symbols should have been removed from the symbol table";
|
|
EXPECT_TRUE(ResourceManagerGotRemove)
|
|
<< "ResourceManager did not receive handleRemoveResources";
|
|
|
|
EXPECT_THAT_ERROR(MR->withResourceKeyDo([](ResourceKey K) {
|
|
ADD_FAILURE() << "Should not reach withResourceKeyDo body for removed key";
|
|
}),
|
|
Failed<ResourceTrackerDefunct>())
|
|
<< "withResourceKeyDo on MR with removed tracker should have failed";
|
|
EXPECT_THAT_ERROR(MR->notifyResolved({{Foo, FooSym}}),
|
|
Failed<ResourceTrackerDefunct>())
|
|
<< "notifyResolved on MR with removed tracker should have failed";
|
|
|
|
MR->failMaterialization();
|
|
}
|
|
|
|
TEST_F(ResourceTrackerStandardTest, JITDylibClear) {
|
|
SimpleResourceManager<> SRM(ES);
|
|
|
|
// Add materializer for Foo.
|
|
cantFail(JD.define(std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Foo, FooSym.getFlags()}}),
|
|
[&](std::unique_ptr<MaterializationResponsibility> R) {
|
|
cantFail(R->withResourceKeyDo(
|
|
[&](ResourceKey K) { ++SRM.getRecordedResources()[K]; }));
|
|
cantFail(R->notifyResolved({{Foo, FooSym}}));
|
|
cantFail(R->notifyEmitted({}));
|
|
})));
|
|
|
|
// Add materializer for Bar.
|
|
cantFail(JD.define(std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Bar, BarSym.getFlags()}}),
|
|
[&](std::unique_ptr<MaterializationResponsibility> R) {
|
|
cantFail(R->withResourceKeyDo(
|
|
[&](ResourceKey K) { ++SRM.getRecordedResources()[K]; }));
|
|
cantFail(R->notifyResolved({{Bar, BarSym}}));
|
|
cantFail(R->notifyEmitted({}));
|
|
})));
|
|
|
|
EXPECT_TRUE(SRM.getRecordedResources().empty())
|
|
<< "Expected no resources recorded yet.";
|
|
|
|
cantFail(
|
|
ES.lookup(makeJITDylibSearchOrder(&JD), SymbolLookupSet({Foo, Bar})));
|
|
|
|
auto JDResourceKey = JD.getDefaultResourceTracker()->getKeyUnsafe();
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 1U)
|
|
<< "Expected exactly one entry (for JD's ResourceKey)";
|
|
EXPECT_EQ(SRM.getRecordedResources().count(JDResourceKey), 1U)
|
|
<< "Expected an entry for JD's ResourceKey";
|
|
EXPECT_EQ(SRM.getRecordedResources()[JDResourceKey], 2U)
|
|
<< "Expected value of 2 for JD's ResourceKey "
|
|
"(+1 for each of Foo and Bar)";
|
|
|
|
cantFail(JD.clear());
|
|
|
|
EXPECT_TRUE(SRM.getRecordedResources().empty())
|
|
<< "Expected no resources recorded after clear";
|
|
}
|
|
|
|
TEST_F(ResourceTrackerStandardTest,
|
|
BasicDefineAndExplicitTransferBeforeMaterializing) {
|
|
|
|
bool ResourceManagerGotTransfer = false;
|
|
SimpleResourceManager<> SRM(ES);
|
|
SRM.setHandleTransfer(
|
|
[&](JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) {
|
|
ResourceManagerGotTransfer = true;
|
|
auto &RR = SRM.getRecordedResources();
|
|
EXPECT_EQ(RR.size(), 0U) << "Expected no resources recorded yet";
|
|
});
|
|
|
|
auto MakeMU = [&](SymbolStringPtr Name, ExecutorSymbolDef Sym) {
|
|
return std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Name, Sym.getFlags()}}),
|
|
[=, &SRM](std::unique_ptr<MaterializationResponsibility> R) {
|
|
cantFail(R->withResourceKeyDo(
|
|
[&](ResourceKey K) { SRM.recordResource(K); }));
|
|
cantFail(R->notifyResolved({{Name, Sym}}));
|
|
cantFail(R->notifyEmitted({}));
|
|
});
|
|
};
|
|
|
|
auto FooRT = JD.createResourceTracker();
|
|
cantFail(JD.define(MakeMU(Foo, FooSym), FooRT));
|
|
|
|
auto BarRT = JD.createResourceTracker();
|
|
cantFail(JD.define(MakeMU(Bar, BarSym), BarRT));
|
|
|
|
BarRT->transferTo(*FooRT);
|
|
|
|
EXPECT_TRUE(ResourceManagerGotTransfer)
|
|
<< "ResourceManager did not receive transfer";
|
|
EXPECT_TRUE(BarRT->isDefunct()) << "BarRT should now be defunct";
|
|
|
|
cantFail(
|
|
ES.lookup(makeJITDylibSearchOrder({&JD}), SymbolLookupSet({Foo, Bar})));
|
|
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 1U)
|
|
<< "Expected exactly one entry (for FooRT's Key)";
|
|
EXPECT_EQ(SRM.getRecordedResources().count(FooRT->getKeyUnsafe()), 1U)
|
|
<< "Expected an entry for FooRT's ResourceKey";
|
|
EXPECT_EQ(SRM.getRecordedResources().count(BarRT->getKeyUnsafe()), 0U)
|
|
<< "Expected no entry for BarRT's ResourceKey";
|
|
|
|
// We need to explicitly destroy FooRT or its resources will be implicitly
|
|
// transferred to the default tracker triggering a second call to our
|
|
// transfer function above (which expects only one call).
|
|
cantFail(FooRT->remove());
|
|
}
|
|
|
|
TEST_F(ResourceTrackerStandardTest,
|
|
BasicDefineAndExplicitTransferAfterMaterializing) {
|
|
|
|
bool ResourceManagerGotTransfer = false;
|
|
SimpleResourceManager<> SRM(ES);
|
|
SRM.setHandleTransfer(
|
|
[&](JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) {
|
|
ResourceManagerGotTransfer = true;
|
|
SRM.transferResources(JD, DstKey, SrcKey);
|
|
});
|
|
|
|
auto MakeMU = [&](SymbolStringPtr Name, ExecutorSymbolDef Sym) {
|
|
return std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Name, Sym.getFlags()}}),
|
|
[=, &SRM](std::unique_ptr<MaterializationResponsibility> R) {
|
|
cantFail(R->withResourceKeyDo(
|
|
[&](ResourceKey K) { SRM.recordResource(K, 1); }));
|
|
cantFail(R->notifyResolved({{Name, Sym}}));
|
|
cantFail(R->notifyEmitted({}));
|
|
});
|
|
};
|
|
|
|
auto FooRT = JD.createResourceTracker();
|
|
cantFail(JD.define(MakeMU(Foo, FooSym), FooRT));
|
|
|
|
auto BarRT = JD.createResourceTracker();
|
|
cantFail(JD.define(MakeMU(Bar, BarSym), BarRT));
|
|
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 0U)
|
|
<< "Expected no recorded resources yet";
|
|
|
|
cantFail(
|
|
ES.lookup(makeJITDylibSearchOrder({&JD}), SymbolLookupSet({Foo, Bar})));
|
|
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 2U)
|
|
<< "Expected recorded resources for both Foo and Bar";
|
|
|
|
BarRT->transferTo(*FooRT);
|
|
|
|
EXPECT_TRUE(ResourceManagerGotTransfer)
|
|
<< "ResourceManager did not receive transfer";
|
|
EXPECT_TRUE(BarRT->isDefunct()) << "BarRT should now be defunct";
|
|
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 1U)
|
|
<< "Expected recorded resources for Foo only";
|
|
EXPECT_EQ(SRM.getRecordedResources().count(FooRT->getKeyUnsafe()), 1U)
|
|
<< "Expected recorded resources for Foo";
|
|
EXPECT_EQ(SRM.getRecordedResources()[FooRT->getKeyUnsafe()], 2U)
|
|
<< "Expected resources value for for Foo to be '2'";
|
|
}
|
|
|
|
TEST_F(ResourceTrackerStandardTest,
|
|
BasicDefineAndExplicitTransferWhileMaterializing) {
|
|
|
|
bool ResourceManagerGotTransfer = false;
|
|
SimpleResourceManager<> SRM(ES);
|
|
SRM.setHandleTransfer(
|
|
[&](JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) {
|
|
ResourceManagerGotTransfer = true;
|
|
SRM.transferResources(JD, DstKey, SrcKey);
|
|
});
|
|
|
|
auto FooRT = JD.createResourceTracker();
|
|
std::unique_ptr<MaterializationResponsibility> FooMR;
|
|
cantFail(JD.define(std::make_unique<SimpleMaterializationUnit>(
|
|
SymbolFlagsMap({{Foo, FooSym.getFlags()}}),
|
|
[&](std::unique_ptr<MaterializationResponsibility> R) {
|
|
FooMR = std::move(R);
|
|
}),
|
|
FooRT));
|
|
|
|
auto BarRT = JD.createResourceTracker();
|
|
|
|
ES.lookup(
|
|
LookupKind::Static, makeJITDylibSearchOrder(&JD), SymbolLookupSet(Foo),
|
|
SymbolState::Ready,
|
|
[](Expected<SymbolMap> Result) { cantFail(Result.takeError()); },
|
|
NoDependenciesToRegister);
|
|
|
|
cantFail(FooMR->withResourceKeyDo([&](ResourceKey K) {
|
|
EXPECT_EQ(FooRT->getKeyUnsafe(), K)
|
|
<< "Expected FooRT's ResourceKey for Foo here";
|
|
SRM.recordResource(K, 1);
|
|
}));
|
|
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 1U)
|
|
<< "Expected one recorded resource here";
|
|
EXPECT_EQ(SRM.getRecordedResources()[FooRT->getKeyUnsafe()], 1U)
|
|
<< "Expected Resource value for FooRT to be '1' here";
|
|
|
|
FooRT->transferTo(*BarRT);
|
|
|
|
EXPECT_TRUE(ResourceManagerGotTransfer)
|
|
<< "Expected resource manager to receive handleTransferResources call";
|
|
|
|
cantFail(FooMR->withResourceKeyDo([&](ResourceKey K) {
|
|
EXPECT_EQ(BarRT->getKeyUnsafe(), K)
|
|
<< "Expected BarRT's ResourceKey for Foo here";
|
|
SRM.recordResource(K, 1);
|
|
}));
|
|
|
|
EXPECT_EQ(SRM.getRecordedResources().size(), 1U)
|
|
<< "Expected one recorded resource here";
|
|
EXPECT_EQ(SRM.getRecordedResources().count(BarRT->getKeyUnsafe()), 1U)
|
|
<< "Expected RecordedResources to contain an entry for BarRT";
|
|
EXPECT_EQ(SRM.getRecordedResources()[BarRT->getKeyUnsafe()], 2U)
|
|
<< "Expected Resource value for BarRT to be '2' here";
|
|
|
|
cantFail(FooMR->notifyResolved({{Foo, FooSym}}));
|
|
cantFail(FooMR->notifyEmitted({}));
|
|
}
|
|
|
|
} // namespace
|