//===---------------------- RemoteObjectLayerTest.cpp ---------------------===// // // 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/CompileUtils.h" #include "llvm/ExecutionEngine/Orc/NullResolver.h" #include "llvm/ExecutionEngine/Orc/RemoteObjectLayer.h" #include "OrcTestCommon.h" #include "QueueChannel.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::orc; namespace { class MockObjectLayer { public: using ObjHandleT = uint64_t; using ObjectPtr = std::unique_ptr<MemoryBuffer>; using LookupFn = std::function<JITSymbol(StringRef, bool)>; using SymbolLookupTable = std::map<ObjHandleT, LookupFn>; using AddObjectFtor = std::function<Expected<ObjHandleT>(ObjectPtr, SymbolLookupTable&)>; class ObjectNotFound : public remote::ResourceNotFound<ObjHandleT> { public: ObjectNotFound(ObjHandleT H) : ResourceNotFound(H, "Object handle") {} }; MockObjectLayer(AddObjectFtor AddObject) : AddObject(std::move(AddObject)) {} Expected<ObjHandleT> addObject(ObjectPtr Obj, std::shared_ptr<JITSymbolResolver> Resolver) { return AddObject(std::move(Obj), SymTab); } Error removeObject(ObjHandleT H) { if (SymTab.count(H)) return Error::success(); else return make_error<ObjectNotFound>(H); } JITSymbol findSymbol(StringRef Name, bool ExportedSymbolsOnly) { for (auto KV : SymTab) { if (auto Sym = KV.second(Name, ExportedSymbolsOnly)) return Sym; else if (auto Err = Sym.takeError()) return std::move(Err); } return JITSymbol(nullptr); } JITSymbol findSymbolIn(ObjHandleT H, StringRef Name, bool ExportedSymbolsOnly) { auto LI = SymTab.find(H); if (LI != SymTab.end()) return LI->second(Name, ExportedSymbolsOnly); else return make_error<ObjectNotFound>(H); } Error emitAndFinalize(ObjHandleT H) { if (SymTab.count(H)) return Error::success(); else return make_error<ObjectNotFound>(H); } private: AddObjectFtor AddObject; SymbolLookupTable SymTab; }; using RPCEndpoint = rpc::SingleThreadedRPCEndpoint<rpc::RawByteChannel>; MockObjectLayer::ObjectPtr createTestObject() { OrcNativeTarget::initialize(); auto TM = std::unique_ptr<TargetMachine>(EngineBuilder().selectTarget()); if (!TM) return nullptr; LLVMContext Ctx; ModuleBuilder MB(Ctx, TM->getTargetTriple().str(), "TestModule"); MB.getModule()->setDataLayout(TM->createDataLayout()); auto *Main = MB.createFunctionDecl( FunctionType::get(Type::getInt32Ty(Ctx), {Type::getInt32Ty(Ctx), Type::getInt8PtrTy(Ctx)->getPointerTo()}, false), "main"); Main->getBasicBlockList().push_back(BasicBlock::Create(Ctx)); IRBuilder<> B(&Main->back()); B.CreateRet(ConstantInt::getSigned(Type::getInt32Ty(Ctx), 42)); SimpleCompiler IRCompiler(*TM); return cantFail(IRCompiler(*MB.getModule())); } TEST(RemoteObjectLayer, AddObject) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { logAllUnhandledErrors(std::move(Err), llvm::errs()); }; // Copy the bytes out of the test object: the copy will be used to verify // that the original is correctly transmitted over RPC to the mock layer. StringRef ObjBytes = TestObject->getBuffer(); std::vector<char> ObjContents(ObjBytes.size()); std::copy(ObjBytes.begin(), ObjBytes.end(), ObjContents.begin()); RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); MockObjectLayer BaseLayer( [&ObjContents](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { // Check that the received object file content matches the original. StringRef RPCObjContents = Obj->getBuffer(); EXPECT_EQ(RPCObjContents.size(), ObjContents.size()) << "RPC'd object file has incorrect size"; EXPECT_TRUE(std::equal(RPCObjContents.begin(), RPCObjContents.end(), ObjContents.begin())) << "RPC'd object file content does not match original content"; return 1; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, AddObjectFailure) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "AddObjectFailure - Test Message") << "Expected error string to be \"AddObjectFailure - Test Message\""; }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) -> Expected<MockObjectLayer::ObjHandleT> { return make_error<StringError>("AddObjectFailure - Test Message", inconvertibleErrorCode()); }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); auto HandleOrErr = Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>()); EXPECT_FALSE(HandleOrErr) << "Expected error from addObject"; auto ErrMsg = toString(HandleOrErr.takeError()); EXPECT_EQ(ErrMsg, "AddObjectFailure - Test Message") << "Expected error string to be \"AddObjectFailure - Test Message\""; cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, RemoveObject) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { logAllUnhandledErrors(std::move(Err), llvm::errs()); }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { SymTab[1] = MockObjectLayer::LookupFn(); return 1; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); auto H = cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); cantFail(Client.removeObject(H)); cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, RemoveObjectFailure) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Object handle 42 not found") << "Expected error string to be \"Object handle 42 not found\""; }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); // AddObject lambda does not update symbol table, so removeObject will treat // this as a bad object handle. MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { return 42; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); auto H = cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); auto Err = Client.removeObject(H); EXPECT_TRUE(!!Err) << "Expected error from removeObject"; auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Object handle 42 not found") << "Expected error string to be \"Object handle 42 not found\""; cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, FindSymbol) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Could not find symbol 'badsymbol'") << "Expected error string to be \"Object handle 42 not found\""; }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); // AddObject lambda does not update symbol table, so removeObject will treat // this as a bad object handle. MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { SymTab[42] = [](StringRef Name, bool ExportedSymbolsOnly) -> JITSymbol { if (Name == "foobar") return JITSymbol(0x12348765, JITSymbolFlags::Exported); if (Name == "badsymbol") return make_error<JITSymbolNotFound>(Name); return nullptr; }; return 42; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); // Check that we can find and materialize a valid symbol. auto Sym1 = Client.findSymbol("foobar", true); EXPECT_TRUE(!!Sym1) << "Symbol 'foobar' should be findable"; EXPECT_EQ(cantFail(Sym1.getAddress()), 0x12348765ULL) << "Symbol 'foobar' does not return the correct address"; { // Check that we can return a symbol containing an error. auto Sym2 = Client.findSymbol("badsymbol", true); EXPECT_FALSE(!!Sym2) << "Symbol 'badsymbol' should not be findable"; auto Err = Sym2.takeError(); EXPECT_TRUE(!!Err) << "Sym2 should contain an error value"; auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Could not find symbol 'badsymbol'") << "Expected symbol-not-found error for Sym2"; } { // Check that we can return a 'null' symbol. auto Sym3 = Client.findSymbol("baz", true); EXPECT_FALSE(!!Sym3) << "Symbol 'baz' should convert to false"; auto Err = Sym3.takeError(); EXPECT_FALSE(!!Err) << "Symbol 'baz' should not contain an error"; } cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, FindSymbolIn) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Could not find symbol 'barbaz'") << "Expected error string to be \"Object handle 42 not found\""; }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); // AddObject lambda does not update symbol table, so removeObject will treat // this as a bad object handle. MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { SymTab[42] = [](StringRef Name, bool ExportedSymbolsOnly) -> JITSymbol { if (Name == "foobar") return JITSymbol(0x12348765, JITSymbolFlags::Exported); return make_error<JITSymbolNotFound>(Name); }; // Dummy symbol table entry - this should not be visible to // findSymbolIn. SymTab[43] = [](StringRef Name, bool ExportedSymbolsOnly) -> JITSymbol { if (Name == "barbaz") return JITSymbol(0xdeadbeef, JITSymbolFlags::Exported); return make_error<JITSymbolNotFound>(Name); }; return 42; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); auto H = cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); auto Sym1 = Client.findSymbolIn(H, "foobar", true); EXPECT_TRUE(!!Sym1) << "Symbol 'foobar' should be findable"; EXPECT_EQ(cantFail(Sym1.getAddress()), 0x12348765ULL) << "Symbol 'foobar' does not return the correct address"; auto Sym2 = Client.findSymbolIn(H, "barbaz", true); EXPECT_FALSE(!!Sym2) << "Symbol 'barbaz' should not be findable"; auto Err = Sym2.takeError(); EXPECT_TRUE(!!Err) << "Sym2 should contain an error value"; auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Could not find symbol 'barbaz'") << "Expected symbol-not-found error for Sym2"; cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, EmitAndFinalize) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { logAllUnhandledErrors(std::move(Err), llvm::errs()); }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { SymTab[1] = MockObjectLayer::LookupFn(); return 1; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); auto H = cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); auto Err = Client.emitAndFinalize(H); EXPECT_FALSE(!!Err) << "emitAndFinalize should work"; cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } TEST(RemoteObjectLayer, EmitAndFinalizeFailure) { llvm::orc::rpc::registerStringError<rpc::RawByteChannel>(); auto TestObject = createTestObject(); if (!TestObject) return; auto Channels = createPairedQueueChannels(); auto ReportError = [](Error Err) { auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Object handle 1 not found") << "Expected bad handle error"; }; RPCEndpoint ClientEP(*Channels.first, true); RemoteObjectClientLayer<RPCEndpoint> Client(AcknowledgeORCv1Deprecation, ClientEP, ReportError); RPCEndpoint ServerEP(*Channels.second, true); MockObjectLayer BaseLayer( [](MockObjectLayer::ObjectPtr Obj, MockObjectLayer::SymbolLookupTable &SymTab) { return 1; }); RemoteObjectServerLayer<MockObjectLayer, RPCEndpoint> Server( AcknowledgeORCv1Deprecation, BaseLayer, ServerEP, ReportError); bool Finished = false; ServerEP.addHandler<remote::utils::TerminateSession>( [&]() { Finished = true; } ); auto ServerThread = std::thread([&]() { while (!Finished) cantFail(ServerEP.handleOne()); }); auto H = cantFail(Client.addObject(std::move(TestObject), std::make_shared<NullLegacyResolver>())); auto Err = Client.emitAndFinalize(H); EXPECT_TRUE(!!Err) << "emitAndFinalize should work"; auto ErrMsg = toString(std::move(Err)); EXPECT_EQ(ErrMsg, "Object handle 1 not found") << "emitAndFinalize returned incorrect error"; cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } }