1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-22 02:33:06 +01:00

[ORC] Update LLJIT to automatically run specially named initializer functions.

The GenericLLVMIRPlatformSupport class runs a transform on all LLVM IR added to
the LLJIT instance to replace instances of llvm.global_ctors with a specially
named function that runs the corresponing static initializers (See
(GlobalCtorDtorScraper from lib/ExecutionEngine/Orc/LLJIT.cpp). This patch
updates the GenericIRPlatform class to check for this specially named function
in other materialization units that are added to the JIT and, if found, add
the function to the initializer work queue. Doing this allows object files
that were compiled from IR and cached to be reloaded in subsequent JIT sessions
without their initializers being skipped.

To enable testing this patch also updates the lli tool's -jit-kind=orc-lazy mode
to respect the -enable-cache-manager and -object-cache-dir options, and modifies
the CompileOnDemandLayer to rename extracted submodules to include a hash of the
names of their symbol definitions. This allows a simple object caching scheme
based on module names (which was already implemented in lli) to work with the
lazy JIT.
This commit is contained in:
Lang Hames 2020-02-22 09:49:55 -08:00
parent 46f1687baa
commit b6c9039962
4 changed files with 155 additions and 34 deletions

View File

@ -7,9 +7,12 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h" #include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/IR/Mangler.h" #include "llvm/IR/Mangler.h"
#include "llvm/IR/Module.h" #include "llvm/IR/Module.h"
#include "llvm/Support/FormatVariadic.h"
using namespace llvm; using namespace llvm;
using namespace llvm::orc; using namespace llvm::orc;
@ -294,29 +297,52 @@ void CompileOnDemandLayer::emitPartition(
// //
// FIXME: We apply this promotion once per partitioning. It's safe, but // FIXME: We apply this promotion once per partitioning. It's safe, but
// overkill. // overkill.
auto ExtractedTSM = auto ExtractedTSM =
TSM.withModuleDo([&](Module &M) -> Expected<ThreadSafeModule> { TSM.withModuleDo([&](Module &M) -> Expected<ThreadSafeModule> {
auto PromotedGlobals = PromoteSymbols(M); auto PromotedGlobals = PromoteSymbols(M);
if (!PromotedGlobals.empty()) { if (!PromotedGlobals.empty()) {
MangleAndInterner Mangle(ES, M.getDataLayout()); MangleAndInterner Mangle(ES, M.getDataLayout());
SymbolFlagsMap SymbolFlags; SymbolFlagsMap SymbolFlags;
for (auto &GV : PromotedGlobals) IRSymbolMapper::add(ES, *getManglingOptions(),
SymbolFlags[Mangle(GV->getName())] = PromotedGlobals, SymbolFlags);
JITSymbolFlags::fromGlobalValue(*GV);
if (auto Err = R.defineMaterializing(SymbolFlags)) if (auto Err = R.defineMaterializing(SymbolFlags))
return std::move(Err); return std::move(Err);
} }
expandPartition(*GVsToExtract); expandPartition(*GVsToExtract);
// Submodule name is given by hashing the names of the globals.
std::string SubModuleName;
{
std::vector<const GlobalValue*> HashGVs;
HashGVs.reserve(GVsToExtract->size());
for (auto *GV : *GVsToExtract)
HashGVs.push_back(GV);
llvm::sort(HashGVs, [](const GlobalValue *LHS, const GlobalValue *RHS) {
return LHS->getName() < RHS->getName();
});
hash_code HC(0);
for (auto *GV : HashGVs) {
assert(GV->hasName() && "All GVs to extract should be named by now");
auto GVName = GV->getName();
HC = hash_combine(HC, hash_combine_range(GVName.begin(), GVName.end()));
}
raw_string_ostream(SubModuleName)
<< ".submodule."
<< formatv(sizeof(size_t) == 8 ? "{0:x16}" : "{0:x8}",
static_cast<size_t>(HC))
<< ".ll";
}
// Extract the requested partiton (plus any necessary aliases) and // Extract the requested partiton (plus any necessary aliases) and
// put the rest back into the impl dylib. // put the rest back into the impl dylib.
auto ShouldExtract = [&](const GlobalValue &GV) -> bool { auto ShouldExtract = [&](const GlobalValue &GV) -> bool {
return GVsToExtract->count(&GV); return GVsToExtract->count(&GV);
}; };
return extractSubModule(TSM, ".submodule", ShouldExtract); return extractSubModule(TSM, SubModuleName , ShouldExtract);
}); });
if (!ExtractedTSM) { if (!ExtractedTSM) {

View File

@ -107,12 +107,16 @@ private:
/// llvm.global_ctors. /// llvm.global_ctors.
class GlobalCtorDtorScraper { class GlobalCtorDtorScraper {
public: public:
GlobalCtorDtorScraper(GenericLLVMIRPlatformSupport &PS) : PS(PS) {}
GlobalCtorDtorScraper(GenericLLVMIRPlatformSupport &PS,
StringRef InitFunctionPrefix)
: PS(PS), InitFunctionPrefix(InitFunctionPrefix) {}
Expected<ThreadSafeModule> operator()(ThreadSafeModule TSM, Expected<ThreadSafeModule> operator()(ThreadSafeModule TSM,
MaterializationResponsibility &R); MaterializationResponsibility &R);
private: private:
GenericLLVMIRPlatformSupport &PS; GenericLLVMIRPlatformSupport &PS;
StringRef InitFunctionPrefix;
}; };
/// Generic IR Platform Support /// Generic IR Platform Support
@ -125,12 +129,14 @@ public:
// GenericLLVMIRPlatform &P) : P(P) { // GenericLLVMIRPlatform &P) : P(P) {
GenericLLVMIRPlatformSupport(LLJIT &J) : J(J) { GenericLLVMIRPlatformSupport(LLJIT &J) : J(J) {
MangleAndInterner Mangle(getExecutionSession(), J.getDataLayout());
InitFunctionPrefix = Mangle("__orc_init_func.");
getExecutionSession().setPlatform( getExecutionSession().setPlatform(
std::make_unique<GenericLLVMIRPlatform>(*this)); std::make_unique<GenericLLVMIRPlatform>(*this));
setInitTransform(J, GlobalCtorDtorScraper(*this)); setInitTransform(J, GlobalCtorDtorScraper(*this, *InitFunctionPrefix));
MangleAndInterner Mangle(getExecutionSession(), J.getDataLayout());
SymbolMap StdInterposes; SymbolMap StdInterposes;
StdInterposes[Mangle("__lljit.platform_support_instance")] = StdInterposes[Mangle("__lljit.platform_support_instance")] =
@ -169,6 +175,18 @@ public:
std::lock_guard<std::mutex> Lock(PlatformSupportMutex); std::lock_guard<std::mutex> Lock(PlatformSupportMutex);
if (auto &InitSym = MU.getInitializerSymbol()) if (auto &InitSym = MU.getInitializerSymbol())
InitSymbols[&JD].add(InitSym); InitSymbols[&JD].add(InitSym);
else {
// If there's no identified init symbol attached, but there is a symbol
// with the GenericIRPlatform::InitFunctionPrefix, then treat that as
// an init function. Add the symbol to both the InitSymbols map (which
// will trigger a lookup to materialize the module) and the InitFunctions
// map (which holds the names of the symbols to execute).
for (auto &KV : MU.getSymbols())
if ((*KV.first).startswith(*InitFunctionPrefix)) {
InitSymbols[&JD].add(KV.first);
InitFunctions[&JD].add(KV.first);
}
}
return Error::success(); return Error::success();
} }
@ -387,6 +405,7 @@ private:
std::mutex PlatformSupportMutex; std::mutex PlatformSupportMutex;
LLJIT &J; LLJIT &J;
SymbolStringPtr InitFunctionPrefix;
DenseMap<JITDylib *, SymbolLookupSet> InitSymbols; DenseMap<JITDylib *, SymbolLookupSet> InitSymbols;
DenseMap<JITDylib *, SymbolLookupSet> InitFunctions; DenseMap<JITDylib *, SymbolLookupSet> InitFunctions;
DenseMap<JITDylib *, SymbolLookupSet> DeInitFunctions; DenseMap<JITDylib *, SymbolLookupSet> DeInitFunctions;
@ -415,7 +434,7 @@ GlobalCtorDtorScraper::operator()(ThreadSafeModule TSM,
std::string InitFunctionName; std::string InitFunctionName;
raw_string_ostream(InitFunctionName) raw_string_ostream(InitFunctionName)
<< "__orc_init." << M.getModuleIdentifier(); << InitFunctionPrefix << M.getModuleIdentifier();
MangleAndInterner Mangle(PS.getExecutionSession(), M.getDataLayout()); MangleAndInterner Mangle(PS.getExecutionSession(), M.getDataLayout());
auto InternedName = Mangle(InitFunctionName); auto InternedName = Mangle(InitFunctionName);

View File

@ -0,0 +1,28 @@
; RUN: rm -rf %t
; RUN: mkdir -p %t
; RUN: lli -jit-kind=orc-lazy -enable-cache-manager -object-cache-dir=%t %s
; RUN: lli -jit-kind=orc-lazy -enable-cache-manager -object-cache-dir=%t %s
;
; Verify that LLJIT Platforms respect static initializers in cached objects.
; This IR file contains a static initializer that must execute for main to exit
; with value zero. The first execution will populate an object cache for the
; second. The initializer in the cached objects must also be successfully run
; for the test to pass.
@HasError = global i8 1, align 1
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @resetHasError, i8* null }]
define void @resetHasError() {
entry:
store i8 0, i8* @HasError, align 1
ret void
}
define i32 @main(i32 %argc, i8** %argv) #2 {
entry:
%0 = load i8, i8* @HasError, align 1
%tobool = trunc i8 %0 to i1
%conv = zext i1 %tobool to i32
ret i32 %conv
}

View File

@ -274,6 +274,7 @@ public:
SmallString<128> dir(sys::path::parent_path(CacheName)); SmallString<128> dir(sys::path::parent_path(CacheName));
sys::fs::create_directories(Twine(dir)); sys::fs::create_directories(Twine(dir));
} }
std::error_code EC; std::error_code EC;
raw_fd_ostream outfile(CacheName, EC, sys::fs::OF_None); raw_fd_ostream outfile(CacheName, EC, sys::fs::OF_None);
outfile.write(Obj.getBufferStart(), Obj.getBufferSize()); outfile.write(Obj.getBufferStart(), Obj.getBufferSize());
@ -306,14 +307,16 @@ private:
size_t PrefixLength = Prefix.length(); size_t PrefixLength = Prefix.length();
if (ModID.substr(0, PrefixLength) != Prefix) if (ModID.substr(0, PrefixLength) != Prefix)
return false; return false;
std::string CacheSubdir = ModID.substr(PrefixLength);
std::string CacheSubdir = ModID.substr(PrefixLength);
#if defined(_WIN32) #if defined(_WIN32)
// Transform "X:\foo" => "/X\foo" for convenience. // Transform "X:\foo" => "/X\foo" for convenience.
if (isalpha(CacheSubdir[0]) && CacheSubdir[1] == ':') { if (isalpha(CacheSubdir[0]) && CacheSubdir[1] == ':') {
CacheSubdir[1] = CacheSubdir[0]; CacheSubdir[1] = CacheSubdir[0];
CacheSubdir[0] = '/'; CacheSubdir[0] = '/';
} }
#endif #endif
CacheName = CacheDir + CacheSubdir; CacheName = CacheDir + CacheSubdir;
size_t pos = CacheName.rfind('.'); size_t pos = CacheName.rfind('.');
CacheName.replace(pos, CacheName.length() - pos, ".o"); CacheName.replace(pos, CacheName.length() - pos, ".o");
@ -777,30 +780,56 @@ Error loadDylibs() {
static void exitOnLazyCallThroughFailure() { exit(1); } static void exitOnLazyCallThroughFailure() { exit(1); }
Expected<orc::ThreadSafeModule>
loadModule(StringRef Path, orc::ThreadSafeContext TSCtx) {
SMDiagnostic Err;
auto M = parseIRFile(Path, Err, *TSCtx.getContext());
if (!M) {
std::string ErrMsg;
{
raw_string_ostream ErrMsgStream(ErrMsg);
Err.print("lli", ErrMsgStream);
}
return make_error<StringError>(std::move(ErrMsg), inconvertibleErrorCode());
}
if (EnableCacheManager)
M->setModuleIdentifier("file:" + M->getModuleIdentifier());
return orc::ThreadSafeModule(std::move(M), std::move(TSCtx));
}
int runOrcLazyJIT(const char *ProgName) { int runOrcLazyJIT(const char *ProgName) {
// Start setting up the JIT environment. // Start setting up the JIT environment.
// Parse the main module. // Parse the main module.
orc::ThreadSafeContext TSCtx(std::make_unique<LLVMContext>()); orc::ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());
SMDiagnostic Err; auto MainModule = ExitOnErr(loadModule(InputFile, TSCtx));
auto MainModule = parseIRFile(InputFile, Err, *TSCtx.getContext());
if (!MainModule) // Get TargetTriple and DataLayout from the main module if they're explicitly
reportError(Err, ProgName); // set.
Optional<Triple> TT;
Optional<DataLayout> DL;
MainModule.withModuleDo([&](Module &M) {
if (!M.getTargetTriple().empty())
TT = Triple(M.getTargetTriple());
if (!M.getDataLayout().isDefault())
DL = M.getDataLayout();
});
Triple TT(MainModule->getTargetTriple());
orc::LLLazyJITBuilder Builder; orc::LLLazyJITBuilder Builder;
Builder.setJITTargetMachineBuilder( Builder.setJITTargetMachineBuilder(
MainModule->getTargetTriple().empty() TT ? orc::JITTargetMachineBuilder(*TT)
? ExitOnErr(orc::JITTargetMachineBuilder::detectHost()) : ExitOnErr(orc::JITTargetMachineBuilder::detectHost()));
: orc::JITTargetMachineBuilder(TT));
TT = Builder.getJITTargetMachineBuilder()->getTargetTriple();
if (DL)
Builder.setDataLayout(DL);
if (!MArch.empty()) if (!MArch.empty())
Builder.getJITTargetMachineBuilder()->getTargetTriple().setArchName(MArch); Builder.getJITTargetMachineBuilder()->getTargetTriple().setArchName(MArch);
if (!MainModule->getDataLayout().isDefault())
Builder.setDataLayout(MainModule->getDataLayout());
Builder.getJITTargetMachineBuilder() Builder.getJITTargetMachineBuilder()
->setCPU(getCPUStr()) ->setCPU(getCPUStr())
.addFeatures(getFeatureList()) .addFeatures(getFeatureList())
@ -815,11 +844,34 @@ int runOrcLazyJIT(const char *ProgName) {
pointerToJITTargetAddress(exitOnLazyCallThroughFailure)); pointerToJITTargetAddress(exitOnLazyCallThroughFailure));
Builder.setNumCompileThreads(LazyJITCompileThreads); Builder.setNumCompileThreads(LazyJITCompileThreads);
// If the object cache is enabled then set a custom compile function
// creator to use the cache.
std::unique_ptr<LLIObjectCache> CacheManager;
if (EnableCacheManager) {
CacheManager = std::make_unique<LLIObjectCache>(ObjectCacheDir);
Builder.setCompileFunctionCreator(
[&](orc::JITTargetMachineBuilder JTMB)
-> Expected<std::unique_ptr<orc::IRCompileLayer::IRCompiler>> {
if (LazyJITCompileThreads > 0)
return std::make_unique<orc::ConcurrentIRCompiler>(std::move(JTMB),
CacheManager.get());
auto TM = JTMB.createTargetMachine();
if (!TM)
return TM.takeError();
return std::make_unique<orc::TMOwningSimpleCompiler>(std::move(*TM),
CacheManager.get());
});
}
// Set up LLJIT platform. // Set up LLJIT platform.
{ {
LLJITPlatform P = Platform; LLJITPlatform P = Platform;
if (P == LLJITPlatform::DetectHost) { if (P == LLJITPlatform::DetectHost) {
if (TT.isOSBinFormatMachO()) if (TT->isOSBinFormatMachO())
P = LLJITPlatform::MachO; P = LLJITPlatform::MachO;
else else
P = LLJITPlatform::GenericIR; P = LLJITPlatform::GenericIR;
@ -871,8 +923,7 @@ int runOrcLazyJIT(const char *ProgName) {
}))); })));
// Add the main module. // Add the main module.
ExitOnErr( ExitOnErr(J->addLazyIRModule(std::move(MainModule)));
J->addLazyIRModule(orc::ThreadSafeModule(std::move(MainModule), TSCtx)));
// Create JITDylibs and add any extra modules. // Create JITDylibs and add any extra modules.
{ {
@ -894,16 +945,13 @@ int runOrcLazyJIT(const char *ProgName) {
for (auto EMItr = ExtraModules.begin(), EMEnd = ExtraModules.end(); for (auto EMItr = ExtraModules.begin(), EMEnd = ExtraModules.end();
EMItr != EMEnd; ++EMItr) { EMItr != EMEnd; ++EMItr) {
auto M = parseIRFile(*EMItr, Err, *TSCtx.getContext()); auto M = ExitOnErr(loadModule(*EMItr, TSCtx));
if (!M)
reportError(Err, ProgName);
auto EMIdx = ExtraModules.getPosition(EMItr - ExtraModules.begin()); auto EMIdx = ExtraModules.getPosition(EMItr - ExtraModules.begin());
assert(EMIdx != 0 && "ExtraModule should have index > 0"); assert(EMIdx != 0 && "ExtraModule should have index > 0");
auto JDItr = std::prev(IdxToDylib.lower_bound(EMIdx)); auto JDItr = std::prev(IdxToDylib.lower_bound(EMIdx));
auto &JD = *JDItr->second; auto &JD = *JDItr->second;
ExitOnErr( ExitOnErr(J->addLazyIRModule(JD, std::move(M)));
J->addLazyIRModule(JD, orc::ThreadSafeModule(std::move(M), TSCtx)));
} }
for (auto EAItr = ExtraArchives.begin(), EAEnd = ExtraArchives.end(); for (auto EAItr = ExtraArchives.begin(), EAEnd = ExtraArchives.end();