Move the functionality of the old SereneContext to JIT itself
This commit is contained in:
parent
7a4e76fe08
commit
e9012b7583
|
@ -19,5 +19,4 @@ target_sources(serene PRIVATE
|
|||
|
||||
commands/commands.cpp
|
||||
jit/jit.cpp
|
||||
context.cpp
|
||||
)
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
/* -*- C++ -*-
|
||||
* Serene Programming Language
|
||||
*
|
||||
* Copyright (c) 2019-2023 Sameer Rahmani <lxsameer@gnu.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <context.h>
|
||||
#include <cstdlib> // for exit
|
||||
|
||||
namespace serene {
|
||||
|
||||
int SereneContext::getOptimizatioLevel() {
|
||||
if (targetPhase <= CompilationPhase::NoOptimization) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (targetPhase == CompilationPhase::O1) {
|
||||
return 1;
|
||||
}
|
||||
if (targetPhase == CompilationPhase::O2) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
void terminate(SereneContext &ctx, int exitCode) {
|
||||
(void)ctx;
|
||||
// TODO: Since we are running in a single thread for now using exit is fine
|
||||
// but we need to adjust and change it to a thread safe termination
|
||||
// process later on.
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
std::exit(exitCode);
|
||||
}
|
||||
|
||||
std::unique_ptr<SereneContext> makeSereneContext(Options opts) {
|
||||
return std::make_unique<SereneContext>(opts);
|
||||
};
|
||||
|
||||
}; // namespace serene
|
|
@ -1,104 +0,0 @@
|
|||
/* -*- C++ -*-
|
||||
* Serene Programming Language
|
||||
*
|
||||
* Copyright (c) 2019-2023 Sameer Rahmani <lxsameer@gnu.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CONTEXT_H
|
||||
#define CONTEXT_H
|
||||
|
||||
#include "options.h" // for Options
|
||||
#include <__fwd/string.h> // for string
|
||||
#include <__memory/unique_ptr.h> // for make_unique, unique_ptr
|
||||
|
||||
#include <llvm/ADT/Twine.h> // for Twine
|
||||
#include <llvm/IR/LLVMContext.h> // for LLVMContext
|
||||
#include <llvm/TargetParser/Host.h> // for getDefaultTargetTriple
|
||||
#include <llvm/TargetParser/Triple.h> // for Triple
|
||||
|
||||
#include <string> // for basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
namespace serene {
|
||||
class SereneContext;
|
||||
|
||||
/// This enum describes the different operational phases for the compiler
|
||||
/// in order. Anything below `NoOptimization` is considered only for debugging
|
||||
enum class CompilationPhase {
|
||||
Parse,
|
||||
Analysis,
|
||||
SLIR,
|
||||
MLIR, // Lowered slir to other dialects
|
||||
LIR, // Lowered to the llvm ir dialect
|
||||
IR, // Lowered to the LLVMIR itself
|
||||
NoOptimization,
|
||||
O1,
|
||||
O2,
|
||||
O3,
|
||||
};
|
||||
|
||||
/// Terminates the serene compiler process in a thread safe manner
|
||||
/// This function is only meant to be used in the compiler context
|
||||
/// if you want to terminate the process in context of a serene program
|
||||
/// via the JIT use an appropriate function in the `serene.core` ns.
|
||||
void terminate(SereneContext &ctx, int exitCode);
|
||||
|
||||
// Why SereneContext and not Context? We will be using LLVMContext
|
||||
// and MLIRContext through out Serene, so it's better to follow
|
||||
// the same convention
|
||||
class SereneContext {
|
||||
|
||||
public:
|
||||
/// The set of options to change the compilers behaviors
|
||||
Options options;
|
||||
|
||||
const llvm::Triple triple;
|
||||
|
||||
explicit SereneContext(Options &options)
|
||||
: options(options), triple(llvm::sys::getDefaultTargetTriple()),
|
||||
targetPhase(CompilationPhase::NoOptimization){};
|
||||
|
||||
/// Set the target compilation phase of the compiler. The compilation
|
||||
/// phase dictates the behavior and the output type of the compiler.
|
||||
void setOperationPhase(CompilationPhase phase);
|
||||
|
||||
CompilationPhase getTargetPhase() { return targetPhase; };
|
||||
int getOptimizatioLevel();
|
||||
|
||||
static std::unique_ptr<llvm::LLVMContext> genLLVMContext() {
|
||||
return std::make_unique<llvm::LLVMContext>();
|
||||
};
|
||||
|
||||
/// Setup the load path for namespace lookups
|
||||
void setLoadPaths(std::vector<std::string> &dirs) { loadPaths.swap(dirs); };
|
||||
|
||||
/// Return the load paths for namespaces
|
||||
std::vector<std::string> &getLoadPaths() { return loadPaths; };
|
||||
|
||||
private:
|
||||
CompilationPhase targetPhase;
|
||||
std::vector<std::string> loadPaths;
|
||||
};
|
||||
|
||||
/// Creates a new context object. Contexts are used through out the compilation
|
||||
/// process to store the state.
|
||||
///
|
||||
/// \p opts is an instance of \c Options that can be used to set options of
|
||||
/// of the compiler.
|
||||
std::unique_ptr<SereneContext> makeSereneContext(Options opts = Options());
|
||||
|
||||
} // namespace serene
|
||||
|
||||
#endif
|
|
@ -16,21 +16,22 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "jit/jit.h"
|
||||
|
||||
#include "options.h" // for Options
|
||||
#include <jit/jit.h>
|
||||
#include <system_error> // for error_code
|
||||
|
||||
#include <llvm/ADT/StringMapEntry.h> // for StringMapEntry
|
||||
#include <llvm/ADT/iterator.h> // for iterator_facade_base
|
||||
#include <llvm/ExecutionEngine/JITEventListener.h> // for JITEventListener
|
||||
#include <llvm/ExecutionEngine/Orc/LLJIT.h> // IWYU pragma: keep
|
||||
#include <llvm/IR/Module.h> // for Module
|
||||
#include <llvm/Support/FileSystem.h> // for OpenFlags
|
||||
#include <llvm/Support/ToolOutputFile.h> // for ToolOutputFile
|
||||
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
|
||||
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
|
||||
#include <llvm/IR/Module.h> // for Module
|
||||
#include <llvm/Support/FileSystem.h> // for OpenFlags
|
||||
#include <llvm/Support/ToolOutputFile.h> // for ToolOutputFile
|
||||
|
||||
#include <assert.h> // for assert
|
||||
#include <string> // for operator+, char_t...
|
||||
#include <assert.h> // for assert
|
||||
#include <options.h> // for Options
|
||||
#include <string> // for operator+, char_t...
|
||||
|
||||
namespace serene::jit {
|
||||
|
||||
|
@ -113,15 +114,168 @@ size_t JIT::getNumberOfJITDylibs(const llvm::StringRef &nsName) {
|
|||
return jitDylibs[nsName].size();
|
||||
};
|
||||
|
||||
JIT::JIT(llvm::orc::JITTargetMachineBuilder &&jtmb, Options &opts)
|
||||
: isLazy(opts.JITLazy),
|
||||
cache(opts.JITenableObjectCache ? new ObjectCache() : nullptr),
|
||||
gdbListener(opts.JITenableGDBNotificationListener
|
||||
JIT::JIT(llvm::orc::JITTargetMachineBuilder &&jtmb,
|
||||
std::unique_ptr<Options> opts)
|
||||
:
|
||||
|
||||
options(std::move(opts)),
|
||||
cache(options->JITenableObjectCache ? new ObjectCache() : nullptr),
|
||||
gdbListener(options->JITenableGDBNotificationListener
|
||||
? llvm::JITEventListener::createGDBRegistrationListener()
|
||||
: nullptr),
|
||||
perfListener(opts.JITenablePerfNotificationListener
|
||||
perfListener(options->JITenablePerfNotificationListener
|
||||
? llvm::JITEventListener::createPerfJITEventListener()
|
||||
: nullptr),
|
||||
jtmb(jtmb){};
|
||||
|
||||
void JIT::dumpToObjectFile(const llvm::StringRef &filename) {
|
||||
cache->dumpToObjectFile(filename);
|
||||
};
|
||||
|
||||
int JIT::getOptimizatioLevel() const {
|
||||
if (options->compilationPhase <= CompilationPhase::NoOptimization) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options->compilationPhase == CompilationPhase::O1) {
|
||||
return 1;
|
||||
}
|
||||
if (options->compilationPhase == CompilationPhase::O2) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
llvm::Error JIT::createCurrentProcessJD() {
|
||||
|
||||
auto &es = WITH_ENGINE(auto &, getExecutionSession());
|
||||
auto *processJDPtr = es.getJITDylibByName(MAIN_PROCESS_JD_NAME);
|
||||
|
||||
if (processJDPtr != nullptr) {
|
||||
// We already created the JITDylib for the current process
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
auto processJD = es.createJITDylib(MAIN_PROCESS_JD_NAME);
|
||||
|
||||
if (!processJD) {
|
||||
return processJD.takeError();
|
||||
}
|
||||
|
||||
auto generator =
|
||||
llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess(
|
||||
WITH_ENGINE(const auto &, getDataLayout()).getGlobalPrefix());
|
||||
|
||||
if (!generator) {
|
||||
return generator.takeError();
|
||||
}
|
||||
|
||||
processJD->addGenerator(std::move(*generator));
|
||||
return llvm::Error::success();
|
||||
};
|
||||
|
||||
MaybeJIT JIT::make(llvm::orc::JITTargetMachineBuilder &&jtmb,
|
||||
std::unique_ptr<Options> opts) {
|
||||
auto dl = jtmb.getDefaultDataLayoutForTarget();
|
||||
if (!dl) {
|
||||
return dl.takeError();
|
||||
}
|
||||
|
||||
auto jitEngine = std::make_unique<JIT>(std::move(jtmb), std::move(opts));
|
||||
//
|
||||
// What might go wrong?
|
||||
// in a repl env when we have to create new modules on top of each other
|
||||
// having two different contex might be a problem, but i think since we
|
||||
// use the first context to generate the IR and the second one to just
|
||||
// run it.
|
||||
std::unique_ptr<llvm::LLVMContext> ctx(new llvm::LLVMContext);
|
||||
|
||||
// Callback to create the object layer with symbol resolution to current
|
||||
// process and dynamically linked libraries.
|
||||
auto objectLinkingLayerCreator = [&](llvm::orc::ExecutionSession &session,
|
||||
const llvm::Triple &tt) {
|
||||
(void)tt;
|
||||
|
||||
auto objectLayer =
|
||||
std::make_unique<llvm::orc::RTDyldObjectLinkingLayer>(session, []() {
|
||||
return std::make_unique<llvm::SectionMemoryManager>();
|
||||
});
|
||||
|
||||
// Register JIT event listeners if they are enabled.
|
||||
if (jitEngine->gdbListener != nullptr) {
|
||||
objectLayer->registerJITEventListener(*jitEngine->gdbListener);
|
||||
}
|
||||
if (jitEngine->perfListener != nullptr) {
|
||||
objectLayer->registerJITEventListener(*jitEngine->perfListener);
|
||||
}
|
||||
|
||||
// COFF format binaries (Windows) need special handling to deal with
|
||||
// exported symbol visibility.
|
||||
// cf llvm/lib/ExecutionEngine/Orc/LLJIT.cpp
|
||||
// LLJIT::createObjectLinkingLayer
|
||||
if (jitEngine->hostTriple.isOSBinFormatCOFF()) {
|
||||
objectLayer->setOverrideObjectFlagsWithResponsibilityFlags(true);
|
||||
objectLayer->setAutoClaimResponsibilityForObjectSymbols(true);
|
||||
}
|
||||
|
||||
return objectLayer;
|
||||
};
|
||||
|
||||
// Callback to inspect the cache and recompile on demand.
|
||||
auto compileFunctionCreator = [&](llvm::orc::JITTargetMachineBuilder JTMB)
|
||||
-> llvm::Expected<
|
||||
std::unique_ptr<llvm::orc::IRCompileLayer::IRCompiler>> {
|
||||
llvm::CodeGenOpt::Level jitCodeGenOptLevel =
|
||||
static_cast<llvm::CodeGenOpt::Level>(jitEngine->getOptimizatioLevel());
|
||||
|
||||
JTMB.setCodeGenOptLevel(jitCodeGenOptLevel);
|
||||
|
||||
auto targetMachine = JTMB.createTargetMachine();
|
||||
if (!targetMachine) {
|
||||
return targetMachine.takeError();
|
||||
}
|
||||
|
||||
return std::make_unique<llvm::orc::TMOwningSimpleCompiler>(
|
||||
std::move(*targetMachine), jitEngine->cache.get());
|
||||
};
|
||||
|
||||
auto compileNotifier = [&](llvm::orc::MaterializationResponsibility &r,
|
||||
llvm::orc::ThreadSafeModule tsm) {
|
||||
auto syms = r.getRequestedSymbols();
|
||||
tsm.withModuleDo([&](llvm::Module &m) {
|
||||
HALLEY_LOG("Compiled " << syms
|
||||
<< " for the module: " << m.getModuleIdentifier());
|
||||
});
|
||||
};
|
||||
|
||||
if (jitEngine->options->JITLazy) {
|
||||
// Setup a LLLazyJIT instance to the times that latency is important
|
||||
// for example in a REPL. This way
|
||||
auto jit =
|
||||
cantFail(llvm::orc::LLLazyJITBuilder()
|
||||
.setCompileFunctionCreator(compileFunctionCreator)
|
||||
.setObjectLinkingLayerCreator(objectLinkingLayerCreator)
|
||||
.create());
|
||||
jit->getIRCompileLayer().setNotifyCompiled(compileNotifier);
|
||||
jitEngine->engine = std::move(jit);
|
||||
|
||||
} else {
|
||||
// Setup a LLJIT instance for the times that performance is important
|
||||
// and we want to compile everything as soon as possible. For instance
|
||||
// when we run the JIT in the compiler
|
||||
auto jit =
|
||||
cantFail(llvm::orc::LLJITBuilder()
|
||||
.setCompileFunctionCreator(compileFunctionCreator)
|
||||
.setObjectLinkingLayerCreator(objectLinkingLayerCreator)
|
||||
.create());
|
||||
jit->getIRCompileLayer().setNotifyCompiled(compileNotifier);
|
||||
jitEngine->engine = std::move(jit);
|
||||
}
|
||||
|
||||
if (auto err = jitEngine->createCurrentProcessJD()) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return MaybeJIT(std::move(jitEngine));
|
||||
};
|
||||
} // namespace serene::jit
|
||||
|
|
|
@ -59,9 +59,19 @@ namespace serene {
|
|||
struct Options;
|
||||
} // namespace serene
|
||||
|
||||
#define MAIN_PROCESS_JD_NAME "*main*"
|
||||
#define HALLEY_LOG(...) \
|
||||
DEBUG_WITH_TYPE("JIT", llvm::dbgs() << "[JIT]: " << __VA_ARGS__ << "\n");
|
||||
|
||||
/// A simple macro that we need to use to call those member functions that are
|
||||
/// shared between LLJIT and LLLAZYJIT. This macro supposed to be used
|
||||
/// only within the JIT class itself. The first argument is the return type
|
||||
/// of the member function and the second arg is the member function call.
|
||||
/// The whole point of this macro is to unwrap the variant type and call
|
||||
/// the shared member function on the unwraped value.
|
||||
#define WITH_ENGINE(retType, fnCall) \
|
||||
std::visit([](auto &e) -> retType { return e->fnCall; }, engine)
|
||||
|
||||
namespace orc = llvm::orc;
|
||||
|
||||
namespace serene::jit {
|
||||
|
@ -91,7 +101,7 @@ private:
|
|||
};
|
||||
|
||||
class JIT {
|
||||
const bool isLazy;
|
||||
std::unique_ptr<const Options> options;
|
||||
|
||||
std::variant<std::unique_ptr<orc::LLJIT>, std::unique_ptr<orc::LLLazyJIT>>
|
||||
engine;
|
||||
|
@ -103,6 +113,8 @@ class JIT {
|
|||
|
||||
llvm::orc::JITTargetMachineBuilder jtmb;
|
||||
|
||||
std::vector<const char *> loadPaths;
|
||||
|
||||
// We keep the jibDylibs for each name space in a mapping from the ns
|
||||
// name to a vector of jitdylibs, the last element is always the newest
|
||||
// jitDylib
|
||||
|
@ -115,9 +127,30 @@ class JIT {
|
|||
void pushJITDylib(const llvm::StringRef &nsName, llvm::orc::JITDylib *l);
|
||||
size_t getNumberOfJITDylibs(const llvm::StringRef &nsName);
|
||||
|
||||
llvm::Error createCurrentProcessJD();
|
||||
|
||||
public:
|
||||
JIT(llvm::orc::JITTargetMachineBuilder &&jtmb, Options &opts);
|
||||
static MaybeJIT make(llvm::orc::JITTargetMachineBuilder &&jtmb);
|
||||
// We will use this triple to generate code that will endup in the binary
|
||||
// for the target platform. If we're not cross compiling, `targetTriple`
|
||||
// will be the same as `hostTriple`.
|
||||
const llvm::Triple targetTriple;
|
||||
|
||||
// This triple will be used in code generation for the host platform in
|
||||
// complie time. For example any function that will be called during
|
||||
// the compile time has to run on the host. So we need to generate
|
||||
// appropriate code for the host. If the same function has to be part
|
||||
// of the runtime, then we use `targetTriple` again to generate the code
|
||||
// for the target platform. So, we might end up with two version of the
|
||||
// same function
|
||||
const llvm::Triple hostTriple;
|
||||
|
||||
JIT(llvm::orc::JITTargetMachineBuilder &&jtmb, std::unique_ptr<Options> opts);
|
||||
static MaybeJIT make(llvm::orc::JITTargetMachineBuilder &&jtmb,
|
||||
std::unique_ptr<Options> opts);
|
||||
|
||||
// Return an integer indicating the level of optimization that is currently
|
||||
// set. 0 == No optimizaion -> it includes compling to IR and AST
|
||||
int getOptimizatioLevel() const;
|
||||
|
||||
/// Return a pointer to the most registered JITDylib of the given \p ns
|
||||
////name
|
||||
|
@ -137,6 +170,11 @@ public:
|
|||
llvm::Error loadModule(const llvm::StringRef &nsName,
|
||||
const llvm::StringRef &file);
|
||||
void dumpToObjectFile(const llvm::StringRef &filename);
|
||||
|
||||
/// Setup the load path for namespace lookups
|
||||
void setLoadPaths(std::vector<const char *> &dirs) { loadPaths.swap(dirs); };
|
||||
/// Return the load paths for namespaces
|
||||
llvm::ArrayRef<const char *> getLoadPaths() { return loadPaths; };
|
||||
};
|
||||
|
||||
MaybeJIT makeJIT();
|
||||
|
|
|
@ -20,6 +20,21 @@
|
|||
#define OPTIONS_H
|
||||
|
||||
namespace serene {
|
||||
/// This enum describes the different operational phases for the compiler
|
||||
/// in order. Anything below `NoOptimization` is considered only for debugging
|
||||
enum class CompilationPhase {
|
||||
Parse,
|
||||
Analysis,
|
||||
SLIR,
|
||||
MLIR, // Lowered slir to other dialects
|
||||
LIR, // Lowered to the llvm ir dialect
|
||||
IR, // Lowered to the LLVMIR itself
|
||||
NoOptimization,
|
||||
O1,
|
||||
O2,
|
||||
O3,
|
||||
};
|
||||
|
||||
/// Options describes the compiler options that can be passed to the
|
||||
/// compiler via command line. Anything that user should be able to
|
||||
/// tweak about the compiler has to end up here regardless of the
|
||||
|
@ -34,6 +49,8 @@ struct Options {
|
|||
bool JITenableGDBNotificationListener = true;
|
||||
bool JITenablePerfNotificationListener = true;
|
||||
bool JITLazy = false;
|
||||
|
||||
CompilationPhase compilationPhase = CompilationPhase::NoOptimization;
|
||||
};
|
||||
|
||||
} // namespace serene
|
||||
|
|
Loading…
Reference in New Issue