Orc JIT now takes the ownership of the llvm::Module.
The old JIT infrastructure assumed shared ownership of the llvm::Module implemented via shared_ptr. This guaranteed each client can have a uniform view of the llvm::Module. The new infrastructure claims ownership of the object and even more transfer ownership when the llvm::Module travels through different layers of the JIT. The claimed advantage is better thread safety. The new logic defines away an important property which cling has been built around, that is the shared symbol object ownership. This patch makes the cling::Transaction the owner of the llvm::Module. The ownership is transfered when we want the JIT to 'emit' the module. Fortunately, there is a JIT callback which can transfer back the ownership to the transaction. This preserves some consistency, however, makes some operations unsafe. For example, we cannot rely on Transaction::getModule when the module is being handed to the JIT. This patch tries to adapt to the new infrastructure by reducing the dependency on the transaction's llvm::Module in favor of relying more on the information available in the execution engine (eg. at_exit handling).
This commit is contained in:
parent
2d1a60d163
commit
fa7c45b1ca
@ -139,9 +139,6 @@ namespace cling {
|
||||
|
||||
unsigned m_IssuedDiags : 2;
|
||||
|
||||
/// If we still own the llvm::Module (m_Module) or it was taken by the EE.
|
||||
unsigned m_OwnedLLVMModule : 1;
|
||||
|
||||
///\brief Options controlling the transformers and code generator.
|
||||
///
|
||||
CompilationOptions m_Opts;
|
||||
@ -153,7 +150,7 @@ namespace cling {
|
||||
|
||||
///\brief The llvm Module containing the information that we will revert
|
||||
///
|
||||
llvm::Module* m_Module;
|
||||
std::unique_ptr<llvm::Module> m_Module;
|
||||
|
||||
///\brief The Executor to use m_ExeUnload on.
|
||||
///
|
||||
@ -469,20 +466,12 @@ namespace cling {
|
||||
m_NestedTransactions->clear();
|
||||
}
|
||||
|
||||
llvm::Module* getModule() const { return m_Module; }
|
||||
std::unique_ptr<llvm::Module> takeModule() {
|
||||
assert(m_Module && "No module");
|
||||
assert(m_OwnedLLVMModule && "No ownership to take");
|
||||
m_OwnedLLVMModule = false;
|
||||
return std::unique_ptr<llvm::Module>(m_Module);
|
||||
}
|
||||
void setModule(std::unique_ptr<llvm::Module> M) {
|
||||
if (m_Module && m_OwnedLLVMModule) {
|
||||
delete m_Module;
|
||||
}
|
||||
m_Module = M.release();
|
||||
m_OwnedLLVMModule = true;
|
||||
llvm::Module* getModule() const { return m_Module.get(); }
|
||||
std::unique_ptr<llvm::Module> takeModule () {
|
||||
assert(getModule());
|
||||
return std::move(m_Module);
|
||||
}
|
||||
void setModule(std::unique_ptr<llvm::Module> M) { m_Module = std::move(M); }
|
||||
|
||||
IncrementalExecutor* getExecutor() const { return m_Exe; }
|
||||
|
||||
|
@ -102,7 +102,13 @@ IncrementalExecutor::IncrementalExecutor(clang::DiagnosticsEngine& diags,
|
||||
CI.getTargetOpts(),
|
||||
CI.getLangOpts(),
|
||||
*TM));
|
||||
m_JIT.reset(new IncrementalJIT(*this, std::move(TM)));
|
||||
auto RetainOwnership =
|
||||
[this](llvm::orc::VModuleKey K, std::unique_ptr<Module> M) -> void {
|
||||
assert (m_PendingModules.count(K) && "Unable to find the module");
|
||||
m_PendingModules[K]->setModule(std::move(M));
|
||||
m_PendingModules.erase(K);
|
||||
};
|
||||
m_JIT.reset(new IncrementalJIT(*this, std::move(TM), RetainOwnership));
|
||||
}
|
||||
|
||||
// Keep in source: ~unique_ptr<ClingJIT> needs ClingJIT
|
||||
@ -130,10 +136,10 @@ void IncrementalExecutor::runAtExitFuncs() {
|
||||
}
|
||||
|
||||
void IncrementalExecutor::AddAtExitFunc(void (*func)(void*), void* arg,
|
||||
const llvm::Module* M) {
|
||||
const Transaction* T) {
|
||||
// Register a CXAAtExit function
|
||||
cling::internal::SpinLockGuard slg(m_AtExitFuncsSpinLock);
|
||||
m_AtExitFuncs[M].emplace_back(func, arg);
|
||||
m_AtExitFuncs[T].emplace_back(func, arg);
|
||||
}
|
||||
|
||||
void unresolvedSymbol()
|
||||
@ -205,11 +211,24 @@ freeCallersOfUnresolvedSymbols(llvm::SmallVectorImpl<llvm::Function*>&
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool isPracticallyEmptyModule(const llvm::Module* M) {
|
||||
return M->empty() && M->global_empty() && M->alias_empty();
|
||||
}
|
||||
|
||||
|
||||
IncrementalExecutor::ExecutionResult
|
||||
IncrementalExecutor::runStaticInitializersOnce(const Transaction& T) const {
|
||||
IncrementalExecutor::runStaticInitializersOnce(Transaction& T) {
|
||||
llvm::Module* m = T.getModule();
|
||||
assert(m && "Module must not be null");
|
||||
|
||||
if (isPracticallyEmptyModule(m))
|
||||
return kExeSuccess;
|
||||
|
||||
llvm::orc::VModuleKey K =
|
||||
emitModule(T.takeModule(), T.getCompilationOpts().OptLevel);
|
||||
m_PendingModules[K] = &T;
|
||||
|
||||
|
||||
// We don't care whether something was unresolved before.
|
||||
m_unresolvedSymbols.clear();
|
||||
|
||||
@ -303,7 +322,7 @@ void IncrementalExecutor::runAndRemoveStaticDestructors(Transaction* T) {
|
||||
AtExitFunctions::mapped_type Local;
|
||||
{
|
||||
cling::internal::SpinLockGuard slg(m_AtExitFuncsSpinLock);
|
||||
auto Itr = m_AtExitFuncs.find(T->getModule());
|
||||
auto Itr = m_AtExitFuncs.find(T);
|
||||
if (Itr == m_AtExitFuncs.end()) return;
|
||||
m_AtExitFuncs.erase(Itr, &Local);
|
||||
} // end of spin lock lifetime block.
|
||||
@ -369,16 +388,15 @@ void* IncrementalExecutor::getAddressOfGlobal(llvm::StringRef symbolName,
|
||||
}
|
||||
|
||||
void*
|
||||
IncrementalExecutor::getPointerToGlobalFromJIT(const llvm::GlobalValue& GV) const {
|
||||
// Get the function / variable pointer referenced by GV.
|
||||
IncrementalExecutor::getPointerToGlobalFromJIT(llvm::StringRef name) const {
|
||||
// Get the function / variable pointer referenced by name.
|
||||
|
||||
// We don't care whether something was unresolved before.
|
||||
m_unresolvedSymbols.clear();
|
||||
|
||||
void* addr = (void*)m_JIT->getSymbolAddress(GV.getName(),
|
||||
false /*no dlsym*/);
|
||||
void* addr = (void*)m_JIT->getSymbolAddress(name, false /*no dlsym*/);
|
||||
|
||||
if (diagnoseUnresolvedSymbols(GV.getName(), "symbol"))
|
||||
if (diagnoseUnresolvedSymbols(name, "symbol"))
|
||||
return 0;
|
||||
return addr;
|
||||
}
|
||||
|
@ -114,12 +114,10 @@ namespace cling {
|
||||
std::atomic_flag m_AtExitFuncsSpinLock; // MSVC doesn't support = ATOMIC_FLAG_INIT;
|
||||
|
||||
///\brief Function registered via __cxa_atexit, atexit, or one of
|
||||
/// it's C++ overloads that should be run when a module is unloaded.
|
||||
/// it's C++ overloads that should be run when a transaction is unloaded.
|
||||
///
|
||||
// FIXME: We should probably try using a weak_ptr instead of a shared_ptr.
|
||||
typedef utils::OrderedMap<const llvm::Module*,
|
||||
std::vector<CXAAtExitElement>>
|
||||
AtExitFunctions;
|
||||
using AtExitFunctions =
|
||||
utils::OrderedMap<const Transaction*, std::vector<CXAAtExitElement>>;
|
||||
AtExitFunctions m_AtExitFuncs;
|
||||
|
||||
///\brief Modules to emit upon the next call to the JIT.
|
||||
@ -141,7 +139,9 @@ namespace cling {
|
||||
clang::DiagnosticsEngine& m_Diags;
|
||||
#endif
|
||||
|
||||
|
||||
///\brief The list of llvm::Module-s to return the transaction
|
||||
/// after the JIT has emitted them.
|
||||
std::map<llvm::orc::VModuleKey, Transaction*> m_PendingModules;
|
||||
public:
|
||||
enum ExecutionResult {
|
||||
kExeSuccess,
|
||||
@ -172,7 +172,7 @@ namespace cling {
|
||||
}
|
||||
|
||||
///\brief Run the static initializers of all modules collected to far.
|
||||
ExecutionResult runStaticInitializersOnce(const Transaction& T) const;
|
||||
ExecutionResult runStaticInitializersOnce(Transaction& T);
|
||||
|
||||
///\brief Runs all destructors bound to the given transaction and removes
|
||||
/// them from the list.
|
||||
@ -196,18 +196,6 @@ namespace cling {
|
||||
///
|
||||
bool addSymbol(const char* Name, void* Address, bool JIT = false) const;
|
||||
|
||||
///\brief Emit a llvm::Module to the JIT.
|
||||
///
|
||||
/// @param[in] module - The module to pass to the execution engine.
|
||||
/// @param[in] optLevel - The optimization level to be used.
|
||||
void
|
||||
emitModule(std::unique_ptr<llvm::Module> module, int optLevel) const {
|
||||
if (m_BackendPasses)
|
||||
m_BackendPasses->runOnModule(*module, optLevel);
|
||||
|
||||
m_JIT->addModule(std::move(module));
|
||||
}
|
||||
|
||||
///\brief Tells the execution to run all registered atexit functions once.
|
||||
///
|
||||
/// This rountine should be used with caution only when an external process
|
||||
@ -227,7 +215,7 @@ namespace cling {
|
||||
/// JIT symbols might not be immediately convertible to e.g. a function
|
||||
/// pointer as their call setup is different.
|
||||
///
|
||||
///\param[in] mangledName - the globa's name
|
||||
///\param[in] mangledName - the global's name
|
||||
///\param[out] fromJIT - whether the symbol was JITted.
|
||||
///
|
||||
void*
|
||||
@ -237,18 +225,30 @@ namespace cling {
|
||||
/// opposed to dynamic libraries). Forces the emission of the symbol if
|
||||
/// it has not happened yet.
|
||||
///
|
||||
///param[in] GV - global value for which the address will be returned.
|
||||
void* getPointerToGlobalFromJIT(const llvm::GlobalValue& GV) const;
|
||||
///param[in] name - the mangled name of the global.
|
||||
void* getPointerToGlobalFromJIT(llvm::StringRef name) const;
|
||||
|
||||
///\brief Keep track of the entities whose dtor we need to call.
|
||||
///
|
||||
void AddAtExitFunc(void (*func)(void*), void* arg, const llvm::Module* M);
|
||||
void AddAtExitFunc(void (*func)(void*), void* arg, const Transaction* T);
|
||||
|
||||
///\brief Try to resolve a symbol through our LazyFunctionCreators;
|
||||
/// print an error message if that fails.
|
||||
void* NotifyLazyFunctionCreators(const std::string&) const;
|
||||
|
||||
private:
|
||||
///\brief Emit a llvm::Module to the JIT.
|
||||
///
|
||||
/// @param[in] module - The module to pass to the execution engine.
|
||||
/// @param[in] optLevel - The optimization level to be used.
|
||||
llvm::orc::VModuleKey
|
||||
emitModule(std::unique_ptr<llvm::Module> module, int optLevel) const {
|
||||
if (m_BackendPasses)
|
||||
m_BackendPasses->runOnModule(*module, optLevel);
|
||||
|
||||
return m_JIT->addModule(std::move(module));
|
||||
}
|
||||
|
||||
///\brief Report and empty m_unresolvedSymbols.
|
||||
///\return true if m_unresolvedSymbols was non-empty.
|
||||
bool diagnoseUnresolvedSymbols(llvm::StringRef trigger,
|
||||
|
@ -272,7 +272,8 @@ public:
|
||||
}; // class Azog
|
||||
|
||||
IncrementalJIT::IncrementalJIT(IncrementalExecutor& exe,
|
||||
std::unique_ptr<TargetMachine> TM):
|
||||
std::unique_ptr<TargetMachine> TM,
|
||||
CompileLayerT::NotifyCompiledCallback NCC):
|
||||
m_Parent(exe),
|
||||
m_TM(std::move(TM)),
|
||||
m_TMDataLayout(m_TM->createDataLayout()),
|
||||
@ -341,6 +342,9 @@ IncrementalJIT::IncrementalJIT(IncrementalExecutor& exe,
|
||||
m_NotifyObjectLoaded, NotifyFinalizedT(*this)),
|
||||
m_CompileLayer(m_ObjectLayer, llvm::orc::SimpleCompiler(*m_TM)),
|
||||
m_LazyEmitLayer(m_CompileLayer) {
|
||||
|
||||
m_CompileLayer.setNotifyCompiled(NCC);
|
||||
|
||||
// Libraries might get exposed through ExposeHiddenSharedLibrarySymbols(),
|
||||
// make them available to the JIT, even though their symbols cannot be
|
||||
// resolved through the process.
|
||||
@ -443,7 +447,8 @@ IncrementalJIT::getSymbolAddressWithoutMangling(const std::string& Name,
|
||||
return llvm::JITSymbol(nullptr);
|
||||
}
|
||||
|
||||
void IncrementalJIT::addModule(std::unique_ptr<llvm::Module> module) {
|
||||
llvm::orc::VModuleKey
|
||||
IncrementalJIT::addModule(std::unique_ptr<llvm::Module> module) {
|
||||
// If this module doesn't have a DataLayout attached then attach the
|
||||
// default.
|
||||
module->setDataLayout(m_TMDataLayout);
|
||||
@ -466,6 +471,7 @@ void IncrementalJIT::addModule(std::unique_ptr<llvm::Module> module) {
|
||||
llvm::orc::VModuleKey K = m_ES.allocateVModule();
|
||||
llvm::cantFail(m_LazyEmitLayer.addModule(K, std::move(module)));
|
||||
m_UnloadPoints[module.get()] = K;
|
||||
return K;
|
||||
}
|
||||
|
||||
llvm::Error
|
||||
|
@ -175,7 +175,8 @@ private:
|
||||
|
||||
public:
|
||||
IncrementalJIT(IncrementalExecutor& exe,
|
||||
std::unique_ptr<llvm::TargetMachine> TM);
|
||||
std::unique_ptr<llvm::TargetMachine> TM,
|
||||
CompileLayerT::NotifyCompiledCallback NCF);
|
||||
|
||||
///\brief Get the address of a symbol from the JIT or the memory manager,
|
||||
/// mangling the name as needed. Use this to resolve symbols as coming
|
||||
@ -203,7 +204,7 @@ public:
|
||||
llvm::JITSymbol getSymbolAddressWithoutMangling(const std::string& Name,
|
||||
bool AlsoInProcess);
|
||||
|
||||
void addModule(std::unique_ptr<llvm::Module> module);
|
||||
llvm::orc::VModuleKey addModule(std::unique_ptr<llvm::Module> module);
|
||||
llvm::Error removeModule(const llvm::Module* module);
|
||||
|
||||
IncrementalExecutor& getParent() const { return m_Parent; }
|
||||
|
@ -88,10 +88,6 @@ namespace {
|
||||
}
|
||||
return cling::Interpreter::kExeSuccess;
|
||||
}
|
||||
|
||||
static bool isPracticallyEmptyModule(const llvm::Module* M) {
|
||||
return M->empty() && M->global_empty() && M->alias_empty();
|
||||
}
|
||||
} // unnamed namespace
|
||||
|
||||
namespace cling {
|
||||
@ -316,25 +312,13 @@ namespace cling {
|
||||
// Now that the transactions have been commited, force symbol emission
|
||||
// and overrides.
|
||||
if (!isInSyntaxOnlyMode() && !m_Opts.CompilerOpts.CUDADevice) {
|
||||
if (const Transaction* T = getLastTransaction()) {
|
||||
if (auto M = T->getModule()) {
|
||||
for (const llvm::StringRef& Sym : Syms) {
|
||||
const llvm::GlobalValue* GV = M->getNamedValue(Sym);
|
||||
#if defined(__linux__)
|
||||
// libstdc++ mangles at_quick_exit on Linux when g++ < 5
|
||||
if (!GV && Sym.equals("at_quick_exit"))
|
||||
GV = M->getNamedValue("_Z13at_quick_exitPFvvE");
|
||||
#endif
|
||||
if (GV) {
|
||||
if (void* Addr = m_Executor->getPointerToGlobalFromJIT(*GV))
|
||||
m_Executor->addSymbol(Sym.str().c_str(), Addr, true);
|
||||
else
|
||||
cling::errs() << Sym << " not defined\n";
|
||||
} else
|
||||
cling::errs() << Sym << " not in Module!\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (void* Addr = m_Executor->getPointerToGlobalFromJIT("at_quick_exit"))
|
||||
m_Executor->addSymbol("at_quick_exit", Addr, true);
|
||||
// libstdc++ mangles at_quick_exit on Linux when g++ < 5
|
||||
else if (void* Addr = m_Executor->getPointerToGlobalFromJIT("_Z13at_quick_exitPFvvE"))
|
||||
m_Executor->addSymbol("_Z13at_quick_exitPFvvE", Addr, true);
|
||||
else
|
||||
cling::errs() << "'at_quick_exit' not defined\n";
|
||||
}
|
||||
|
||||
m_IncrParser->SetTransformers(parentInterp);
|
||||
@ -1285,15 +1269,9 @@ namespace cling {
|
||||
const FunctionDecl* FD = DeclareCFunction(name, code, withAccessControl, T);
|
||||
if (!FD || !T)
|
||||
return 0;
|
||||
//
|
||||
// Get the wrapper function pointer
|
||||
// from the ExecutionEngine (the JIT).
|
||||
//
|
||||
if (const llvm::GlobalValue* GV
|
||||
= T->getModule()->getNamedValue(name))
|
||||
return m_Executor->getPointerToGlobalFromJIT(*GV);
|
||||
|
||||
return 0;
|
||||
// Get the wrapper function pointer from the ExecutionEngine (the JIT).
|
||||
return m_Executor->getPointerToGlobalFromJIT(name);
|
||||
}
|
||||
|
||||
void*
|
||||
@ -1685,13 +1663,10 @@ namespace cling {
|
||||
|
||||
IncrementalExecutor::ExecutionResult ExeRes
|
||||
= IncrementalExecutor::kExeSuccess;
|
||||
if (!isPracticallyEmptyModule(T.getModule())) {
|
||||
m_Executor->emitModule(T.takeModule(), T.getCompilationOpts().OptLevel);
|
||||
|
||||
// Forward to IncrementalExecutor; should not be called by
|
||||
// anyone except for IncrementalParser.
|
||||
ExeRes = m_Executor->runStaticInitializersOnce(T);
|
||||
}
|
||||
// Forward to IncrementalExecutor; should not be called by
|
||||
// anyone except for IncrementalParser.
|
||||
ExeRes = m_Executor->runStaticInitializersOnce(T);
|
||||
|
||||
return ConvertExecutionResult(ExeRes);
|
||||
}
|
||||
@ -1736,7 +1711,7 @@ namespace cling {
|
||||
}
|
||||
|
||||
void Interpreter::AddAtExitFunc(void (*Func) (void*), void* Arg) {
|
||||
m_Executor->AddAtExitFunc(Func, Arg, getLatestTransaction()->getModule());
|
||||
m_Executor->AddAtExitFunc(Func, Arg, getLatestTransaction());
|
||||
}
|
||||
|
||||
void Interpreter::runAtExitFuncs() {
|
||||
|
@ -40,7 +40,6 @@ namespace cling {
|
||||
m_Parent = 0;
|
||||
m_State = kCollecting;
|
||||
m_IssuedDiags = kNone;
|
||||
m_OwnedLLVMModule = false;
|
||||
m_Opts = CompilationOptions();
|
||||
m_DefinitionShadowNS = 0;
|
||||
m_Module = 0;
|
||||
|
@ -10,8 +10,8 @@
|
||||
// RUN: cat %s | %cling -L %T -Xclang -verify 2>&1 | FileCheck %s
|
||||
|
||||
#pragma cling load("DoesNotExistPleaseRecover")
|
||||
// expected-error@input_line_13:1{{expected is not a library; did you mean #include "DoesNotExistPleaseRecover"}}
|
||||
// expected-error@input_line_13:1{{'DoesNotExistPleaseRecover' file not found}}
|
||||
// expected-error@input_line_12:1{{expected is not a library; did you mean #include "DoesNotExistPleaseRecover"}}
|
||||
// expected-error@input_line_12:1{{'DoesNotExistPleaseRecover' file not found}}
|
||||
|
||||
#pragma cling load("libcall_lib")
|
||||
extern "C" int cling_testlibrary_function();
|
||||
|
Loading…
Reference in New Issue
Block a user