//===------ 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 class SimpleResourceManager : public ResourceManager { public: using HandleRemoveFunction = unique_function; using HandleTransferFunction = unique_function; using RecordedResourcesMap = DenseMap; 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 > 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 > 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( SymbolFlagsMap({{Foo, FooSym.getFlags()}}), [&](std::unique_ptr 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( SymbolFlagsMap({{Foo, FooSym.getFlags()}}), [&](std::unique_ptr 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 MR; auto MU = std::make_unique( SymbolFlagsMap({{Foo, FooSym.getFlags()}}), [&](std::unique_ptr 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 Result) { EXPECT_THAT_EXPECTED(Result, Failed()) << "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()) << "withResourceKeyDo on MR with removed tracker should have failed"; EXPECT_THAT_ERROR(MR->notifyResolved({{Foo, FooSym}}), Failed()) << "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( SymbolFlagsMap({{Foo, FooSym.getFlags()}}), [&](std::unique_ptr 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( SymbolFlagsMap({{Bar, BarSym.getFlags()}}), [&](std::unique_ptr 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( SymbolFlagsMap({{Name, Sym.getFlags()}}), [=, &SRM](std::unique_ptr 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( SymbolFlagsMap({{Name, Sym.getFlags()}}), [=, &SRM](std::unique_ptr 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 FooMR; cantFail(JD.define(std::make_unique( SymbolFlagsMap({{Foo, FooSym.getFlags()}}), [&](std::unique_ptr R) { FooMR = std::move(R); }), FooRT)); auto BarRT = JD.createResourceTracker(); ES.lookup( LookupKind::Static, makeJITDylibSearchOrder(&JD), SymbolLookupSet(Foo), SymbolState::Ready, [](Expected 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