Add the Halley JIT and migrate to it
This commit is contained in:
parent
123a3e8d4f
commit
c41c91b335
|
@ -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 <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/StandardOps/IR/Ops.h>
|
||||
|
@ -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<DiagnosticEngine> diagEngine;
|
||||
|
||||
std::unique_ptr<serene::jit::SereneJIT> jit;
|
||||
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.
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<ErrID, ErrorVariant *> ErrDesc = {
|
||||
{E0000, &UnknownError}, {E0001, &DefExpectSymbol},
|
||||
{E0002, &DefWrongNumberOfArgs}, {E0003, &FnNoArgsList},
|
||||
|
@ -113,7 +116,8 @@ static std::map<ErrID, ErrorVariant *> 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
|
||||
|
|
|
@ -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 <llvm/ADT/StringRef.h>
|
||||
|
@ -40,13 +38,19 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#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<std::unique_ptr<JIT>>;
|
||||
class SereneContext;
|
||||
class Namespace;
|
||||
|
||||
namespace jit {
|
||||
class Halley;
|
||||
|
||||
using MaybeJIT = llvm::Expected<std::unique_ptr<Halley>>;
|
||||
|
||||
/// A simple object cache following Lang's LLJITWithObjectCache example and
|
||||
/// MLIR's SimpelObjectCache.
|
||||
|
@ -67,12 +71,8 @@ private:
|
|||
llvm::StringMap<std::unique_ptr<llvm::MemoryBuffer>> cachedObjects;
|
||||
};
|
||||
|
||||
class JIT {
|
||||
// TODO: Should the JIT own the context ???
|
||||
Namespace &ns;
|
||||
|
||||
class SERENE_EXPORT Halley {
|
||||
std::unique_ptr<llvm::orc::LLJIT> engine;
|
||||
|
||||
std::unique_ptr<ObjectCache> 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<llvm::StringRef> sharedLibPaths = {},
|
||||
mlir::Optional<llvm::CodeGenOpt::Level> jitCodeGenOptLevel = llvm::None,
|
||||
bool enableObjectCache = true, bool enableGDBNotificationListener = true,
|
||||
bool enablePerfNotificationListener = true);
|
||||
std::shared_ptr<Namespace> 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<llvm::orc::SymbolMap(llvm::orc::MangleAndInterner)>
|
||||
symbolMap);
|
||||
|
||||
std::unique_ptr<exprs::Expression> eval(SereneContext &ctx,
|
||||
std::string input);
|
||||
llvm::Optional<errors::ErrorTree> addNS(Namespace &ns,
|
||||
reader::LocationRange &loc);
|
||||
llvm::Optional<errors::ErrorTree> addNS(llvm::StringRef nsname,
|
||||
reader::LocationRange &loc);
|
||||
|
||||
Namespace &getActiveNS();
|
||||
};
|
||||
|
||||
llvm::Expected<std::unique_ptr<Halley>> makeHalleyJIT(SereneContext &ctx);
|
||||
|
||||
} // namespace jit
|
||||
} // namespace serene
|
||||
|
||||
#endif
|
|
@ -48,6 +48,7 @@ add_library(serene
|
|||
# jit.cpp
|
||||
jit/engine.cpp
|
||||
jit/layers.cpp
|
||||
jit/halley.cpp
|
||||
|
||||
# Reader
|
||||
reader/reader.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;
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
#include "serene/jit/layers.h"
|
||||
#include "serene/utils.h"
|
||||
|
||||
#include <llvm/ExecutionEngine/JITSymbol.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace serene::jit {
|
||||
|
|
|
@ -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 <llvm/ADT/None.h>
|
||||
#include <llvm/ADT/Optional.h>
|
||||
#include <llvm/ExecutionEngine/Orc/CompileUtils.h>
|
||||
#include <llvm/ExecutionEngine/Orc/ExecutionUtils.h>
|
||||
#include <llvm/ExecutionEngine/Orc/IRCompileLayer.h>
|
||||
|
@ -37,6 +42,9 @@
|
|||
#include <llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h>
|
||||
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
|
||||
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
|
||||
#include <llvm/IR/Module.h>
|
||||
#include <llvm/Support/ErrorHandling.h>
|
||||
#include <llvm/Support/MemoryBuffer.h>
|
||||
#include <llvm/Support/ToolOutputFile.h>
|
||||
#include <mlir/ExecutionEngine/ExecutionEngine.h>
|
||||
#include <mlir/Support/FileUtilities.h>
|
||||
|
@ -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<llvm::Function *> 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<llvm::Function *> 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<llvm::Function>(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<llvm::Function>(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<llvm::Value *, COMMON_ARGS_COUNT> 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<llvm::Value *, COMMON_ARGS_COUNT> 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<llvm::StringRef> sharedLibPaths,
|
||||
mlir::Optional<llvm::CodeGenOpt::Level> jitCodeGenOptLevel,
|
||||
bool enableObjectCache, bool enableGDBNotificationListener,
|
||||
bool enablePerfNotificationListener) {
|
||||
|
||||
auto jitEngine = std::make_unique<JIT>(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<llvm::LLVMContext> 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<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
|
||||
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<llvm::orc::IRCompileLayer::IRCompiler>> {
|
||||
if (jitCodeGenOptLevel) {
|
||||
JTMB.setCodeGenOptLevel(jitCodeGenOptLevel.getValue());
|
||||
}
|
||||
|
||||
auto TM = JTMB.createTargetMachine();
|
||||
if (!TM) {
|
||||
return TM.takeError();
|
||||
}
|
||||
|
||||
return std::make_unique<llvm::orc::TMOwningSimpleCompiler>(
|
||||
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<void (*)(void **)> JIT::lookup(llvm::StringRef name) const {
|
||||
llvm::Expected<void (*)(void **)> 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<void (*)(void **)> JIT::lookup(llvm::StringRef name) const {
|
|||
return fptr;
|
||||
}
|
||||
|
||||
llvm::Error JIT::invokePacked(llvm::StringRef name,
|
||||
llvm::MutableArrayRef<void *> args) const {
|
||||
llvm::Error Halley::invokePacked(llvm::StringRef name,
|
||||
llvm::MutableArrayRef<void *> 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<llvm::orc::SymbolMap(llvm::orc::MangleAndInterner)>
|
||||
symbolMap) {
|
||||
auto &mainJitDylib = engine->getMainJITDylib();
|
||||
cantFail(mainJitDylib.define(
|
||||
absoluteSymbols(symbolMap(llvm::orc::MangleAndInterner(
|
||||
mainJitDylib.getExecutionSession(), engine->getDataLayout())))));
|
||||
}
|
||||
};
|
||||
|
||||
llvm::Optional<errors::ErrorTree> 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<errors::ErrorTree> 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<Halley>(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<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) {
|
||||
UNUSED(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
|
||||
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::orc::IRCompileLayer::IRCompiler>> {
|
||||
llvm::CodeGenOpt::Level jitCodeGenOptLevel =
|
||||
static_cast<llvm::CodeGenOpt::Level>(serene_ctx.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());
|
||||
};
|
||||
|
||||
// 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<std::unique_ptr<Halley>> makeHalleyJIT(SereneContext &ctx) {
|
||||
|
||||
llvm::orc::JITTargetMachineBuilder jtmb(ctx.getTargetTriple());
|
||||
|
||||
return Halley::make(ctx, std::move(jtmb));
|
||||
};
|
||||
} // namespace jit
|
||||
} // namespace serene
|
|
@ -19,8 +19,8 @@
|
|||
#include "serene/passes.h"
|
||||
#include "serene/slir/dialect.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mlir/Conversion/AffineToStandard/AffineToStandard.h>
|
||||
#include <mlir/Conversion/ArithmeticToLLVM/ArithmeticToLLVM.h>
|
||||
#include <mlir/Conversion/LLVMCommon/ConversionTarget.h>
|
||||
#include <mlir/Conversion/LLVMCommon/TypeConverter.h>
|
||||
#include <mlir/Conversion/SCFToStandard/SCFToStandard.h>
|
||||
|
@ -32,6 +32,8 @@
|
|||
#include <mlir/Pass/Pass.h>
|
||||
#include <mlir/Transforms/DialectConversion.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace serene::passes {
|
||||
struct SLIRToLLVMDialect
|
||||
: public mlir::PassWrapper<SLIRToLLVMDialect,
|
||||
|
@ -68,7 +70,8 @@ void SLIRToLLVMDialect::runOnOperation() {
|
|||
mlir::RewritePatternSet patterns(&getContext());
|
||||
|
||||
populateStdToLLVMConversionPatterns(typeConverter, patterns);
|
||||
|
||||
mlir::arith::populateArithmeticToLLVMConversionPatterns(typeConverter,
|
||||
patterns);
|
||||
// patterns.add<PrintOpLowering>(&getContext());
|
||||
|
||||
// We want to completely lower to LLVM, so we use a `FullConversion`. This
|
||||
|
|
|
@ -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<exprs::Number>(loc, "4", false, false);
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "serene/jit.h"
|
||||
#include "serene/jit/halley.h"
|
||||
#include "serene/namespace.h"
|
||||
#include "serene/reader/location.h"
|
||||
#include "serene/reader/reader.h"
|
||||
|
|
Loading…
Reference in New Issue