Index: llvm/lib/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.cpp =================================================================== --- llvm/lib/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.cpp +++ llvm/lib/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.cpp @@ -18,7 +18,8 @@ public: JITDylibSearchOrderResolver(MaterializationResponsibility &MR) : MR(MR) {} - void lookup(const LookupSet &Symbols, OnResolvedFunction OnResolved) override { + void lookup(const LookupSet &Symbols, + OnResolvedFunction OnResolved) override { auto &ES = MR.getTargetJITDylib().getExecutionSession(); SymbolLookupSet InternedSymbols; @@ -363,12 +364,6 @@ return; } - if (auto Err = R.notifyEmitted()) { - getExecutionSession().reportError(std::move(Err)); - R.failMaterialization(); - return; - } - std::unique_ptr Obj; std::unique_ptr ObjBuffer; std::tie(Obj, ObjBuffer) = O.takeBinary(); @@ -384,10 +379,24 @@ if (NotifyEmitted) NotifyEmitted(R, std::move(ObjBuffer)); + // Add to MemMgrs before potentially notifying other threads, that the object + // was emitted. Otherwise these other threads could already execute the code + // and remove the object, leading to defunct resource trackers. if (auto Err = R.withResourceKeyDo( [&](ResourceKey K) { MemMgrs[K].push_back(std::move(MemMgr)); })) { getExecutionSession().reportError(std::move(Err)); R.failMaterialization(); + return; + } + + if (auto Err = R.notifyEmitted()) { + // Undo adding the memory manager and calling the event listeners. + consumeError(R.withResourceKeyDo([&](ResourceKey K) { + consumeError(handleRemoveResources(R.getTargetJITDylib(), K)); + })); + + getExecutionSession().reportError(std::move(Err)); + R.failMaterialization(); } } Index: llvm/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp =================================================================== --- llvm/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp +++ llvm/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp @@ -6,14 +6,15 @@ // //===----------------------------------------------------------------------===// +#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" #include "OrcTestCommon.h" #include "llvm/ExecutionEngine/ExecutionEngine.h" #include "llvm/ExecutionEngine/Orc/CompileUtils.h" #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" -#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" #include "llvm/IR/Constants.h" #include "llvm/IR/LLVMContext.h" +#include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" #include @@ -244,4 +245,104 @@ ES.reportError(std::move(Err)); } +TEST(RTDyldObjectLinkingLayerTest, RemoveJITDylibMaterialzedInOtherThread) { +#if LLVM_ENABLE_THREADS + OrcNativeTarget::initialize(); + + JITTargetMachineBuilder JTMB = + cantFail(JITTargetMachineBuilder::detectHost()); + + // Try to build a target machine. + Expected> TM = JTMB.createTargetMachine(); + if (TM.takeError()) + GTEST_SKIP(); + + // Create a module with a void() function foo. + ThreadSafeContext TSCtx(std::make_unique()); + ThreadSafeModule MFoo; + { + ModuleBuilder MB(*TSCtx.getContext(), (*TM)->getTargetTriple().str(), + "FooMod"); + MB.getModule()->setDataLayout((*TM)->createDataLayout()); + + Function *Func = MB.createFunctionDecl( + FunctionType::get(Type::getVoidTy(*TSCtx.getContext()), {}, false), + "foo"); + Func->setLinkage(GlobalValue::ExternalLinkage); + BasicBlock *FooEntry = + BasicBlock::Create(*TSCtx.getContext(), "entry", Func); + IRBuilder<> B(FooEntry); + B.CreateRetVoid(); + + MFoo = ThreadSafeModule(MB.takeModule(), TSCtx); + } + + // Create a simple stack. + ExecutionSession ES{std::make_unique()}; + RTDyldObjectLinkingLayer ObjLayer( + ES, []() { return std::make_unique(); }); + IRCompileLayer CompileLayer(ES, ObjLayer, + std::make_unique(JTMB)); + + // By default, the Exported flag is not set for symbols. + // Ensure, the flags are overridden, if necessary + if (JTMB.getTargetTriple().isOSBinFormatCOFF()) + ObjLayer.setOverrideObjectFlagsWithResponsibilityFlags(true); + + auto &JDFoo = ES.createBareJITDylib("JDFoo"); + cantFail(CompileLayer.add(JDFoo, std::move(MFoo))); + + // What we want to achieve: + // - Thread1 calls lookup(Foo) and gets stuck in waiting for the promise + // - Thread2 is created to dispatch the materialization task for FooMod + // - Thread2 materializes Foo and sends notifyEmitted in + // RTDyldObjectLinkingLayer::onObjEmit() + // - Thread1 reads the promised result and removes the module JD + // - Thread2 continues execution in onObjEmit(). + // In the original version, onObjEmit() would now try to add the + // MemoryManager for the already removed module which results in an error + // due to a defunct resource tracker + // ("JIT session error: Resource tracker became defunct"). + + // Always run tasks on an own thread. + std::mutex WorkThreadsMutex; + std::vector WorkThreads; + ES.setDispatchTask([&](std::unique_ptr T) { + std::promise WaitP; + std::lock_guard Lock(WorkThreadsMutex); + WorkThreads.push_back( + std::thread([T = std::move(T), WaitF = WaitP.get_future()]() mutable { + WaitF.get(); + T->run(); + })); + WaitP.set_value(); + }); + + // No errors should happen in this session. + ES.setErrorReporter([&](Error Err) { + EXPECT_THAT_ERROR(std::move(Err), Succeeded()) << "Test failed"; + }); + + // Install NotifyEmitted handler to delay execution in + // RTDyldObjectLinkingLayer::onObjEmit(). + ObjLayer.setNotifyEmitted( + [](MaterializationResponsibility &R, std::unique_ptr) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + }); + + // Do a blocking lookup on foo. + auto Foo = ES.intern("foo"); + cantFail(ES.lookup(makeJITDylibSearchOrder(&JDFoo), SymbolLookupSet({Foo}))); + + // Remove JDFoo while emitting thread is stuck. + cantFail(ES.removeJITDylib(JDFoo)); + + for (auto &WT : WorkThreads) + WT.join(); + + if (auto Err = ES.endSession()) + ES.reportError(std::move(Err)); +#endif +} + } // end anonymous namespace