blob: 40063b27acfb5150135ceea92c2210d925f5787c [file] [log] [blame]
//===------ 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(ResourceKey)>;
using HandleTransferFunction =
unique_function<void(ResourceKey, ResourceKey)>;
using RecordedResourcesMap = DenseMap<ResourceKey, ResourceT>;
SimpleResourceManager(ExecutionSession &ES) : ES(ES) {
HandleRemove = [&](ResourceKey K) -> Error {
ES.runSessionLocked([&] { removeResource(K); });
return Error::success();
};
HandleTransfer = [this](ResourceKey DstKey, ResourceKey SrcKey) {
transferResources(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(ResourceKey K) { Resources.erase(K); }
/// Transfer resources from DstKey to SrcKey.
template <typename MergeOp = std::plus<ResourceT>>
void transferResources(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(ResourceKey K) override {
return HandleRemove(K);
}
void handleTransferResources(ResourceKey DstKey,
ResourceKey SrcKey) override {
HandleTransfer(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([&](ResourceKey K) -> Error {
ResourceManagerGotRemove = true;
EXPECT_EQ(SRM.getRecordedResources().size(), 0U)
<< "Unexpected resources recorded";
SRM.removeResource(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([&](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(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([&](ResourceKey K) -> Error {
ResourceManagerGotRemove = true;
EXPECT_EQ(SRM.getRecordedResources().size(), 0U)
<< "Unexpected resources recorded";
SRM.removeResource(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([&](ResourceKey DstKey, ResourceKey SrcKey) {
ResourceManagerGotTransfer = true;
auto &RR = SRM.getRecordedResources();
EXPECT_EQ(RR.size(), 0U) << "Expected no resources recorded yet";
});
auto MakeMU = [&](SymbolStringPtr Name, JITEvaluatedSymbol 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([&](ResourceKey DstKey, ResourceKey SrcKey) {
ResourceManagerGotTransfer = true;
SRM.transferResources(DstKey, SrcKey);
});
auto MakeMU = [&](SymbolStringPtr Name, JITEvaluatedSymbol 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([&](ResourceKey DstKey, ResourceKey SrcKey) {
ResourceManagerGotTransfer = true;
SRM.transferResources(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