llvm-project/llvm/examples/ThinLtoJIT/ThinLtoJIT.cpp

341 lines
12 KiB
C++

#include "ThinLtoJIT.h"
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/IndirectionUtils.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Host.h"
#include "ThinLtoDiscoveryThread.h"
#include "ThinLtoInstrumentationLayer.h"
#include "ThinLtoModuleIndex.h"
#include <set>
#include <string>
#include <thread>
#ifndef NDEBUG
#include <chrono>
#endif
#define DEBUG_TYPE "thinltojit"
namespace llvm {
namespace orc {
class ThinLtoDefinitionGenerator : public JITDylib::DefinitionGenerator {
public:
ThinLtoDefinitionGenerator(ThinLtoModuleIndex &GlobalIndex,
ThinLtoInstrumentationLayer &InstrumentationLayer,
ThinLtoJIT::AddModuleFunction AddModule,
char Prefix, bool AllowNudge, bool PrintStats)
: GlobalIndex(GlobalIndex), InstrumentationLayer(InstrumentationLayer),
AddModule(std::move(AddModule)), ManglePrefix(Prefix),
AllowNudgeIntoDiscovery(AllowNudge), PrintStats(PrintStats) {}
~ThinLtoDefinitionGenerator() {
if (PrintStats)
dump(errs());
}
Error tryToGenerate(LookupKind K, JITDylib &JD,
JITDylibLookupFlags JDLookupFlags,
const SymbolLookupSet &Symbols) override;
void dump(raw_ostream &OS) {
OS << format("Modules submitted synchronously: %d\n", NumModulesMissed);
}
private:
ThinLtoModuleIndex &GlobalIndex;
ThinLtoInstrumentationLayer &InstrumentationLayer;
ThinLtoJIT::AddModuleFunction AddModule;
char ManglePrefix;
bool AllowNudgeIntoDiscovery;
bool PrintStats;
unsigned NumModulesMissed{0};
// ThinLTO summaries encode unprefixed names.
StringRef stripGlobalManglePrefix(StringRef Symbol) const {
bool Strip = (ManglePrefix != '\0' && Symbol[0] == ManglePrefix);
return Strip ? StringRef(Symbol.data() + 1, Symbol.size() - 1) : Symbol;
}
};
Error ThinLtoDefinitionGenerator::tryToGenerate(
LookupKind K, JITDylib &JD, JITDylibLookupFlags JDLookupFlags,
const SymbolLookupSet &Symbols) {
std::set<StringRef> ModulePaths;
std::vector<GlobalValue::GUID> NewDiscoveryRoots;
for (const auto &KV : Symbols) {
StringRef UnmangledName = stripGlobalManglePrefix(*KV.first);
auto Guid = GlobalValue::getGUID(UnmangledName);
if (GlobalValueSummary *S = GlobalIndex.getSummary(Guid)) {
// We could have discovered it ahead of time.
LLVM_DEBUG(dbgs() << format("Failed to discover symbol: %s\n",
UnmangledName.str().c_str()));
ModulePaths.insert(S->modulePath());
if (AllowNudgeIntoDiscovery && isa<FunctionSummary>(S)) {
NewDiscoveryRoots.push_back(Guid);
}
}
}
NumModulesMissed += ModulePaths.size();
// Parse the requested modules if it hasn't happened yet.
GlobalIndex.scheduleModuleParsing(ModulePaths);
for (StringRef Path : ModulePaths) {
ThreadSafeModule TSM = GlobalIndex.takeModule(Path);
assert(TSM && "We own the session lock, no asynchronous access possible");
if (Error LoadErr = AddModule(std::move(TSM)))
// Failed to add the module to the session.
return LoadErr;
LLVM_DEBUG(dbgs() << "Generator: added " << Path << " synchronously\n");
}
// Requested functions that we failed to discover ahead of time, are likely
// close to the execution front. We can anticipate to run into them as soon
// as execution continues and trigger their discovery flags already now. This
// behavior is enabled with the 'allow-nudge' option and implemented below.
// On the one hand, it may give us a head start in a moment where discovery
// was lacking behind. On the other hand, we may bet on the wrong horse and
// waste extra time speculating in the wrong direction.
if (!NewDiscoveryRoots.empty()) {
assert(AllowNudgeIntoDiscovery);
InstrumentationLayer.nudgeIntoDiscovery(std::move(NewDiscoveryRoots));
}
return Error::success();
}
ThinLtoJIT::ThinLtoJIT(ArrayRef<std::string> InputFiles,
StringRef MainFunctionName, unsigned LookaheadLevels,
unsigned NumCompileThreads, unsigned NumLoadThreads,
unsigned DiscoveryFlagsPerBucket,
ExplicitMemoryBarrier MemFence,
bool AllowNudgeIntoDiscovery, bool PrintStats,
Error &Err) {
ErrorAsOutParameter ErrAsOutParam(&Err);
// Populate the module index, so we know which modules exist and we can find
// the one that defines the main function.
GlobalIndex = std::make_unique<ThinLtoModuleIndex>(ES, NumLoadThreads);
for (StringRef F : InputFiles) {
if (auto Err = GlobalIndex->add(F))
ES.reportError(std::move(Err));
}
// Load the module that defines the main function.
auto TSM = setupMainModule(MainFunctionName);
if (!TSM) {
Err = TSM.takeError();
return;
}
// Infer target-specific utils from the main module.
ThreadSafeModule MainModule = std::move(*TSM);
auto JTMB = setupTargetUtils(MainModule.getModuleUnlocked());
if (!JTMB) {
Err = JTMB.takeError();
return;
}
// Set up the JIT compile pipeline.
setupLayers(std::move(*JTMB), NumCompileThreads, DiscoveryFlagsPerBucket,
MemFence);
// We can use the mangler now. Remember the mangled name of the main function.
MainFunctionMangled = (*Mangle)(MainFunctionName);
// We are restricted to a single dylib currently. Add runtime overrides and
// symbol generators.
MainJD = &ES.createBareJITDylib("main");
Err = setupJITDylib(MainJD, AllowNudgeIntoDiscovery, PrintStats);
if (Err)
return;
// Spawn discovery thread and let it add newly discovered modules to the JIT.
setupDiscovery(MainJD, LookaheadLevels, PrintStats);
Err = AddModule(std::move(MainModule));
if (Err)
return;
if (AllowNudgeIntoDiscovery) {
auto MainFunctionGuid = GlobalValue::getGUID(MainFunctionName);
InstrumentationLayer->nudgeIntoDiscovery({MainFunctionGuid});
}
}
Expected<ThreadSafeModule> ThinLtoJIT::setupMainModule(StringRef MainFunction) {
Optional<StringRef> M = GlobalIndex->getModulePathForSymbol(MainFunction);
if (!M) {
std::string Buffer;
raw_string_ostream OS(Buffer);
OS << "No ValueInfo for symbol '" << MainFunction;
OS << "' in provided modules: ";
for (StringRef P : GlobalIndex->getAllModulePaths())
OS << P << " ";
OS << "\n";
return createStringError(inconvertibleErrorCode(), OS.str());
}
if (auto TSM = GlobalIndex->parseModuleFromFile(*M))
return std::move(TSM); // Not a redundant move: fix build on gcc-7.5
return createStringError(inconvertibleErrorCode(),
"Failed to parse main module");
}
Expected<JITTargetMachineBuilder> ThinLtoJIT::setupTargetUtils(Module *M) {
std::string T = M->getTargetTriple();
JITTargetMachineBuilder JTMB(Triple(T.empty() ? sys::getProcessTriple() : T));
// CallThroughManager is ABI-specific
auto LCTM = createLocalLazyCallThroughManager(
JTMB.getTargetTriple(), ES,
pointerToJITTargetAddress(exitOnLazyCallThroughFailure));
if (!LCTM)
return LCTM.takeError();
CallThroughManager = std::move(*LCTM);
// Use DataLayout or the given module or fall back to the host's default.
DL = DataLayout(M);
if (DL.getStringRepresentation().empty()) {
auto HostDL = JTMB.getDefaultDataLayoutForTarget();
if (!HostDL)
return HostDL.takeError();
DL = std::move(*HostDL);
if (Error Err = applyDataLayout(M))
return std::move(Err);
}
// Now that we know the target data layout we can setup the mangler.
Mangle = std::make_unique<MangleAndInterner>(ES, DL);
return JTMB;
}
Error ThinLtoJIT::applyDataLayout(Module *M) {
if (M->getDataLayout().isDefault())
M->setDataLayout(DL);
if (M->getDataLayout() != DL)
return make_error<StringError>(
"Added modules have incompatible data layouts",
inconvertibleErrorCode());
return Error::success();
}
static bool IsTrivialModule(MaterializationUnit *MU) {
StringRef ModuleName = MU->getName();
return ModuleName == "<Lazy Reexports>" || ModuleName == "<Reexports>" ||
ModuleName == "<Absolute Symbols>";
}
void ThinLtoJIT::setupLayers(JITTargetMachineBuilder JTMB,
unsigned NumCompileThreads,
unsigned DiscoveryFlagsPerBucket,
ExplicitMemoryBarrier MemFence) {
ObjLinkingLayer = std::make_unique<RTDyldObjectLinkingLayer>(
ES, []() { return std::make_unique<SectionMemoryManager>(); });
CompileLayer = std::make_unique<IRCompileLayer>(
ES, *ObjLinkingLayer, std::make_unique<ConcurrentIRCompiler>(JTMB));
InstrumentationLayer = std::make_unique<ThinLtoInstrumentationLayer>(
ES, *CompileLayer, MemFence, DiscoveryFlagsPerBucket);
OnDemandLayer = std::make_unique<CompileOnDemandLayer>(
ES, *InstrumentationLayer, *CallThroughManager,
createLocalIndirectStubsManagerBuilder(JTMB.getTargetTriple()));
// Don't break up modules. Insert stubs on module boundaries.
OnDemandLayer->setPartitionFunction(CompileOnDemandLayer::compileWholeModule);
// Delegate compilation to the thread pool.
CompileThreads = std::make_unique<ThreadPool>(
llvm::hardware_concurrency(NumCompileThreads));
ES.setDispatchMaterialization(
[this](std::unique_ptr<MaterializationUnit> MU,
MaterializationResponsibility MR) {
if (IsTrivialModule(MU.get())) {
// This should be quick and we may save a few session locks.
MU->materialize(std::move(MR));
} else {
// FIXME: Drop the std::shared_ptr workaround once ThreadPool::async()
// accepts llvm::unique_function to define jobs.
auto SharedMU = std::shared_ptr<MaterializationUnit>(std::move(MU));
auto SharedMR =
std::make_shared<MaterializationResponsibility>(std::move(MR));
CompileThreads->async(
[MU = std::move(SharedMU), MR = std::move(SharedMR)]() {
MU->materialize(std::move(*MR));
});
}
});
AddModule = [this](ThreadSafeModule TSM) -> Error {
assert(MainJD && "Setup MainJD JITDylib before calling");
Module *M = TSM.getModuleUnlocked();
if (Error Err = applyDataLayout(M))
return Err;
VModuleKey Id = GlobalIndex->getModuleId(M->getName());
return OnDemandLayer->add(*MainJD, std::move(TSM), Id);
};
}
void ThinLtoJIT::setupDiscovery(JITDylib *MainJD, unsigned LookaheadLevels,
bool PrintStats) {
JitRunning.store(true);
DiscoveryThreadWorker = std::make_unique<ThinLtoDiscoveryThread>(
JitRunning, ES, MainJD, *InstrumentationLayer, *GlobalIndex, AddModule,
LookaheadLevels, PrintStats);
DiscoveryThread = std::thread(std::ref(*DiscoveryThreadWorker));
}
Error ThinLtoJIT::setupJITDylib(JITDylib *JD, bool AllowNudge,
bool PrintStats) {
// Register symbols for C++ static destructors.
LocalCXXRuntimeOverrides CXXRuntimeoverrides;
Error Err = CXXRuntimeoverrides.enable(*JD, *Mangle);
if (Err)
return Err;
// Lookup symbol names in the global ThinLTO module index first
char Prefix = DL.getGlobalPrefix();
JD->addGenerator(std::make_unique<ThinLtoDefinitionGenerator>(
*GlobalIndex, *InstrumentationLayer, AddModule, Prefix, AllowNudge,
PrintStats));
// Then try lookup in the host process.
auto HostLookup = DynamicLibrarySearchGenerator::GetForCurrentProcess(Prefix);
if (!HostLookup)
return HostLookup.takeError();
JD->addGenerator(std::move(*HostLookup));
return Error::success();
}
ThinLtoJIT::~ThinLtoJIT() {
// Signal the DiscoveryThread to shut down.
JitRunning.store(false);
DiscoveryThread.join();
// Wait for potential compile actions to finish.
CompileThreads->wait();
}
} // namespace orc
} // namespace llvm