Move the functionality of the old SereneContext to JIT itself

This commit is contained in:
Sameer Rahmani 2023-07-23 20:10:04 +01:00
parent 7a4e76fe08
commit e9012b7583
Signed by: lxsameer
GPG Key ID: B0A4AF28AB9FD90B
6 changed files with 225 additions and 172 deletions

View File

@ -19,5 +19,4 @@ target_sources(serene PRIVATE
commands/commands.cpp
jit/jit.cpp
context.cpp
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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