//===----------- CoreAPIsTest.cpp - Unit tests for Core ORC APIs ----------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "OrcTestCommon.h" #include "llvm/Config/llvm-config.h" #include "llvm/ExecutionEngine/Orc/Core.h" #include "gtest/gtest.h" #include #include using namespace llvm; using namespace llvm::orc; namespace { class SimpleMaterializationUnit : public MaterializationUnit { public: using GetSymbolsFunction = std::function; using MaterializeFunction = std::function; using DiscardFunction = std::function; using DestructorFunction = std::function; SimpleMaterializationUnit( GetSymbolsFunction GetSymbols, MaterializeFunction Materialize, DiscardFunction Discard, DestructorFunction Destructor = DestructorFunction()) : GetSymbols(std::move(GetSymbols)), Materialize(std::move(Materialize)), Discard(std::move(Discard)), Destructor(std::move(Destructor)) {} ~SimpleMaterializationUnit() override { if (Destructor) Destructor(); } SymbolFlagsMap getSymbols() override { return GetSymbols(); } void materialize(MaterializationResponsibility R) override { Materialize(std::move(R)); } void discard(const VSO &V, SymbolStringPtr Name) override { Discard(V, std::move(Name)); } private: GetSymbolsFunction GetSymbols; MaterializeFunction Materialize; DiscardFunction Discard; DestructorFunction Destructor; }; TEST(CoreAPIsTest, AsynchronousSymbolQuerySuccessfulResolutionOnly) { SymbolStringPool SP; auto Foo = SP.intern("foo"); constexpr JITTargetAddress FakeAddr = 0xdeadbeef; SymbolNameSet Names({Foo}); bool OnResolutionRun = false; bool OnReadyRun = false; auto OnResolution = [&](Expected Result) { EXPECT_TRUE(!!Result) << "Resolution unexpectedly returned error"; auto I = Result->find(Foo); EXPECT_NE(I, Result->end()) << "Could not find symbol definition"; EXPECT_EQ(I->second.getAddress(), FakeAddr) << "Resolution returned incorrect result"; OnResolutionRun = true; }; auto OnReady = [&](Error Err) { cantFail(std::move(Err)); OnReadyRun = true; }; AsynchronousSymbolQuery Q(Names, OnResolution, OnReady); Q.resolve(Foo, JITEvaluatedSymbol(FakeAddr, JITSymbolFlags::Exported)); EXPECT_TRUE(OnResolutionRun) << "OnResolutionCallback was not run"; EXPECT_FALSE(OnReadyRun) << "OnReady unexpectedly run"; } TEST(CoreAPIsTest, AsynchronousSymbolQueryResolutionErrorOnly) { SymbolStringPool SP; auto Foo = SP.intern("foo"); SymbolNameSet Names({Foo}); bool OnResolutionRun = false; bool OnReadyRun = false; auto OnResolution = [&](Expected Result) { EXPECT_FALSE(!!Result) << "Resolution unexpectedly returned success"; auto Msg = toString(Result.takeError()); EXPECT_EQ(Msg, "xyz") << "Resolution returned incorrect result"; OnResolutionRun = true; }; auto OnReady = [&](Error Err) { cantFail(std::move(Err)); OnReadyRun = true; }; AsynchronousSymbolQuery Q(Names, OnResolution, OnReady); Q.notifyMaterializationFailed( make_error("xyz", inconvertibleErrorCode())); EXPECT_TRUE(OnResolutionRun) << "OnResolutionCallback was not run"; EXPECT_FALSE(OnReadyRun) << "OnReady unexpectedly run"; } TEST(CoreAPIsTest, SimpleAsynchronousSymbolQueryAgainstVSO) { SymbolStringPool SP; auto Foo = SP.intern("foo"); constexpr JITTargetAddress FakeAddr = 0xdeadbeef; SymbolNameSet Names({Foo}); bool OnResolutionRun = false; bool OnReadyRun = false; auto OnResolution = [&](Expected Result) { EXPECT_TRUE(!!Result) << "Query unexpectedly returned error"; auto I = Result->find(Foo); EXPECT_NE(I, Result->end()) << "Could not find symbol definition"; EXPECT_EQ(I->second.getAddress(), FakeAddr) << "Resolution returned incorrect result"; OnResolutionRun = true; }; auto OnReady = [&](Error Err) { cantFail(std::move(Err)); OnReadyRun = true; }; auto Q = std::make_shared(Names, OnResolution, OnReady); VSO V; SymbolMap Defs; Defs[Foo] = JITEvaluatedSymbol(FakeAddr, JITSymbolFlags::Exported); cantFail(V.define(std::move(Defs))); V.lookup(Q, Names); EXPECT_TRUE(OnResolutionRun) << "OnResolutionCallback was not run"; EXPECT_TRUE(OnReadyRun) << "OnReady was not run"; } TEST(CoreAPIsTest, LookupFlagsTest) { // Test that lookupFlags works on a predefined symbol, and does not trigger // materialization of a lazy symbol. SymbolStringPool SP; auto Foo = SP.intern("foo"); auto Bar = SP.intern("bar"); auto Baz = SP.intern("baz"); JITSymbolFlags FooFlags = JITSymbolFlags::Exported; JITSymbolFlags BarFlags = static_cast( JITSymbolFlags::Exported | JITSymbolFlags::Weak); VSO V; auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap({{Bar, BarFlags}}); }, [](MaterializationResponsibility R) { llvm_unreachable("Symbol materialized on flags lookup"); }, [](const VSO &V, SymbolStringPtr Name) { llvm_unreachable("Symbol finalized on flags lookup"); }); SymbolMap InitialDefs; InitialDefs[Foo] = JITEvaluatedSymbol(0xdeadbeef, FooFlags); cantFail(V.define(std::move(InitialDefs))); cantFail(V.defineLazy(std::move(MU))); SymbolNameSet Names({Foo, Bar, Baz}); SymbolFlagsMap SymbolFlags; auto SymbolsNotFound = V.lookupFlags(SymbolFlags, Names); EXPECT_EQ(SymbolsNotFound.size(), 1U) << "Expected one not-found symbol"; EXPECT_EQ(SymbolsNotFound.count(Baz), 1U) << "Expected Baz to be not-found"; EXPECT_EQ(SymbolFlags.size(), 2U) << "Returned symbol flags contains unexpected results"; EXPECT_EQ(SymbolFlags.count(Foo), 1U) << "Missing lookupFlags result for Foo"; EXPECT_EQ(SymbolFlags[Foo], FooFlags) << "Incorrect flags returned for Foo"; EXPECT_EQ(SymbolFlags.count(Bar), 1U) << "Missing lookupFlags result for Bar"; EXPECT_EQ(SymbolFlags[Bar], BarFlags) << "Incorrect flags returned for Bar"; } TEST(CoreAPIsTest, DropMaterializerWhenEmpty) { SymbolStringPool SP; auto Foo = SP.intern("foo"); auto Bar = SP.intern("bar"); bool DestructorRun = false; auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap( {{Foo, JITSymbolFlags::Weak}, {Bar, JITSymbolFlags::Weak}}); }, [](MaterializationResponsibility R) { llvm_unreachable("Unexpected call to materialize"); }, [&](const VSO &V, SymbolStringPtr Name) { EXPECT_TRUE(Name == Foo || Name == Bar) << "Discard of unexpected symbol?"; }, [&]() { DestructorRun = true; }); VSO V; cantFail(V.defineLazy(std::move(MU))); auto FooSym = JITEvaluatedSymbol(1, JITSymbolFlags::Exported); auto BarSym = JITEvaluatedSymbol(2, JITSymbolFlags::Exported); cantFail(V.define(SymbolMap({{Foo, FooSym}}))); EXPECT_FALSE(DestructorRun) << "MaterializationUnit should not have been destroyed yet"; cantFail(V.define(SymbolMap({{Bar, BarSym}}))); EXPECT_TRUE(DestructorRun) << "MaterializationUnit should have been destroyed"; } TEST(CoreAPIsTest, AddAndMaterializeLazySymbol) { constexpr JITTargetAddress FakeFooAddr = 0xdeadbeef; constexpr JITTargetAddress FakeBarAddr = 0xcafef00d; SymbolStringPool SP; auto Foo = SP.intern("foo"); auto Bar = SP.intern("bar"); bool FooMaterialized = false; bool BarDiscarded = false; VSO V; auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap( {{Foo, JITSymbolFlags::Exported}, {Bar, static_cast( JITSymbolFlags::Exported | JITSymbolFlags::Weak)}}); }, [&](MaterializationResponsibility R) { assert(BarDiscarded && "Bar should have been discarded by this point"); SymbolMap SymbolsToResolve; SymbolsToResolve[Foo] = JITEvaluatedSymbol(FakeFooAddr, JITSymbolFlags::Exported); R.resolve(std::move(SymbolsToResolve)); R.finalize(); FooMaterialized = true; }, [&](const VSO &V, SymbolStringPtr Name) { EXPECT_EQ(Name, Bar) << "Expected Name to be Bar"; BarDiscarded = true; }); cantFail(V.defineLazy(std::move(MU))); SymbolMap BarOverride; BarOverride[Bar] = JITEvaluatedSymbol(FakeBarAddr, JITSymbolFlags::Exported); cantFail(V.define(std::move(BarOverride))); SymbolNameSet Names({Foo}); bool OnResolutionRun = false; bool OnReadyRun = false; auto OnResolution = [&](Expected Result) { EXPECT_TRUE(!!Result) << "Resolution unexpectedly returned error"; auto I = Result->find(Foo); EXPECT_NE(I, Result->end()) << "Could not find symbol definition"; EXPECT_EQ(I->second.getAddress(), FakeFooAddr) << "Resolution returned incorrect result"; OnResolutionRun = true; }; auto OnReady = [&](Error Err) { cantFail(std::move(Err)); OnReadyRun = true; }; auto Q = std::make_shared(Names, OnResolution, OnReady); auto LR = V.lookup(std::move(Q), Names); for (auto &M : LR.Materializers) M(); EXPECT_TRUE(LR.UnresolvedSymbols.empty()) << "Could not find Foo in dylib"; EXPECT_TRUE(FooMaterialized) << "Foo was not materialized"; EXPECT_TRUE(BarDiscarded) << "Bar was not discarded"; EXPECT_TRUE(OnResolutionRun) << "OnResolutionCallback was not run"; EXPECT_TRUE(OnReadyRun) << "OnReady was not run"; } TEST(CoreAPIsTest, FailResolution) { SymbolStringPool SP; auto Foo = SP.intern("foo"); auto Bar = SP.intern("bar"); SymbolNameSet Names({Foo, Bar}); auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap( {{Foo, JITSymbolFlags::Weak}, {Bar, JITSymbolFlags::Weak}}); }, [&](MaterializationResponsibility R) { R.notifyMaterializationFailed(); }, [&](const VSO &V, SymbolStringPtr Name) { llvm_unreachable("Unexpected call to discard"); }); VSO V; cantFail(V.defineLazy(std::move(MU))); auto OnResolution = [&](Expected Result) { handleAllErrors(Result.takeError(), [&](FailedToResolve &F) { EXPECT_EQ(F.getSymbols(), Names) << "Expected to fail on symbols in Names"; }, [](ErrorInfoBase &EIB) { std::string ErrMsg; { raw_string_ostream ErrOut(ErrMsg); EIB.log(ErrOut); } ADD_FAILURE() << "Expected a FailedToResolve error. Got:\n" << ErrMsg; }); }; auto OnReady = [](Error Err) { cantFail(std::move(Err)); ADD_FAILURE() << "OnReady should never be called"; }; auto Q = std::make_shared(Names, OnResolution, OnReady); auto LR = V.lookup(std::move(Q), Names); for (auto &M : LR.Materializers) M(); } TEST(CoreAPIsTest, FailFinalization) { SymbolStringPool SP; auto Foo = SP.intern("foo"); auto Bar = SP.intern("bar"); SymbolNameSet Names({Foo, Bar}); auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap( {{Foo, JITSymbolFlags::Exported}, {Bar, JITSymbolFlags::Exported}}); }, [&](MaterializationResponsibility R) { constexpr JITTargetAddress FakeFooAddr = 0xdeadbeef; constexpr JITTargetAddress FakeBarAddr = 0xcafef00d; auto FooSym = JITEvaluatedSymbol(FakeFooAddr, JITSymbolFlags::Exported); auto BarSym = JITEvaluatedSymbol(FakeBarAddr, JITSymbolFlags::Exported); R.resolve(SymbolMap({{Foo, FooSym}, {Bar, BarSym}})); R.notifyMaterializationFailed(); }, [&](const VSO &V, SymbolStringPtr Name) { llvm_unreachable("Unexpected call to discard"); }); VSO V; cantFail(V.defineLazy(std::move(MU))); auto OnResolution = [](Expected Result) { cantFail(std::move(Result)); }; auto OnReady = [&](Error Err) { handleAllErrors(std::move(Err), [&](FailedToFinalize &F) { EXPECT_EQ(F.getSymbols(), Names) << "Expected to fail on symbols in Names"; }, [](ErrorInfoBase &EIB) { std::string ErrMsg; { raw_string_ostream ErrOut(ErrMsg); EIB.log(ErrOut); } ADD_FAILURE() << "Expected a FailedToFinalize error. Got:\n" << ErrMsg; }); }; auto Q = std::make_shared(Names, OnResolution, OnReady); auto LR = V.lookup(std::move(Q), Names); for (auto &M : LR.Materializers) M(); } TEST(CoreAPIsTest, TestLambdaSymbolResolver) { JITEvaluatedSymbol FooSym(0xdeadbeef, JITSymbolFlags::Exported); JITEvaluatedSymbol BarSym(0xcafef00d, JITSymbolFlags::Exported); SymbolStringPool SP; auto Foo = SP.intern("foo"); auto Bar = SP.intern("bar"); auto Baz = SP.intern("baz"); VSO V; cantFail(V.define({{Foo, FooSym}, {Bar, BarSym}})); auto Resolver = createSymbolResolver( [&](SymbolFlagsMap &SymbolFlags, const SymbolNameSet &Symbols) { return V.lookupFlags(SymbolFlags, Symbols); }, [&](std::shared_ptr Q, SymbolNameSet Symbols) { auto LR = V.lookup(std::move(Q), Symbols); assert(LR.Materializers.empty() && "Test generated unexpected materialization work?"); return std::move(LR.UnresolvedSymbols); }); SymbolNameSet Symbols({Foo, Bar, Baz}); SymbolFlagsMap SymbolFlags; SymbolNameSet SymbolsNotFound = Resolver->lookupFlags(SymbolFlags, Symbols); EXPECT_EQ(SymbolFlags.size(), 2U) << "lookupFlags returned the wrong number of results"; EXPECT_EQ(SymbolFlags.count(Foo), 1U) << "Missing lookupFlags result for foo"; EXPECT_EQ(SymbolFlags.count(Bar), 1U) << "Missing lookupFlags result for bar"; EXPECT_EQ(SymbolFlags[Foo], FooSym.getFlags()) << "Incorrect lookupFlags result for Foo"; EXPECT_EQ(SymbolFlags[Bar], BarSym.getFlags()) << "Incorrect lookupFlags result for Bar"; EXPECT_EQ(SymbolsNotFound.size(), 1U) << "Expected one symbol not found in lookupFlags"; EXPECT_EQ(SymbolsNotFound.count(Baz), 1U) << "Expected baz not to be found in lookupFlags"; bool OnResolvedRun = false; auto OnResolved = [&](Expected Result) { OnResolvedRun = true; EXPECT_TRUE(!!Result) << "Unexpected error"; EXPECT_EQ(Result->size(), 2U) << "Unexpected number of resolved symbols"; EXPECT_EQ(Result->count(Foo), 1U) << "Missing lookup result for foo"; EXPECT_EQ(Result->count(Bar), 1U) << "Missing lookup result for bar"; EXPECT_EQ((*Result)[Foo].getAddress(), FooSym.getAddress()) << "Incorrect address for foo"; EXPECT_EQ((*Result)[Bar].getAddress(), BarSym.getAddress()) << "Incorrect address for bar"; }; auto OnReady = [&](Error Err) { EXPECT_FALSE(!!Err) << "Finalization should never fail in this test"; }; auto Q = std::make_shared(SymbolNameSet({Foo, Bar}), OnResolved, OnReady); auto Unresolved = Resolver->lookup(std::move(Q), Symbols); EXPECT_EQ(Unresolved.size(), 1U) << "Expected one unresolved symbol"; EXPECT_EQ(Unresolved.count(Baz), 1U) << "Expected baz to not be resolved"; EXPECT_TRUE(OnResolvedRun) << "OnResolved was never run"; } TEST(CoreAPIsTest, TestLookupWithUnthreadedMaterialization) { constexpr JITTargetAddress FakeFooAddr = 0xdeadbeef; JITEvaluatedSymbol FooSym(FakeFooAddr, JITSymbolFlags::Exported); ExecutionSession ES(std::make_shared()); auto Foo = ES.getSymbolStringPool().intern("foo"); auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap({{Foo, JITSymbolFlags::Exported}}); }, [&](MaterializationResponsibility R) { R.resolve({{Foo, FooSym}}); R.finalize(); }, [](const VSO &V, SymbolStringPtr Name) { llvm_unreachable("Not expecting finalization"); }); VSO V; cantFail(V.defineLazy(std::move(MU))); auto FooLookupResult = cantFail(lookup({&V}, Foo, MaterializeOnCurrentThread())); EXPECT_EQ(FooLookupResult.getAddress(), FooSym.getAddress()) << "lookup returned an incorrect address"; EXPECT_EQ(FooLookupResult.getFlags(), FooSym.getFlags()) << "lookup returned incorrect flags"; } TEST(CoreAPIsTest, TestLookupWithThreadedMaterialization) { #if LLVM_ENABLE_THREADS constexpr JITTargetAddress FakeFooAddr = 0xdeadbeef; JITEvaluatedSymbol FooSym(FakeFooAddr, JITSymbolFlags::Exported); ExecutionSession ES(std::make_shared()); auto Foo = ES.getSymbolStringPool().intern("foo"); auto MU = llvm::make_unique( [=]() { return SymbolFlagsMap({{Foo, JITSymbolFlags::Exported}}); }, [&](MaterializationResponsibility R) { R.resolve({{Foo, FooSym}}); R.finalize(); }, [](const VSO &V, SymbolStringPtr Name) { llvm_unreachable("Not expecting finalization"); }); VSO V; cantFail(V.defineLazy(std::move(MU))); std::thread MaterializationThread; auto MaterializeOnNewThread = [&](VSO::Materializer M) { // FIXME: Use move capture once we move to C++14. auto SharedM = std::make_shared(std::move(M)); MaterializationThread = std::thread([SharedM]() { (*SharedM)(); }); }; auto FooLookupResult = cantFail(lookup({&V}, Foo, MaterializeOnNewThread)); EXPECT_EQ(FooLookupResult.getAddress(), FooSym.getAddress()) << "lookup returned an incorrect address"; EXPECT_EQ(FooLookupResult.getFlags(), FooSym.getFlags()) << "lookup returned incorrect flags"; MaterializationThread.join(); #endif } } // namespace