diff --git a/serene/CMakeLists.txt b/serene/CMakeLists.txt index 6e4e24d..aa0dc86 100644 --- a/serene/CMakeLists.txt +++ b/serene/CMakeLists.txt @@ -38,6 +38,7 @@ endif() target_include_directories(serene PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src ) target_include_directories(serene SYSTEM PUBLIC diff --git a/serene/src/CMakeLists.txt b/serene/src/CMakeLists.txt index 532bb28..8595cb8 100644 --- a/serene/src/CMakeLists.txt +++ b/serene/src/CMakeLists.txt @@ -18,4 +18,6 @@ target_sources(serene PRIVATE serene.cpp commands/commands.cpp + jit/jit.cpp + context.cpp ) diff --git a/serene/src/commands/commands.cpp b/serene/src/commands/commands.cpp index 714eb98..3556431 100644 --- a/serene/src/commands/commands.cpp +++ b/serene/src/commands/commands.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "serene/commands.h" +#include #include diff --git a/serene/include/serene/commands.h b/serene/src/commands/commands.h similarity index 100% rename from serene/include/serene/commands.h rename to serene/src/commands/commands.h diff --git a/serene/src/context.cpp b/serene/src/context.cpp new file mode 100644 index 0000000..841ed92 --- /dev/null +++ b/serene/src/context.cpp @@ -0,0 +1,51 @@ +/* -*- C++ -*- + * Serene Programming Language + * + * Copyright (c) 2019-2023 Sameer Rahmani + * + * 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 . + */ + +#include +#include // 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 makeSereneContext(Options opts) { + return std::make_unique(opts); +}; + +}; // namespace serene diff --git a/serene/src/context.h b/serene/src/context.h new file mode 100644 index 0000000..3955b1c --- /dev/null +++ b/serene/src/context.h @@ -0,0 +1,104 @@ +/* -*- C++ -*- + * Serene Programming Language + * + * Copyright (c) 2019-2023 Sameer Rahmani + * + * 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 . + */ + +#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 // for Twine +#include // for LLVMContext +#include // for getDefaultTargetTriple +#include // for Triple + +#include // for basic_string +#include // 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 genLLVMContext() { + return std::make_unique(); + }; + + /// Setup the load path for namespace lookups + void setLoadPaths(std::vector &dirs) { loadPaths.swap(dirs); }; + + /// Return the load paths for namespaces + std::vector &getLoadPaths() { return loadPaths; }; + +private: + CompilationPhase targetPhase; + std::vector 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 makeSereneContext(Options opts = Options()); + +} // namespace serene + +#endif diff --git a/serene/src/jit/jit.cpp b/serene/src/jit/jit.cpp new file mode 100644 index 0000000..caed305 --- /dev/null +++ b/serene/src/jit/jit.cpp @@ -0,0 +1,127 @@ +/* -*- C++ -*- + * Serene Programming Language + * + * Copyright (c) 2019-2023 Sameer Rahmani + * + * 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 . + */ + +#include "jit/jit.h" + +#include "options.h" // for Options +#include // for error_code + +#include // for StringMapEntry +#include // for iterator_facade_base +#include // for JITEventListener +#include // IWYU pragma: keep +#include // for Module +#include // for OpenFlags +#include // for ToolOutputFile + +#include // for assert +#include // for operator+, char_t... + +namespace serene::jit { + +// ---------------------------------------------------------------------------- +// ObjectCache Implementation +// ---------------------------------------------------------------------------- +void ObjectCache::notifyObjectCompiled(const llvm::Module *m, + llvm::MemoryBufferRef objBuffer) { + cachedObjects[m->getModuleIdentifier()] = + llvm::MemoryBuffer::getMemBufferCopy(objBuffer.getBuffer(), + objBuffer.getBufferIdentifier()); +} + +std::unique_ptr +ObjectCache::getObject(const llvm::Module *m) { + auto i = cachedObjects.find(m->getModuleIdentifier()); + + if (i == cachedObjects.end()) { + HALLEY_LOG("No object for " + m->getModuleIdentifier() + + " in cache. Compiling."); + return nullptr; + } + + HALLEY_LOG("Object for " + m->getModuleIdentifier() + " loaded from cache."); + return llvm::MemoryBuffer::getMemBuffer(i->second->getMemBufferRef()); +} + +void ObjectCache::dumpToObjectFile(llvm::StringRef outputFilename) { + // Set up the output file. + std::error_code error; + + auto file = std::make_unique(outputFilename, error, + llvm::sys::fs::OF_None); + if (error) { + + llvm::errs() << "cannot open output file '" + outputFilename.str() + + "': " + error.message() + << "\n"; + return; + } + // Dump the object generated for a single module to the output file. + // TODO: Replace this with a runtime check + assert(cachedObjects.size() == 1 && "Expected only one object entry."); + + auto &cachedObject = cachedObjects.begin()->second; + file->os() << cachedObject->getBuffer(); + file->keep(); +} + +// ---------------------------------------------------------------------------- +// JIT Implementation +// ---------------------------------------------------------------------------- +orc::JITDylib *JIT::getLatestJITDylib(const llvm::StringRef &nsName) { + if (jitDylibs.count(nsName) == 0) { + return nullptr; + } + + auto vec = jitDylibs[nsName]; + // TODO: Make sure that the returning Dylib still exists in the JIT + // by calling jit->engine->getJITDylibByName(dylib_name); + return vec.empty() ? nullptr : vec.back(); +}; + +void JIT::pushJITDylib(const llvm::StringRef &nsName, llvm::orc::JITDylib *l) { + if (jitDylibs.count(nsName) == 0) { + llvm::SmallVector vec{l}; + jitDylibs[nsName] = vec; + return; + } + auto vec = jitDylibs[nsName]; + vec.push_back(l); + jitDylibs[nsName] = vec; +} + +size_t JIT::getNumberOfJITDylibs(const llvm::StringRef &nsName) { + if (jitDylibs.count(nsName) == 0) { + return 0; + } + + return jitDylibs[nsName].size(); +}; + +JIT::JIT(llvm::orc::JITTargetMachineBuilder &&jtmb, Options &opts) + : isLazy(opts.JITLazy), + cache(opts.JITenableObjectCache ? new ObjectCache() : nullptr), + gdbListener(opts.JITenableGDBNotificationListener + ? llvm::JITEventListener::createGDBRegistrationListener() + : nullptr), + perfListener(opts.JITenablePerfNotificationListener + ? llvm::JITEventListener::createPerfJITEventListener() + : nullptr), + jtmb(jtmb){}; + +} // namespace serene::jit diff --git a/serene/src/jit/jit.h b/serene/src/jit/jit.h new file mode 100644 index 0000000..663609c --- /dev/null +++ b/serene/src/jit/jit.h @@ -0,0 +1,144 @@ +/* -*- C++ -*- + * Serene Programming Language + * + * Copyright (c) 2019-2023 Sameer Rahmani + * + * 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 . + */ + +/** + * Commentary: + - It operates in lazy (for REPL) and non-lazy mode and wraps LLJIT + and LLLazyJIT + - It uses an object cache layer to cache module (not NSs) objects. + */ + +#ifndef JIT_JIT_H +#define JIT_JIT_H + +#include <__memory/unique_ptr.h> + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace llvm { +class JITEventListener; +} // namespace llvm +namespace llvm { +class Module; +} // namespace llvm +namespace llvm::orc { +class JITDylib; +class LLJIT; +class LLLazyJIT; +} // namespace llvm::orc +namespace serene { +struct Options; +} // namespace serene + +#define HALLEY_LOG(...) \ + DEBUG_WITH_TYPE("JIT", llvm::dbgs() << "[JIT]: " << __VA_ARGS__ << "\n"); + +namespace orc = llvm::orc; + +namespace serene::jit { +class JIT; +using JITPtr = std::unique_ptr; +using MaybeJIT = llvm::Expected; +using JitWrappedAddress = void (*)(void **); +using MaybeJitAddress = llvm::Expected; + +/// A simple object cache following Lang's LLJITWithObjectCache example and +/// MLIR's SimpelObjectCache. +class ObjectCache : public llvm::ObjectCache { +public: + /// Cache the given `objBuffer` for the given module `m`. The buffer contains + /// the combiled objects of the module + void notifyObjectCompiled(const llvm::Module *m, + llvm::MemoryBufferRef objBuffer) override; + + // Lookup the cache for the given module `m` or returen a nullptr. + std::unique_ptr getObject(const llvm::Module *m) override; + + /// Dump cached object to output file `filename`. + void dumpToObjectFile(llvm::StringRef filename); + +private: + llvm::StringMap> cachedObjects; +}; + +class JIT { + const bool isLazy; + + std::variant, std::unique_ptr> + engine; + std::unique_ptr cache; + + llvm::JITEventListener *gdbListener; + /// Perf notification listener. + llvm::JITEventListener *perfListener; + + llvm::orc::JITTargetMachineBuilder jtmb; + + // 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 + // + // Questions: + // Is using string as the key good enough, what about an ID for NSs + // or even a pointer to the ns? + llvm::StringMap> jitDylibs; + + void pushJITDylib(const llvm::StringRef &nsName, llvm::orc::JITDylib *l); + size_t getNumberOfJITDylibs(const llvm::StringRef &nsName); + +public: + JIT(llvm::orc::JITTargetMachineBuilder &&jtmb, Options &opts); + static MaybeJIT make(llvm::orc::JITTargetMachineBuilder &&jtmb); + + /// Return a pointer to the most registered JITDylib of the given \p ns + ////name + llvm::orc::JITDylib *getLatestJITDylib(const llvm::StringRef &nsName); + + /// Looks up a packed-argument function with the given sym name and returns a + /// pointer to it. Propagates errors in case of failure. + MaybeJitAddress lookup(const llvm::StringRef &nsName, + const llvm::StringRef &sym) const; + + /// Invokes the function with the given name passing it the list of opaque + /// pointers containing the actual arguments. + llvm::Error + invokePacked(const llvm::StringRef &symbolName, + llvm::MutableArrayRef args = std::nullopt) const; + + llvm::Error loadModule(const llvm::StringRef &nsName, + const llvm::StringRef &file); + void dumpToObjectFile(const llvm::StringRef &filename); +}; + +MaybeJIT makeJIT(); +} // namespace serene::jit +#endif diff --git a/serene/src/options.h b/serene/src/options.h new file mode 100644 index 0000000..7094d51 --- /dev/null +++ b/serene/src/options.h @@ -0,0 +1,40 @@ +/* -*- C++ -*- + * Serene Programming Language + * + * Copyright (c) 2019-2023 Sameer Rahmani + * + * 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 . + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +namespace serene { +/// 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 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; +}; + +} // namespace serene +#endif diff --git a/serene/src/serene.cpp b/serene/src/serene.cpp index 1fa50fb..c1b575b 100644 --- a/serene/src/serene.cpp +++ b/serene/src/serene.cpp @@ -16,14 +16,19 @@ * along with this program. If not, see . */ -#include "serene/commands.h" -#include "serene/config.h" +#include "serene/config.h" // for SERENE_VERSION -#include -#include +#include "commands/commands.h" // for cc, run +#include <__fwd/string.h> // for string -#include -#include +#include // for StringRef +#include // for SubCommand, ParseCom... +#include // for formatv, formatv_object +#include // for provider_format_adapter + +#include // for strcmp +#include // for basic_string +#include // for tuple namespace cl = llvm::cl;