Index: lib/ExecutionEngine/Orc/IRCompileLayer.cpp =================================================================== --- lib/ExecutionEngine/Orc/IRCompileLayer.cpp +++ lib/ExecutionEngine/Orc/IRCompileLayer.cpp @@ -21,23 +21,34 @@ this->NotifyCompiled = std::move(NotifyCompiled); } +// FIXME: Quick hack to avoid calling into PassManager::run() in parallel. +static std::mutex PassManagerRunMutex; + void IRCompileLayer2::emit(MaterializationResponsibility R, VModuleKey K, std::unique_ptr M) { assert(M && "Module must not be null"); - if (auto Obj = Compile(*M)) { - { - std::lock_guard Lock(IRLayerMutex); - if (NotifyCompiled) - NotifyCompiled(K, std::move(M)); - else - M = nullptr; + std::unique_ptr Obj; + { + std::lock_guard Lock(PassManagerRunMutex); + if (auto CompileRes = Compile(*M)) { + Obj = std::move(*CompileRes); + assert(Obj && "In success case Obj cannot be null"); + } else { + R.failMaterialization(); + getExecutionSession().reportError(CompileRes.takeError()); + return; } - BaseLayer.emit(std::move(R), std::move(K), std::move(*Obj)); - } else { - R.failMaterialization(); - getExecutionSession().reportError(Obj.takeError()); } + + { + std::lock_guard Lock(IRLayerMutex); + if (NotifyCompiled) + NotifyCompiled(K, std::move(M)); + else + M = nullptr; + } + BaseLayer.emit(std::move(R), std::move(K), std::move(Obj)); } } // End namespace orc. Index: unittests/ExecutionEngine/Orc/LLJITTest.cpp =================================================================== --- unittests/ExecutionEngine/Orc/LLJITTest.cpp +++ unittests/ExecutionEngine/Orc/LLJITTest.cpp @@ -24,157 +24,233 @@ using namespace llvm; using namespace llvm::orc; -#define MULTILINE(...) #__VA_ARGS__ +//===----------------------------------------------------------------------===// +// Some utils shared by all tests +//===----------------------------------------------------------------------===// -const char *FooIR = MULTILINE( - declare i32 @bar() - declare i32 @far() +#define LLVM_IR(...) #__VA_ARGS__ +const char *FooIR = LLVM_IR( define i32 @foo() { - %1 = call i32 @bar() - %2 = call i32 @far() - %3 = add nsw i32 %1, %2 - ret i32 %3 + ret i32 1 } ); -const char *BarIR = MULTILINE( +const char *BarIR = LLVM_IR( define i32 @bar() { ret i32 1 } ); -const char *FarIR = MULTILINE( - define i32 @far() { - ret i32 1 +const char *BuzIR = LLVM_IR( + declare i32 @foo() + declare i32 @bar() + + define i32 @buz() { + %1 = call i32 @foo() + %2 = call i32 @bar() + %3 = add nsw i32 %1, %2 + ret i32 %3 } ); -using MaterializeFunction = - std::function; - -using DiscardFunction = - std::function; - -using DestructorFunction = std::function; - -class SimpleMaterializationUnit : public MaterializationUnit { -public: - SimpleMaterializationUnit( - SymbolFlagsMap SymbolFlags, MaterializeFunction Materialize, - DiscardFunction Discard = DiscardFunction(), - DestructorFunction Destructor = DestructorFunction()) - : MaterializationUnit(std::move(SymbolFlags)), - Materialize(std::move(Materialize)), Discard(std::move(Discard)), - Destructor(std::move(Destructor)) {} - - ~SimpleMaterializationUnit() override { - if (Destructor) - Destructor(); - } - - void materialize(MaterializationResponsibility R) override { - Materialize(std::move(R)); - } - - void discard(const JITDylib &JD, SymbolStringPtr Name) override { - if (Discard) - Discard(JD, std::move(Name)); - else - llvm_unreachable("Discard not supported"); - } - -private: - MaterializeFunction Materialize; - DiscardFunction Discard; - DestructorFunction Destructor; -}; +static const MemoryBufferRef Foo{FooIR, "FooIR"}; +static const MemoryBufferRef Bar{BarIR, "BarIR"}; +static const MemoryBufferRef Buz{BuzIR, "BuzIR"}; -TEST(LLJIT, SymbolLookupRace) { +static std::unique_ptr Init() { InitializeNativeTarget(); InitializeNativeTargetAsmParser(); InitializeNativeTargetAsmPrinter(); - + + auto ES = llvm::make_unique(); auto TMB = cantFail(JITTargetMachineBuilder::detectHost()); auto TM = cantFail(TMB.createTargetMachine()); auto DL = TM->createDataLayout(); - // Use single JIT for all functions. - // -> Maintains one single ThinLTO index. - std::unique_ptr JIT = cantFail(LLJIT::Create( - llvm::make_unique(), std::move(TM), DL)); + return cantFail(LLJIT::Create(std::move(ES), std::move(TM), DL)); +} + +static int Exec(JITEvaluatedSymbol FuncSym) { + auto FuncPtr = (int(*)())FuncSym.getAddress(); + return FuncPtr(); +} + +static void JoinAll(std::vector &Ts) { + for (std::thread &T : Ts) + if (T.joinable()) + T.join(); +} + +//===----------------------------------------------------------------------===// + +TEST(LLJIT, UnthreadedSingleDylib) { + std::unique_ptr Jit = Init(); + + SMDiagnostic E; + std::vector Cs(3); + std::vector Bs{Foo, Bar, Buz}; - // Run all in one session. - ExecutionSession &CommonES = JIT->getExecutionSession(); + for (size_t i = 0; i < 3; i++) + cantFail(Jit->addIRModule(parseIR(Bs[i], E, Cs[i]))); - // Emit foo and bar into one virtual dylib. - // -> Test inlining within one translation unit. - JITDylib &MainDylib = JIT->getMainJITDylib(); - JITDylib &FarDylib = CommonES.createJITDylib("Far"); + JITEvaluatedSymbol FooSym = cantFail(Jit->lookup("foo")); + EXPECT_EQ(1, Exec(FooSym)); - // Register function names. - auto FooName = CommonES.getSymbolStringPool().intern("foo"); - auto BarName = CommonES.getSymbolStringPool().intern("bar"); - auto FarName = CommonES.getSymbolStringPool().intern("far"); - - //SymbolFlagsMap Symbols({{FooName, JITSymbolFlags::Exported}, - // {BarName, JITSymbolFlags::Exported}}); - - // One MaterializationResponsibility for each of the sumbols, so we can - // define dependencies below. - Optional FooMR; - Optional BarMR; - Optional FarMR; - - // Actually, I want one MaterializationUnit for Foo and Bar. - auto FooMU = llvm::make_unique( - SymbolFlagsMap({{FooName, JITSymbolFlags::Exported}}), - [&](MaterializationResponsibility R) { FooMR.emplace(std::move(R)); }); - - auto BarMU = llvm::make_unique( - SymbolFlagsMap({{BarName, JITSymbolFlags::Exported}}), - [&](MaterializationResponsibility R) { BarMR.emplace(std::move(R)); }); + JITEvaluatedSymbol BarSym = cantFail(Jit->lookup("bar")); + EXPECT_EQ(1, Exec(BarSym)); - auto FarMU = llvm::make_unique( - SymbolFlagsMap({{FarName, JITSymbolFlags::Exported}}), - [&](MaterializationResponsibility R) { FarMR.emplace(std::move(R)); }); + JITEvaluatedSymbol BuzSym = cantFail(Jit->lookup("buz")); + EXPECT_EQ(2, Exec(BuzSym)); +} + +//===----------------------------------------------------------------------===// + +TEST(LLJIT, UnthreadedMultiDylib) { + std::unique_ptr Jit = Init(); + ExecutionSession &ES = Jit->getExecutionSession(); + + JITDylib &Main = Jit->getMainJITDylib(); + JITDylib &Extra1 = ES.createJITDylib("1"); + JITDylib &Extra2 = ES.createJITDylib("2"); - cantFail(MainDylib.define(FooMU)); - cantFail(MainDylib.define(BarMU)); - cantFail(FarDylib.define(FarMU)); + SMDiagnostic E; + std::vector Cs(3); + std::vector Bs{Foo, Bar, Buz}; + std::vector Ds{&Main, &Extra1, &Extra2}; + + for (size_t i = 0; i < 3; i++) + cantFail(Jit->addIRModule(*Ds[i], parseIR(Bs[i], E, Cs[i]))); + + JITEvaluatedSymbol FooSym = cantFail(Jit->lookup("foo")); + EXPECT_EQ(1, Exec(FooSym)); - // Foo depdens on both, Bar and Far. - CommonES.lookup({&MainDylib}, {FooName}, nullptr, nullptr, NoDependenciesToRegister); - FooMR->addDependenciesForAll({{&MainDylib, SymbolNameSet({BarName})}}); - FooMR->addDependenciesForAll({{&FarDylib, SymbolNameSet({FarName})}}); + JITEvaluatedSymbol BarSym = cantFail(Jit->lookup(Extra1, "bar")); + EXPECT_EQ(1, Exec(BarSym)); + + // Allow buz to resolve foo and bar from the other dylibs: + Extra2.addToSearchOrder(Main); + Extra2.addToSearchOrder(Extra1); + + JITEvaluatedSymbol BuzSym = cantFail(Jit->lookup(Extra2, "buz")); + EXPECT_EQ(2, Exec(BuzSym)); +} + +//===----------------------------------------------------------------------===// - //std::mutex MUAccess; - CommonES.setDispatchMaterialization( +TEST(LLJIT, ThreadedSingleDylib) { + std::unique_ptr Jit = Init(); + ExecutionSession &ES = Jit->getExecutionSession(); + + SMDiagnostic E; + std::vector Ts; + std::vector Cs(3); + std::vector Bs{Foo, Bar, Buz}; + + for (size_t i = 0; i < 3; i++) + cantFail(Jit->addIRModule(parseIR(Bs[i], E, Cs[i]))); + + // Dispatch compile job to different threads. + std::vector Ms; + static constexpr size_t MaxMaterializerThreads = 3; + Ms.reserve(MaxMaterializerThreads); + + ES.setDispatchMaterialization( [&](JITDylib &JD, std::unique_ptr MU) { - //std::lock_guard Lock(MUAccess); - MU->doMaterialize(JD); + assert(Ms.size() < MaxMaterializerThreads && + "More slots used than preallocated"); + + auto SMU = std::shared_ptr(std::move(MU)); + Ms.emplace_back([SMU, &JD]() { + SMU->doMaterialize(JD); + }); }); - SMDiagnostic E; - LLVMContext FooCtx, BarCtx, FarCtx; - cantFail(JIT->addIRModule(parseIR(MemoryBufferRef(FooIR, "Foo"), E, FooCtx))); - cantFail(JIT->addIRModule(parseIR(MemoryBufferRef(BarIR, "Bar"), E, BarCtx))); - cantFail(JIT->addIRModule(parseIR(MemoryBufferRef(FarIR, "Far"), E, FarCtx))); + // Run functions in different threads. + Ts.emplace_back([&Jit]() { + JITEvaluatedSymbol FooSym = cantFail(Jit->lookup("foo")); + EXPECT_EQ(1, Exec(FooSym)); // Automatically waits for materializer. + }); - using Sig_t = int(); + Ts.emplace_back([&Jit]() { + JITEvaluatedSymbol BarSym = cantFail(Jit->lookup("bar")); + EXPECT_EQ(1, Exec(BarSym)); // Automatically waits for materializer. + }); + + // FIXME: Lookup for "buz" can't run in parallel. + JoinAll(Ts); + JoinAll(Ms); - std::thread RunFoo([&JIT]() { - JITEvaluatedSymbol FooSym = cantFail(JIT->lookup("foo")); - EXPECT_EQ(2, ((Sig_t *)FooSym.getAddress())()); + Ts.emplace_back([&Jit]() { + JITEvaluatedSymbol BuzSym = cantFail(Jit->lookup("buz")); + EXPECT_EQ(2, Exec(BuzSym)); // Automatically waits for materializer. }); + + JoinAll(Ts); + JoinAll(Ms); +} + +//===----------------------------------------------------------------------===// + +TEST(LLJIT, ThreadedMultiDylib) { + std::unique_ptr Jit = Init(); + ExecutionSession &ES = Jit->getExecutionSession(); + + JITDylib &Main = Jit->getMainJITDylib(); + JITDylib &Extra1 = ES.createJITDylib("1"); + JITDylib &Extra2 = ES.createJITDylib("2"); + Main.addToSearchOrder(Extra1); + Main.addToSearchOrder(Extra2); - /*std::thread RunBar([&JIT]() { - JITEvaluatedSymbol BarSym = cantFail(JIT->lookup("bar")); - EXPECT_EQ(1, ((Sig_t *)BarSym.getAddress())()); + SMDiagnostic E; + std::vector Ts; + std::vector Cs(3); + std::vector Bs{Foo, Bar, Buz}; + std::vector Ds{&Main, &Extra1, &Extra2}; + + for (size_t i = 0; i < 3; i++) + cantFail(Jit->addIRModule(*Ds[i], parseIR(Bs[i], E, Cs[i]))); + + // Dispatch compile job to different threads. + std::vector Ms; + static constexpr size_t MaxMaterializerThreads = 3; + Ms.reserve(MaxMaterializerThreads); + + ES.setDispatchMaterialization( + [&](JITDylib &JD, std::unique_ptr MU) { + assert(Ms.size() < MaxMaterializerThreads && + "More slots used than preallocated"); + + auto SMU = std::shared_ptr(std::move(MU)); + Ms.emplace_back([SMU, &JD]() { + SMU->doMaterialize(JD); + }); + }); + + // Run functions in different threads. + Ts.emplace_back([&]() { + JITEvaluatedSymbol FooSym = cantFail(Jit->lookup("foo")); + EXPECT_EQ(1, Exec(FooSym)); // Automatically waits for materializer. }); - RunBar.join(); - */ + Ts.emplace_back([&]() { + JITEvaluatedSymbol BarSym = cantFail(Jit->lookup(Extra1, "bar")); + EXPECT_EQ(1, Exec(BarSym)); // Automatically waits for materializer. + }); + + // FIXME: Lookup for "buz" can't run in parallel. + JoinAll(Ts); + JoinAll(Ms); + + // Allow buz to resolve foo and bar from the other dylibs. + Extra2.addToSearchOrder(Main); + Extra2.addToSearchOrder(Extra1); + + Ts.emplace_back([&]() { + JITEvaluatedSymbol BuzSym = cantFail(Jit->lookup(Extra2, "buz")); + EXPECT_EQ(2, Exec(BuzSym)); // Automatically waits for materializer. + }); - RunFoo.join(); + JoinAll(Ts); + JoinAll(Ms); }