diff --git a/include/serene/context.h b/include/serene/context.h index a172097..4c8fce5 100644 --- a/include/serene/context.h +++ b/include/serene/context.h @@ -22,7 +22,7 @@ #include "serene/diagnostics.h" #include "serene/environment.h" #include "serene/export.h" -#include "serene/jit/engine.h" +#include "serene/jit/halley.h" #include "serene/namespace.h" #include "serene/passes.h" #include "serene/slir/dialect.h" @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,10 @@ enum class CompilationPhase { class SERENE_EXPORT SereneContext { struct Options { /// Whether to use colors for the output or not - bool withColors = true; + bool withColors = true; + bool JITenableObjectCache = true; + bool JITenableGDBNotificationListener = true; + bool JITenablePerfNotificationListener = true; Options() = default; }; @@ -89,7 +93,7 @@ public: std::unique_ptr diagEngine; - std::unique_ptr jit; + std::unique_ptr jit; /// The source manager is responsible for loading namespaces and practically /// managing the source code in form of memory buffers. @@ -120,6 +124,11 @@ public: /// return a pointer to it or a `nullptr` in it doesn't exist. Namespace *getNS(llvm::StringRef ns_name); + /// 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 ns_name); + SereneContext() : pm(&mlirContext), diagEngine(makeDiagnosticEngine(*this)), targetPhase(CompilationPhase::NoOptimization) { @@ -158,7 +167,7 @@ public: assert(ns != nullptr && "Default ns doesn't exit!"); - auto maybeJIT = serene::jit::makeSereneJIT(*ns); + auto maybeJIT = serene::jit::makeHalleyJIT(*ctx); if (!maybeJIT) { // TODO: Raise an error here @@ -170,6 +179,8 @@ public: return ctx; }; + llvm::Triple getTargetTriple() const { return llvm::Triple(targetTriple); }; + private: CompilationPhase targetPhase; diff --git a/include/serene/errors/constants.h b/include/serene/errors/constants.h index 468d961..9581083 100644 --- a/include/serene/errors/constants.h +++ b/include/serene/errors/constants.h @@ -50,6 +50,7 @@ enum ErrID { E0011, E0012, E0013, + E0014, }; struct ErrorVariant { @@ -106,6 +107,8 @@ static ErrorVariant static ErrorVariant InvalidCharacterForSymbol(E0013, "Invalid character for a symbol", ""); +static ErrorVariant CompilationError(E0014, "Compilation error!", ""); + static std::map ErrDesc = { {E0000, &UnknownError}, {E0001, &DefExpectSymbol}, {E0002, &DefWrongNumberOfArgs}, {E0003, &FnNoArgsList}, @@ -113,7 +116,8 @@ static std::map ErrDesc = { {E0006, &DontKnowHowToCallNode}, {E0007, &PassFailureError}, {E0008, &NSLoadError}, {E0009, &NSAddToSMError}, {E0010, &EOFWhileScaningAList}, {E0011, &InvalidDigitForNumber}, - {E0012, &TwoFloatPoints}, {E0013, &InvalidCharacterForSymbol}}; + {E0012, &TwoFloatPoints}, {E0013, &InvalidCharacterForSymbol}, + {E0014, &CompilationError}}; } // namespace errors } // namespace serene diff --git a/include/serene/jit.h b/include/serene/jit/halley.h similarity index 79% rename from include/serene/jit.h rename to include/serene/jit/halley.h index 001b5b8..cb93901 100644 --- a/include/serene/jit.h +++ b/include/serene/jit/halley.h @@ -18,16 +18,14 @@ /** * Commentary: + * The code is based on the MLIR's JIT and named after Edmond Halley. */ -#ifndef SERENE_JIT_H -#define SERENE_JIT_H +#ifndef SERENE_JIT_HALLEY_H +#define SERENE_JIT_HALLEY_H #include "serene/errors.h" #include "serene/export.h" -#include "serene/exprs/expression.h" -#include "serene/namespace.h" -#include "serene/slir/generatable.h" #include "serene/utils.h" #include @@ -40,13 +38,19 @@ #include -#define JIT2_LOG(...) \ - DEBUG_WITH_TYPE("JIT", llvm::dbgs() << "[JIT]: " << __VA_ARGS__ << "\n"); +#define HALLEY_LOG(...) \ + DEBUG_WITH_TYPE("halley", llvm::dbgs() \ + << "[HALLEY]: " << __VA_ARGS__ << "\n"); namespace serene { -class SERENE_EXPORT JIT; -using MaybeJIT = llvm::Optional>; +class SereneContext; +class Namespace; + +namespace jit { +class Halley; + +using MaybeJIT = llvm::Expected>; /// A simple object cache following Lang's LLJITWithObjectCache example and /// MLIR's SimpelObjectCache. @@ -67,12 +71,8 @@ private: llvm::StringMap> cachedObjects; }; -class JIT { - // TODO: Should the JIT own the context ??? - Namespace &ns; - +class SERENE_EXPORT Halley { std::unique_ptr engine; - std::unique_ptr cache; /// GDB notification listener. @@ -81,16 +81,19 @@ class JIT { /// Perf notification listener. llvm::JITEventListener *perfListener; -public: - JIT(Namespace &ns, bool enableObjectCache = true, - bool enableGDBNotificationListener = true, - bool enablePerfNotificationListener = true); + llvm::orc::JITTargetMachineBuilder jtmb; + llvm::DataLayout &dl; + SereneContext &ctx; - static MaybeJIT - make(Namespace &ns, mlir::ArrayRef sharedLibPaths = {}, - mlir::Optional jitCodeGenOptLevel = llvm::None, - bool enableObjectCache = true, bool enableGDBNotificationListener = true, - bool enablePerfNotificationListener = true); + std::shared_ptr activeNS; + +public: + Halley(serene::SereneContext &ctx, llvm::orc::JITTargetMachineBuilder &&jtmb, + llvm::DataLayout &&dl); + + // TODO: Read the sharedLibPaths via context + static MaybeJIT make(serene::SereneContext &ctx, + llvm::orc::JITTargetMachineBuilder &&jtmb); /// Looks up a packed-argument function with the given name and returns a /// pointer to it. Propagates errors in case of failure. @@ -165,9 +168,17 @@ public: llvm::function_ref symbolMap); - std::unique_ptr eval(SereneContext &ctx, - std::string input); + llvm::Optional addNS(Namespace &ns, + reader::LocationRange &loc); + llvm::Optional addNS(llvm::StringRef nsname, + reader::LocationRange &loc); + + Namespace &getActiveNS(); }; + +llvm::Expected> makeHalleyJIT(SereneContext &ctx); + +} // namespace jit } // namespace serene #endif diff --git a/src/libserene/CMakeLists.txt b/src/libserene/CMakeLists.txt index 800bf43..139085d 100644 --- a/src/libserene/CMakeLists.txt +++ b/src/libserene/CMakeLists.txt @@ -48,6 +48,7 @@ add_library(serene # jit.cpp jit/engine.cpp jit/layers.cpp + jit/halley.cpp # Reader reader/reader.cpp diff --git a/src/libserene/context.cpp b/src/libserene/context.cpp index f17d210..04d71ea 100644 --- a/src/libserene/context.cpp +++ b/src/libserene/context.cpp @@ -61,6 +61,14 @@ Namespace &SereneContext::getCurrentNS() { return *namespaces[this->current_ns]; }; +NSPtr SereneContext::getSharedPtrToNS(llvm::StringRef ns_name) { + if (namespaces.count(ns_name.str()) == 0) { + return nullptr; + } + + return namespaces[ns_name.str()]; +}; + void SereneContext::setOperationPhase(CompilationPhase phase) { this->targetPhase = phase; diff --git a/src/libserene/jit/engine.cpp b/src/libserene/jit/engine.cpp index 62d5606..6a04457 100644 --- a/src/libserene/jit/engine.cpp +++ b/src/libserene/jit/engine.cpp @@ -22,8 +22,6 @@ #include "serene/jit/layers.h" #include "serene/utils.h" -#include - #include namespace serene::jit { diff --git a/src/libserene/jit.cpp b/src/libserene/jit/halley.cpp similarity index 53% rename from src/libserene/jit.cpp rename to src/libserene/jit/halley.cpp index c352f41..4382213 100644 --- a/src/libserene/jit.cpp +++ b/src/libserene/jit/halley.cpp @@ -25,11 +25,16 @@ * license applies here */ -#include "serene/jit.h" +#include "serene/jit/halley.h" #include "serene/context.h" +#include "serene/errors/constants.h" +#include "serene/errors/error.h" #include "serene/namespace.h" +#include "serene/utils.h" +#include +#include #include #include #include @@ -37,6 +42,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -49,6 +57,7 @@ namespace serene { +namespace jit { // TODO: Remove this function and replace it by our own version of // error handler /// Wrap a string into an llvm::StringError. @@ -61,70 +70,73 @@ static std::string makePackedFunctionName(llvm::StringRef name) { return "_serene_" + name.str(); } -static void packFunctionArguments(llvm::Module *module) { - auto &ctx = module->getContext(); - llvm::IRBuilder<> builder(ctx); - llvm::DenseSet interfaceFunctions; - for (auto &func : module->getFunctionList()) { - if (func.isDeclaration()) { - continue; - } - if (interfaceFunctions.count(&func) != 0) { - continue; - } +// static void packFunctionArguments(llvm::Module *module) { +// auto &ctx = module->getContext(); +// llvm::IRBuilder<> builder(ctx); +// llvm::DenseSet interfaceFunctions; +// for (auto &func : module->getFunctionList()) { +// if (func.isDeclaration()) { +// continue; +// } +// if (interfaceFunctions.count(&func) != 0) { +// continue; +// } - // Given a function `foo(<...>)`, define the interface function - // `mlir_foo(i8**)`. - auto *newType = llvm::FunctionType::get( - builder.getVoidTy(), builder.getInt8PtrTy()->getPointerTo(), - /*isVarArg=*/false); - auto newName = makePackedFunctionName(func.getName()); - auto funcCst = module->getOrInsertFunction(newName, newType); - llvm::Function *interfaceFunc = - llvm::cast(funcCst.getCallee()); - interfaceFunctions.insert(interfaceFunc); +// // Given a function `foo(<...>)`, define the interface function +// // `serene_foo(i8**)`. +// auto *newType = llvm::FunctionType::get( +// builder.getVoidTy(), builder.getInt8PtrTy()->getPointerTo(), +// /*isVarArg=*/false); +// auto newName = makePackedFunctionName(func.getName()); +// auto funcCst = module->getOrInsertFunction(newName, newType); +// llvm::Function *interfaceFunc = +// llvm::cast(funcCst.getCallee()); +// interfaceFunctions.insert(interfaceFunc); - // Extract the arguments from the type-erased argument list and cast them to - // the proper types. - auto *bb = llvm::BasicBlock::Create(ctx); - bb->insertInto(interfaceFunc); - builder.SetInsertPoint(bb); - llvm::Value *argList = interfaceFunc->arg_begin(); - llvm::SmallVector args; - args.reserve(llvm::size(func.args())); - for (auto &indexedArg : llvm::enumerate(func.args())) { - llvm::Value *argIndex = llvm::Constant::getIntegerValue( - builder.getInt64Ty(), llvm::APInt(I64_BIT_SIZE, indexedArg.index())); - llvm::Value *argPtrPtr = - builder.CreateGEP(builder.getInt8PtrTy(), argList, argIndex); - llvm::Value *argPtr = - builder.CreateLoad(builder.getInt8PtrTy(), argPtrPtr); - llvm::Type *argTy = indexedArg.value().getType(); - argPtr = builder.CreateBitCast(argPtr, argTy->getPointerTo()); - llvm::Value *arg = builder.CreateLoad(argTy, argPtr); - args.push_back(arg); - } +// // Extract the arguments from the type-erased argument list and cast them +// to +// // the proper types. +// auto *bb = llvm::BasicBlock::Create(ctx); +// bb->insertInto(interfaceFunc); +// builder.SetInsertPoint(bb); +// llvm::Value *argList = interfaceFunc->arg_begin(); +// llvm::SmallVector args; +// args.reserve(llvm::size(func.args())); +// for (auto &indexedArg : llvm::enumerate(func.args())) { +// llvm::Value *argIndex = llvm::Constant::getIntegerValue( +// builder.getInt64Ty(), llvm::APInt(I64_BIT_SIZE, +// indexedArg.index())); +// llvm::Value *argPtrPtr = +// builder.CreateGEP(builder.getInt8PtrTy(), argList, argIndex); +// llvm::Value *argPtr = +// builder.CreateLoad(builder.getInt8PtrTy(), argPtrPtr); +// llvm::Type *argTy = indexedArg.value().getType(); +// argPtr = builder.CreateBitCast(argPtr, +// argTy->getPointerTo()); llvm::Value *arg = builder.CreateLoad(argTy, +// argPtr); args.push_back(arg); +// } - // Call the implementation function with the extracted arguments. - llvm::Value *result = builder.CreateCall(&func, args); +// // Call the implementation function with the extracted arguments. +// llvm::Value *result = builder.CreateCall(&func, args); - // Assuming the result is one value, potentially of type `void`. - if (!result->getType()->isVoidTy()) { - llvm::Value *retIndex = llvm::Constant::getIntegerValue( - builder.getInt64Ty(), - llvm::APInt(I64_BIT_SIZE, llvm::size(func.args()))); - llvm::Value *retPtrPtr = - builder.CreateGEP(builder.getInt8PtrTy(), argList, retIndex); - llvm::Value *retPtr = - builder.CreateLoad(builder.getInt8PtrTy(), retPtrPtr); - retPtr = builder.CreateBitCast(retPtr, result->getType()->getPointerTo()); - builder.CreateStore(result, retPtr); - } +// // Assuming the result is one value, potentially of type `void`. +// if (!result->getType()->isVoidTy()) { +// llvm::Value *retIndex = llvm::Constant::getIntegerValue( +// builder.getInt64Ty(), +// llvm::APInt(I64_BIT_SIZE, llvm::size(func.args()))); +// llvm::Value *retPtrPtr = +// builder.CreateGEP(builder.getInt8PtrTy(), argList, retIndex); +// llvm::Value *retPtr = +// builder.CreateLoad(builder.getInt8PtrTy(), retPtrPtr); +// retPtr = builder.CreateBitCast(retPtr, +// result->getType()->getPointerTo()); builder.CreateStore(result, +// retPtr); +// } - // The interface function returns void. - builder.CreateRetVoid(); - } -}; +// // The interface function returns void. +// builder.CreateRetVoid(); +// } +// }; void ObjectCache::notifyObjectCompiled(const llvm::Module *m, llvm::MemoryBufferRef objBuffer) { @@ -138,11 +150,12 @@ ObjectCache::getObject(const llvm::Module *m) { auto i = cachedObjects.find(m->getModuleIdentifier()); if (i == cachedObjects.end()) { - JIT_LOG("No object for " << m->getModuleIdentifier() - << " in cache. Compiling.\n"); + HALLEY_LOG("No object for " + m->getModuleIdentifier() + + " in cache. Compiling.\n"); return nullptr; } - JIT_LOG("Object for " << m->getModuleIdentifier() << " loaded from cache.\n"); + HALLEY_LOG("Object for " + m->getModuleIdentifier() + + " loaded from cache.\n"); return llvm::MemoryBuffer::getMemBuffer(i->second->getMemBufferRef()); } @@ -164,144 +177,22 @@ void ObjectCache::dumpToObjectFile(llvm::StringRef outputFilename) { file->keep(); } -JIT::JIT(Namespace &ns, bool enableObjectCache, - bool enableGDBNotificationListener, - bool enablePerfNotificationListener) - : ns(ns), cache(enableObjectCache ? new ObjectCache() : nullptr), - gdbListener(enableGDBNotificationListener +Halley::Halley(serene::SereneContext &ctx, + llvm::orc::JITTargetMachineBuilder &&jtmb, llvm::DataLayout &&dl) + : cache(ctx.opts.JITenableObjectCache ? new ObjectCache() : nullptr), + gdbListener(ctx.opts.JITenableGDBNotificationListener + ? llvm::JITEventListener::createGDBRegistrationListener() : nullptr), - perfListener(enablePerfNotificationListener + perfListener(ctx.opts.JITenablePerfNotificationListener ? llvm::JITEventListener::createPerfJITEventListener() - : nullptr){}; - -MaybeJIT JIT::make(Namespace &ns, - mlir::ArrayRef sharedLibPaths, - mlir::Optional jitCodeGenOptLevel, - bool enableObjectCache, bool enableGDBNotificationListener, - bool enablePerfNotificationListener) { - - auto jitEngine = std::make_unique(ns, enableObjectCache, - enableGDBNotificationListener, - enablePerfNotificationListener); - - // Why not the llvmcontext from the SereneContext?? - // Sice we're going to pass the ownership of this context to a thread - // safe module later on and we will only create the jit function wrappers - // with it, then it is fine to use a new context. - // - // 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 ctx(new llvm::LLVMContext); - - auto maybeModule = jitEngine->ns.compileToLLVM(); - - if (!maybeModule.hasValue()) { - return llvm::None; - } - - auto llvmModule = std::move(maybeModule.getValue()); - packFunctionArguments(llvmModule.get()); - - auto dataLayout = llvmModule->getDataLayout(); - - // 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) { - UNUSED(tt); - auto objectLayer = - std::make_unique(session, []() { - return std::make_unique(); - }); - - // 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 - llvm::Triple targetTriple(llvm::Twine(llvmModule->getTargetTriple())); - if (targetTriple.isOSBinFormatCOFF()) { - objectLayer->setOverrideObjectFlagsWithResponsibilityFlags(true); - objectLayer->setAutoClaimResponsibilityForObjectSymbols(true); - } - - // Resolve symbols from shared libraries. - for (auto libPath : sharedLibPaths) { - auto mb = llvm::MemoryBuffer::getFile(libPath); - if (!mb) { - llvm::errs() << "Failed to create MemoryBuffer for: " << libPath - << "\nError: " << mb.getError().message() << "\n"; - continue; - } - auto &JD = session.createBareJITDylib(std::string(libPath)); - auto loaded = llvm::orc::DynamicLibrarySearchGenerator::Load( - libPath.data(), dataLayout.getGlobalPrefix()); - if (!loaded) { - llvm::errs() << "Could not load " << libPath << ":\n " - << loaded.takeError() << "\n"; - continue; - } - - JD.addGenerator(std::move(*loaded)); - cantFail(objectLayer->add(JD, std::move(mb.get()))); - } - - return objectLayer; - }; - - // Callback to inspect the cache and recompile on demand. This follows Lang's - // LLJITWithObjectCache example. - auto compileFunctionCreator = [&](llvm::orc::JITTargetMachineBuilder JTMB) - -> llvm::Expected< - std::unique_ptr> { - if (jitCodeGenOptLevel) { - JTMB.setCodeGenOptLevel(jitCodeGenOptLevel.getValue()); - } - - auto TM = JTMB.createTargetMachine(); - if (!TM) { - return TM.takeError(); - } - - return std::make_unique( - std::move(*TM), jitEngine->cache.get()); - }; - - // Create the LLJIT by calling the LLJITBuilder with 2 callbacks. - auto jit = - cantFail(llvm::orc::LLJITBuilder() - .setCompileFunctionCreator(compileFunctionCreator) - .setObjectLinkingLayerCreator(objectLinkingLayerCreator) - .create()); - - // Add a ThreadSafemodule to the engine and return. - llvm::orc::ThreadSafeModule tsm(std::move(llvmModule), std::move(ctx)); - - // TODO: Do we need a module transformer here??? - - cantFail(jit->addIRModule(std::move(tsm))); - jitEngine->engine = std::move(jit); - - // Resolve symbols that are statically linked in the current process. - llvm::orc::JITDylib &mainJD = jitEngine->engine->getMainJITDylib(); - mainJD.addGenerator( - cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( - dataLayout.getGlobalPrefix()))); - - return MaybeJIT(std::move(jitEngine)); + : nullptr), + jtmb(jtmb), dl(dl), ctx(ctx), + activeNS(ctx.getSharedPtrToNS(ctx.getCurrentNS().name)) { + assert(activeNS != nullptr && "Active NS is null!!!"); }; -llvm::Expected JIT::lookup(llvm::StringRef name) const { +llvm::Expected Halley::lookup(llvm::StringRef name) const { auto expectedSymbol = engine->lookup(makePackedFunctionName(name)); // JIT lookup may return an Error referring to strings stored internally by @@ -329,8 +220,8 @@ llvm::Expected JIT::lookup(llvm::StringRef name) const { return fptr; } -llvm::Error JIT::invokePacked(llvm::StringRef name, - llvm::MutableArrayRef args) const { +llvm::Error Halley::invokePacked(llvm::StringRef name, + llvm::MutableArrayRef args) const { auto expectedFPtr = lookup(name); if (!expectedFPtr) { return expectedFPtr.takeError(); @@ -342,13 +233,173 @@ llvm::Error JIT::invokePacked(llvm::StringRef name, return llvm::Error::success(); } -void JIT::registerSymbols( +void Halley::registerSymbols( llvm::function_ref symbolMap) { auto &mainJitDylib = engine->getMainJITDylib(); cantFail(mainJitDylib.define( absoluteSymbols(symbolMap(llvm::orc::MangleAndInterner( mainJitDylib.getExecutionSession(), engine->getDataLayout()))))); -} +}; +llvm::Optional Halley::addNS(Namespace &ns, + reader::LocationRange &loc) { + // TODO: Fix compileToLLVM to return proper errors + auto maybeModule = ns.compileToLLVM(); + + if (!maybeModule.hasValue()) { + return errors::makeErrorTree(loc, errors::CompilationError); + } + + auto tsm = std::move(maybeModule.getValue()); + // tsm.withModuleDo([](llvm::Module &m) { packFunctionArguments(&m); }); + + // TODO: Make sure that the data layout of the module is the same as the + // engine + + cantFail(engine->addIRModule(std::move(tsm))); + return llvm::None; +}; + +llvm::Optional Halley::addNS(llvm::StringRef nsname, + reader::LocationRange &loc) { + auto maybeNS = ctx.sourceManager.readNamespace(ctx, nsname.str(), loc); + + if (!maybeNS) { + // TODO: Fix this by making Serene errors compatible with llvm::Error + auto err = maybeNS.getError(); + return err; + } + auto &ns = maybeNS.getValue(); + auto err = addNS(*ns, loc); + + if (err) { + return err.getValue(); + } + return llvm::None; +}; + +MaybeJIT Halley::make(SereneContext &serene_ctx, + llvm::orc::JITTargetMachineBuilder &&jtmb) { + + auto dl = jtmb.getDefaultDataLayoutForTarget(); + if (!dl) { + return dl.takeError(); + } + + auto jitEngine = + std::make_unique(serene_ctx, std::move(jtmb), std::move(*dl)); + + // Why not the llvmcontext from the SereneContext?? + // Sice we're going to pass the ownership of this context to a thread + // safe module later on and we will only create the jit function wrappers + // with it, then it is fine to use a new context. + // + // 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 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) { + UNUSED(tt); + + auto objectLayer = + std::make_unique(session, []() { + return std::make_unique(); + }); + + // 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 + llvm::Triple targetTriple(llvm::Twine(serene_ctx.targetTriple)); + + if (targetTriple.isOSBinFormatCOFF()) { + objectLayer->setOverrideObjectFlagsWithResponsibilityFlags(true); + objectLayer->setAutoClaimResponsibilityForObjectSymbols(true); + } + + // Resolve symbols from shared libraries. + // for (auto libPath : sharedLibPaths) { + // auto mb = llvm::MemoryBuffer::getFile(libPath); + // if (!mb) { + // llvm::errs() << "Failed to create MemoryBuffer for: " << libPath + // << "\nError: " << mb.getError().message() << "\n"; + // continue; + // } + // auto &JD = session.createBareJITDylib(std::string(libPath)); + // auto loaded = llvm::orc::DynamicLibrarySearchGenerator::Load( + // libPath.data(), dataLayout.getGlobalPrefix()); + // if (!loaded) { + // llvm::errs() << "Could not load " << libPath << ":\n " + // << loaded.takeError() << "\n"; + // continue; + // } + + // JD.addGenerator(std::move(*loaded)); + // cantFail(objectLayer->add(JD, std::move(mb.get()))); + // } + + return objectLayer; + }; + + // Callback to inspect the cache and recompile on demand. This follows Lang's + // LLJITWithObjectCache example. + auto compileFunctionCreator = [&](llvm::orc::JITTargetMachineBuilder JTMB) + -> llvm::Expected< + std::unique_ptr> { + llvm::CodeGenOpt::Level jitCodeGenOptLevel = + static_cast(serene_ctx.getOptimizatioLevel()); + + JTMB.setCodeGenOptLevel(jitCodeGenOptLevel); + + auto targetMachine = JTMB.createTargetMachine(); + if (!targetMachine) { + return targetMachine.takeError(); + } + + return std::make_unique( + std::move(*targetMachine), jitEngine->cache.get()); + }; + + // Create the LLJIT by calling the LLJITBuilder with 2 callbacks. + auto jit = + cantFail(llvm::orc::LLJITBuilder() + .setCompileFunctionCreator(compileFunctionCreator) + .setObjectLinkingLayerCreator(objectLinkingLayerCreator) + .create()); + + jitEngine->engine = std::move(jit); + + // Resolve symbols that are statically linked in the current process. + llvm::orc::JITDylib &mainJD = jitEngine->engine->getMainJITDylib(); + mainJD.addGenerator( + cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + jitEngine->dl.getGlobalPrefix()))); + + return MaybeJIT(std::move(jitEngine)); +}; + +Namespace &Halley::getActiveNS() { return *activeNS; }; + +llvm::Expected> makeHalleyJIT(SereneContext &ctx) { + + llvm::orc::JITTargetMachineBuilder jtmb(ctx.getTargetTriple()); + + return Halley::make(ctx, std::move(jtmb)); +}; +} // namespace jit } // namespace serene diff --git a/src/libserene/passes/to_llvm_dialect.cpp b/src/libserene/passes/to_llvm_dialect.cpp index 3d65db5..24f6ecd 100644 --- a/src/libserene/passes/to_llvm_dialect.cpp +++ b/src/libserene/passes/to_llvm_dialect.cpp @@ -19,8 +19,8 @@ #include "serene/passes.h" #include "serene/slir/dialect.h" -#include #include +#include #include #include #include @@ -32,6 +32,8 @@ #include #include +#include + namespace serene::passes { struct SLIRToLLVMDialect : public mlir::PassWrapper(&getContext()); // We want to completely lower to LLVM, so we use a `FullConversion`. This diff --git a/src/libserene/serene.cpp b/src/libserene/serene.cpp index 6b9e71f..0c7af77 100644 --- a/src/libserene/serene.cpp +++ b/src/libserene/serene.cpp @@ -109,32 +109,30 @@ SERENE_EXPORT exprs::MaybeNode eval(SereneContext &ctx, exprs::Ast &input) { UNUSED(input); auto loc = reader::LocationRange::UnknownLocation("nsname"); - auto err = ctx.jit->addNS("docs.examples.hello_world"); + auto err = ctx.jit->addNS("docs.examples.hello_world", loc); if (err) { - llvm::errs() << err; - auto e = errors::makeErrorTree(loc, errors::NSLoadError); - - return exprs::makeErrorNode(loc, errors::NSLoadError); + auto es = err.getValue(); + auto nsloadErr = errors::makeError(loc, errors::NSLoadError); + es.push_back(nsloadErr); + return exprs::MaybeNode::error(es); } + std::string tmp("main"); llvm::ExitOnError e; // Get the anonymous expression's JITSymbol. auto sym = e(ctx.jit->lookup(tmp)); llvm::outs() << "eval here\n"; - // Get the symbol's address and cast it to the right type (takes no - // arguments, returns a double) so we can call it as a native function. - auto *f = (int (*)())(intptr_t)sym.getAddress(); - f(); + sym((void **)3); - err = ctx.jit->addAst(input); - if (err) { - llvm::errs() << err; - auto e = errors::makeErrorTree(loc, errors::NSLoadError); + // err = ctx.jit->addAst(input); + // if (err) { + // llvm::errs() << err; + // auto e = errors::makeErrorTree(loc, errors::NSLoadError); - return exprs::makeErrorNode(loc, errors::NSLoadError); - } + // return exprs::makeErrorNode(loc, errors::NSLoadError); + // } return exprs::make(loc, "4", false, false); }; diff --git a/src/serene-repl/serene-repl.cpp b/src/serene-repl/serene-repl.cpp index 77daf0e..7091f8f 100644 --- a/src/serene-repl/serene-repl.cpp +++ b/src/serene-repl/serene-repl.cpp @@ -73,7 +73,7 @@ int main(int argc, char *argv[]) { // Read line std::string line; std::string result; - std::string prompt = ctx->jit->getCurrentNS().name + "> "; + std::string prompt = ctx->jit->getActiveNS().name + "> "; auto quit = linenoise::Readline(prompt.c_str(), line); diff --git a/src/serenec/serenec.cpp b/src/serenec/serenec.cpp index c45981a..c808e95 100644 --- a/src/serenec/serenec.cpp +++ b/src/serenec/serenec.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "serene/jit.h" +#include "serene/jit/halley.h" #include "serene/namespace.h" #include "serene/reader/location.h" #include "serene/reader/reader.h"