serene/libserene.v0/include/serene/context.h

286 lines
9.3 KiB
C++

/* -*- C++ -*-
* Serene Programming Language
*
* Copyright (c) 2019-2022 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 SERENE_CONTEXT_H
#define SERENE_CONTEXT_H
#include "serene/diagnostics.h"
#include "serene/environment.h"
#include "serene/export.h"
#include "serene/jit/halley.h"
#include "serene/namespace.h"
#include "serene/passes.h"
#include "serene/slir/dialect.h"
#include "serene/source_mgr.h"
#include <llvm/ADT/DenseMap.h>
#include <llvm/ADT/None.h>
#include <llvm/ADT/Optional.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/ADT/Triple.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/Support/Host.h>
#include <mlir/Dialect/Func/IR/FuncOps.h>
#include <mlir/Dialect/LLVMIR/LLVMDialect.h>
#include <mlir/IR/MLIRContext.h>
#include <mlir/Pass/PassManager.h>
#include <memory>
#define DEFAULT_NS_NAME "serene.user"
#define INTERNAL_NS "serene.internal"
namespace serene {
class SereneContext;
namespace reader {
class LocationRange;
} // namespace reader
namespace exprs {
class Expression;
using Node = std::shared_ptr<Expression>;
} // namespace exprs
/// 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
SERENE_EXPORT void terminate(SereneContext &ctx, int exitCode);
/// 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
/// different subsystem that might use it.
struct SERENE_EXPORT Options {
/// Whether to use colors for the output or not
bool withColors = true;
// JIT related flags
bool JITenableObjectCache = true;
bool JITenableGDBNotificationListener = true;
bool JITenablePerfNotificationListener = true;
bool JITLazy = false;
Options() = default;
};
class SERENE_EXPORT SereneContext {
public:
template <typename T>
using CurrentNSFn = std::function<T()>;
mlir::MLIRContext mlirContext;
mlir::PassManager pm;
std::unique_ptr<DiagnosticEngine> diagEngine;
std::unique_ptr<serene::jit::Halley> jit;
/// The source manager is responsible for loading namespaces and practically
/// managing the source code in form of memory buffers.
SourceMgr sourceManager;
/// The set of options to change the compilers behaivoirs
Options opts;
const llvm::Triple triple;
/// Insert the given `ns` into the context. The Context object is
/// the owner of all the namespaces. The `ns` will overwrite any
/// namespace with the same name.
void insertNS(NSPtr &ns);
/// Execute the given function \p f by setting the `currentNS`
/// to the given \p nsName. It will restore the value of `currentNS`
/// after \p f returned.
template <typename T>
T withCurrentNS(llvm::StringRef nsName, CurrentNSFn<T> f) {
assert(!currentNS.empty() && "The currentNS is not initialized!");
auto tmp = this->currentNS;
this->currentNS = nsName.str();
T res = f();
this->currentNS = tmp;
return res;
};
// void specialization
template <>
void withCurrentNS(llvm::StringRef nsName, CurrentNSFn<void> f) {
assert(!currentNS.empty() && "The currentNS is not initialized!");
auto tmp = this->currentNS;
this->currentNS = nsName.str();
f();
this->currentNS = tmp;
}
/// Return the current namespace that is being processed at the moment
Namespace &getCurrentNS();
/// Lookup the namespace with the give name in the current context and
/// return a pointer to it or a `nullptr` in it doesn't exist.
Namespace *getNS(llvm::StringRef nsName);
/// Lookup and return a shared pointer to the given \p ns_name. This
/// method should be used only if you need to own the namespace as well
/// and want to keep it long term (like the JIT).
NSPtr getSharedPtrToNS(llvm::StringRef nsName);
SereneContext(Options &options)
: pm(&mlirContext), diagEngine(makeDiagnosticEngine(*this)),
opts(options), triple(llvm::sys::getDefaultTargetTriple()),
targetPhase(CompilationPhase::NoOptimization) {
mlirContext.getOrLoadDialect<serene::slir::SereneDialect>();
mlirContext.getOrLoadDialect<mlir::func::FuncDialect>();
mlirContext.getOrLoadDialect<mlir::LLVM::LLVMDialect>();
// We need to create one empty namespace, so that the JIT can
// start it's operation.
auto ns = Namespace::make(*this, DEFAULT_NS_NAME, llvm::None);
insertNS(ns);
currentNS = ns->name;
// TODO: Get the crash report path dynamically from the cli
// pm.enableCrashReproducerGeneration("/home/lxsameer/mlir.mlir");
};
/// 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();
// Namespace stuff ---
/// Create an empty namespace with the given \p name and optional \p filename
/// and then insert it into the context
NSPtr makeNamespace(llvm::StringRef name,
llvm::Optional<llvm::StringRef> filename);
/// Read a namespace with the given \p name and returns a share pointer
/// to the name or an Error.
///
/// It just `read` the namespace by parsing it and running the semantic
/// analyzer on it.
MaybeNS readNamespace(const std::string &name);
MaybeNS readNamespace(const std::string &name, reader::LocationRange loc);
/// Reads and add the namespace with the given \p name to the context. The
/// namespace will be added to the context and the JIT engine as well.
///
/// It will \r a shared pointer to the namespace or an error tree.
MaybeNS importNamespace(const std::string &name);
MaybeNS importNamespace(const std::string &name, reader::LocationRange loc);
// ---
static std::unique_ptr<llvm::LLVMContext> genLLVMContext() {
return std::make_unique<llvm::LLVMContext>();
};
static std::unique_ptr<SereneContext> make(Options &options) {
auto ctx = std::make_unique<SereneContext>(options);
auto *ns = ctx->getNS(DEFAULT_NS_NAME);
assert(ns != nullptr && "Default ns doesn't exit!");
auto maybeJIT = serene::jit::makeHalleyJIT(*ctx);
if (!maybeJIT) {
auto err = maybeJIT.takeError();
panic(*ctx, err);
}
ctx->jit.swap(*maybeJIT);
// Make serene.user which is the defult NS available on the
// JIT
auto loc = reader::LocationRange::UnknownLocation(INTERNAL_NS);
if (auto err = ctx->jit->addNS(*ns, loc)) {
panic(*ctx, err);
}
return ctx;
};
// JIT JITDylib related functions ---
// TODO: For Dylib related functions, make sure that the namespace in questoin
// is aleady registered in the context
/// Return a pointer to the most registered JITDylib of the given \p ns
////name
llvm::orc::JITDylib *getLatestJITDylib(Namespace &ns);
/// Register the given pointer to a `JITDylib` \p l, with the give \p ns.
void pushJITDylib(Namespace &ns, llvm::orc::JITDylib *l);
/// Returns the number of registered `JITDylib` for the given \p ns.
size_t getNumberOfJITDylibs(Namespace &ns);
private:
CompilationPhase targetPhase;
// TODO: We need to keep different instances of the namespace
// because if any one of them gets cleaned up via reference
// count (if we are still using shared ptr for namespaces if not
// remove this todo) then we will end up with dangling references
// it the JIT
/// The namespace table. Every namespace that needs to be compiled has
/// to register itself with the context and appear on this table.
/// This table acts as a cache as well.
std::map<std::string, NSPtr> namespaces;
/// Why string vs pointer? We might rewrite the namespace and
/// holding a pointer means that it might point to the old version
std::string currentNS;
/// A vector of pointers to all the jitDylibs for namespaces. Usually
/// There will be only one pre NS but in case of forceful reloads of a
/// namespace there will be more.
llvm::StringMap<llvm::SmallVector<llvm::orc::JITDylib *, 1>> jitDylibs;
};
/// 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.
SERENE_EXPORT std::unique_ptr<SereneContext>
makeSereneContext(Options opts = Options());
} // namespace serene
#endif