diff --git a/include/xo/jit/activation_record.new.hpp b/include/xo/jit/activation_record.new.hpp new file mode 100644 index 00000000..7c70ba3f --- /dev/null +++ b/include/xo/jit/activation_record.new.hpp @@ -0,0 +1,66 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# include +#pragma GCC diagnostic pop +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record(llvm::Function * llvm_fn, + llvm::AllocaInst * frame) : frame_{frame} { + int i_arg = 0; + for (auto & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + + name2ix_map_[arg_name] = 2 + i_arg; + } + } + + std::int32_t lookup_var(const std::string & var_name) const; + +#ifdef OBSOLETE + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); +#endif + + private: + /** stack frame for a user-defined function (lambda) **/ + llvm::AllocaInst * frame_ = nullptr; + + /** for each formal parameter, + * reports its position in stack frame. + * This is the position to use with getelementptr, + * i.e. +2 to skip first two slots, that are reserved + * for nextframe pointer (slot 0) + unwind pointer (slot 1) + **/ + std::map name2ix_map_; + +#ifdef OBSOLETE + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ +#endif + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/include/xo/jit/activation_record.orig.hpp b/include/xo/jit/activation_record.orig.hpp new file mode 100644 index 00000000..c2aba2dd --- /dev/null +++ b/include/xo/jit/activation_record.orig.hpp @@ -0,0 +1,41 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# include +#pragma GCC diagnostic pop +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record() = default; + + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); + + private: + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 35a6e739..fb108a3e 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -8,7 +8,10 @@ #include "LlvmContext.hpp" #include "xo/expression/Lambda.hpp" #include "xo/reflect/TypeDescr.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" #include +#pragma GCC diagnostic pop //#include namespace xo { diff --git a/src/jit/MachPipeline.new.cpp b/src/jit/MachPipeline.new.cpp new file mode 100644 index 00000000..c4faff4a --- /dev/null +++ b/src/jit/MachPipeline.new.cpp @@ -0,0 +1,1341 @@ +/* @file MachPipeline.cpp */ + +#include "MachPipeline.hpp" +#include + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + //using xo::ast::FunctionInterface; + using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; + using xo::ast::Apply; + using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; + using xo::reflect::Reflect; + using xo::reflect::StructMember; + using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; + using llvm::DataLayout; + using std::cerr; + using std::endl; + + namespace jit { + void + MachPipeline::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_compile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + MachPipeline::make_aux() + { + MachPipeline::init_once(); + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); + + return std::unique_ptr(new MachPipeline(std::move(jit) + )); + } /*make*/ + + xo::ref::rp + MachPipeline::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} + { + this->recreate_llvm_ir_pipeline(); + } + + void + MachPipeline::recreate_llvm_ir_pipeline() + { + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(this->jit_->data_layout()); + + if (!llvm_cx_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); + } + if (!llvm_toplevel_ir_builder_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); + } + + ir_pipeline_ = new IrPipeline(llvm_cx_); + } /*recreate_llvm_ir_pipeline*/ + + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ + const std::string & + MachPipeline::target_triple() const { + // although this getter is defined, seems to be empty in practice + //return llvm_module_->getTargetTriple(); + + return this->jit_->target_triple(); + } + + std::vector + MachPipeline::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + MachPipeline::dump_execution_session() { + this->jit_->dump_execution_session(); + } + + llvm::Value * + MachPipeline::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } /*codegen_constant*/ + + namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + + llvm::Function * + MachPipeline::codegen_primitive(ref::brw expr) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygenL/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!llvm_fn_type) + return nullptr; + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + +#ifdef NOT_USING + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } +#endif + + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); + } + +#ifdef OBSOLETE + log && log("returning llvm function"); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + + using std::cerr; + using std::endl; + + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. + */ + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + { + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + + if (pm) { + llvm_fnval = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); + } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); + + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ + } + } + + if (!llvm_fnval) { + return nullptr; + } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ + switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + + } /*codegen_apply*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return nullptr; + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_block_alloca*/ + + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, + llvm::StructType * frame_llvm_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + if (!frame_llvm_type) + return nullptr; + + if (log) { + std::string llvm_frame_type_str; + llvm::raw_string_ostream ss(llvm_frame_type_str); + frame_llvm_type->print(ss); + + log(xtag("frame_llvm_type", llvm_frame_type_str)); + } + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, + nullptr, + llvm_fn->getName()); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_frame_alloca*/ + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + + llvm::Function * + MachPipeline::codegen_lambda_decl(ref::brw lambda) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + return fn; + } + + /* establish prototype for this function */ + +#ifdef OBSOLETE + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); + + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); + } +#endif + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)->name()); + ++i; + } + } + + return fn; + } /*codegen_lambda_decl*/ + + namespace { + /** A function type: + * + * _baseframe* (*) (_baseframe* + **/ + llvm::FunctionType * + require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, + llvm::PointerType * frameptr_llvm_type) + { + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is frame pointer */ + llvm_argtype_v.push_back(frameptr_llvm_type); + + /* 2nd arg is an i32. + * 0 -> unwind. + * 1 -> lift to heap (someday) + */ + llvm_argtype_v.push_back + (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is frame pointer */ + llvm::Type * retval_llvm_type = frameptr_llvm_type; + + auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + return unwind_llvm_type; + } /*require_baseframe_unwind_llvm_type*/ + + /** Each lambda gets its own stack frame definition. + * However all the various frame representations share the same 'baseframe' + * prefix. + * + * _baseframe: + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) + * +-------+ + * + * This helper function generates an llvm::Type* for a baseframe. + * It only needs to be invoked once (per LlvmContext, I guess ..) + **/ + llvm::StructType * + require_baseframe_llvm_type(xo::ref::brw llvm_cx) + { + /* _baseframe: base type for a stack frame */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), + "_baseframe"); + + /* _baseframe*: pointer to a stack frame */ + llvm::PointerType * frameptr_llvm_type + = llvm::PointerType::getUnqual(frame_llvm_type); + + /* unwind function = frame[1] */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + frameptr_llvm_type); + + /* _baseframe members */ + std::vector llvm_membertype_v; + { + llvm_membertype_v.reserve(2); + + /* frame[0] = pointer to next frame */ + llvm_membertype_v.push_back(frameptr_llvm_type); + /* frame[1] = unwind function */ + llvm_membertype_v.push_back(unwind_llvm_type); + } + + frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, + unwind_llvm_type /*frame[1]*/); + + return frame_llvm_type; + } /*require_baseframe_llvm_type*/ + + llvm::PointerType * + require_baseframe_ptr_llvm_type(xo::ref::brw llvm_cx) + { + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + return llvm::PointerType::getUnqual(baseframe_llvm_type); + } + + /** need a supporting type for stack frame + * - so we can handle variables with non-trivial dtors + * (e.g. smart pointers) + * - so we can implement nested lexical scoping + * - so we can walk stack for exception handling + * - eventually: so we can eventually implement trampoline + * + * frame representation: + * + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) + * +-------| + * arg[i] [2+i] | ... | + * +-------+ + * + * invoke frame.unwind_fn to dispose of a frame + * - ctl=0 dtor. deal with smart pointers etc. + * - ctl=1 copy. lift frame into heap for lambda capture + * + * every frame is a subtype of _baseframe (ofc llvm doesn't know this). + * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above + * + * editor bait: activation_record_to_llvm_type + **/ + llvm::StructType * + frame_to_llvm_type(xo::ref::brw llvm_cx, + ref::brw lambda, + llvm::Function * lambda_llvm_fn) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* frame type doesn't need a name */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + /* _baseframe */ + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + /* _baseframe*: pointer to a generic stack frame */ + llvm::PointerType * baseframeptr_llvm_type + = llvm::PointerType::getUnqual(baseframe_llvm_type); + + /* _baseframe* (*)(_baseframe*, i32) */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + baseframeptr_llvm_type); + + /* llvm_argtype_v: + * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame + * - llvm_argtype_v[1] = llvm::Type* for unwind_fn + * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) + */ + std::vector llvm_argtype_v; + { + llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); + + /* frame pointer */ + llvm_argtype_v.push_back(baseframeptr_llvm_type); + /* unwind function */ + llvm_argtype_v.push_back(unwind_llvm_type); + + int i_arg = 0; + for (auto & arg : lambda_llvm_fn->args()) { + log && log(xtag("i_arg", i_arg), + xtag("param", std::string(arg.getName()))); + + llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, + lambda->fn_arg(i_arg))); + + ++i_arg; + } + } + + frame_llvm_type->setBody(llvm_argtype_v); + + return frame_llvm_type; + } /*frame_to_llvm_type*/ + } /*namespace*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + /* generate function body -- code to execute later if/when function is called */ + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + + ir_builder.SetInsertPoint(block); + + /* create stack frame */ + llvm::StructType * frame_llvm_type + = frame_to_llvm_type(llvm_cx_, + lambda, + llvm_fn); + + llvm::AllocaInst * frame_alloca + = create_entry_frame_alloca(llvm_fn, + frame_llvm_type); + + if (!frame_alloca) + return nullptr; + + /** Actual parameters will need their own activation record. + * Track its shape here. + * + * Local variables will be formal parameters to a nested lambda; + **/ + this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); + + { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + + int i_slot = 0; + int n_slot = 2 + lambda->n_arg(); + + auto arg_ix = llvm_fn->arg_begin(); + + for (i_slot = 0; i_slot < n_slot; ++i_slot) { + // TODO: move the frame-slot computation into helper function + + /* argument i_slot-2 */ + llvm::Value * frame_slot_ptr = nullptr; + { + /* note: we have to create instructions here because + * the llvm IR is invariant w.r.t. data layout. + * But we can't compute byte offsets until later + * when data layout is revealed. + */ + + llvm::Value * i32_zero + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, 0)); + llvm::Value * i32_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, i_slot)); + + std::array index_v = { + {i32_zero /*deref frame pointer*/, + i32_slot /*field# relative to frame pointer*/}}; + + /* location in stack frame of arg #i */ + frame_slot_ptr + = ir_builder.CreateInBoundsGEP(frame_llvm_type, + frame_alloca, + index_v); + } + + if (i_slot == 0) { + /* frame pointer */ + assert(false); + } else if (i_slot == 1) { + /* unwind function */ + assert(false); + } else { + int i_arg = i_slot - 2; + + std::string arg_name = std::string(lambda->argv().at(i_arg)->name()); + + /* store param on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&(*arg_ix), + frame_slot_ptr /*destination*/); + + ++arg_ix; + } + } + } + + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + + if (retval) { + /* completes the function.. */ + ir_builder.CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*llvm_fn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); + + llvm_fn = nullptr; + } + + this->env_stack_.pop(); + + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ + + /* frame pointer */ + llvm::Value * + MachPipeline::codegen_global_frameptr(llvm::IRBuilder<> & ir_builder) + { + /* only want to jit this once */ + + /* though really: this would be once per thread */ + + llvm::Type * baseframe_ptr_llvm_type + = require_baseframe_ptr_llvm_type(llvm_cx_); + + /** nullptr - initial value for global frame pointer **/ + llvm::Constant * nullptr_llvm_value + = llvm::ConstantPointerNull::get(baseframe_ptr_llvm_type); + + llvm::Value * fp_llvm_value + = llvm::GlobalVariable(baseframe_ptr_llvm_type, + false /*!isConstant*/ + llvm::GlobalValues::LinkOnceOdrLinkage, + nullptr_llvm_value /*Initializer*/, + "frame_pointer"); + + } /*codegen_global_frameptr*/ + + llvm::Value * + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) + { + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" + << xtag("x", var->name()) + << endl; + + return nullptr; + } + + std::int32_t i_slot = env_stack_.top().lookup_var(var->name()); + //llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; + + /* code to load value from stack */ + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); + } /*codegen_variable*/ + + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + + /** need test result in a variable **/ + llvm::Value * test_with_cmp_ir + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + ir_builder.SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = ir_builder.GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = ir_builder.GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + ir_builder.SetInsertPoint(merge_bb); + + /** TODO: switch to getInt1Ty here **/ + llvm::PHINode * phi_node + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } /*codegen_ifexpr*/ + + llvm::Value * + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + return this->codegen_apply(Apply::from(expr), ir_builder); + case exprtype::lambda: + return this->codegen_lambda_decl(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr), ir_builder); + case exprtype::ifexpr: + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + cerr << "MachPipeline::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + + return nullptr; + } /*codegen*/ + + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + + void + MachPipeline::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); + + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; + + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } /*machgen_current_module*/ + + std::string_view + MachPipeline::mangle(const std::string & sym) const + { + return this->jit_->mangle(sym); + } /*mangle*/ + + llvm::Expected + MachPipeline::lookup_symbol(const std::string & sym) + { + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym_expected = this->jit_->lookup(sym); + + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); + + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } + } /*lookup_symbol*/ + + void + MachPipeline::display(std::ostream & os) const { + os << ""; + } + + std::string + MachPipeline::display_string() const { + return tostr(*this); + } + } /*namespace jit*/ +} /*namespace xo*/ + +/* end MachPipeline.cpp */ diff --git a/src/jit/MachPipeline.orig.cpp b/src/jit/MachPipeline.orig.cpp new file mode 100644 index 00000000..69cf0d02 --- /dev/null +++ b/src/jit/MachPipeline.orig.cpp @@ -0,0 +1,1064 @@ +/* @file MachPipeline.cpp */ + +#include "MachPipeline.hpp" +#include + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + //using xo::ast::FunctionInterface; + using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; + using xo::ast::Apply; + using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; + using xo::reflect::Reflect; + using xo::reflect::StructMember; + using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; + using llvm::DataLayout; + using std::cerr; + using std::endl; + + namespace jit { + void + MachPipeline::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_compile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + MachPipeline::make_aux() + { + MachPipeline::init_once(); + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); + + return std::unique_ptr(new MachPipeline(std::move(jit) + )); + } /*make*/ + + xo::ref::rp + MachPipeline::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} + { + this->recreate_llvm_ir_pipeline(); + } + + void + MachPipeline::recreate_llvm_ir_pipeline() + { + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(this->jit_->data_layout()); + + if (!llvm_cx_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); + } + if (!llvm_toplevel_ir_builder_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); + } + + ir_pipeline_ = new IrPipeline(llvm_cx_); + } /*recreate_llvm_ir_pipeline*/ + + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ + const std::string & + MachPipeline::target_triple() const { + // although this getter is defined, seems to be empty in practice + //return llvm_module_->getTargetTriple(); + + return this->jit_->target_triple(); + } + + std::vector + MachPipeline::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + MachPipeline::dump_execution_session() { + this->jit_->dump_execution_session(); + } + + llvm::Value * + MachPipeline::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } /*codegen_constant*/ + + namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + + llvm::Function * + MachPipeline::codegen_primitive(ref::brw expr) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygenL/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!llvm_fn_type) + return nullptr; + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + +#ifdef NOT_USING + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } +#endif + + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); + } + +#ifdef OBSOLETE + log && log("returning llvm function"); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + + using std::cerr; + using std::endl; + + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. + */ + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + { + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + + if (pm) { + llvm_fnval = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); + } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); + + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ + } + } + + if (!llvm_fnval) { + return nullptr; + } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ + switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + + } /*codegen_apply*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return nullptr; + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_block_alloca*/ + + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + + llvm::Function * + MachPipeline::codegen_lambda_decl(ref::brw lambda) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + return fn; + } + + /* establish prototype for this function */ + +#ifdef OBSOLETE + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); + + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); + } +#endif + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)->name()); + ++i; + } + } + + return fn; + } /*codegen_lambda_decl*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + + /* generate function body */ + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + + ir_builder.SetInsertPoint(block); + + /** Actual parameters will need their own activation record. + * Track its shape here. + **/ + this->env_stack_.push(activation_record()); + + { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + + int i = 0; + for (auto & arg : llvm_fn->args()) { + log && log("nested environment", + xtag("i", i), + xtag("param", std::string(arg.getName()))); + + std::string arg_name = std::string(arg.getName()); + + /* stack location for arg[i] */ + llvm::AllocaInst * alloca + = create_entry_block_alloca(llvm_fn, + arg_name, + lambda->fn_arg(i)); + + if (!alloca) { + this->env_stack_.pop(); + return nullptr; + } + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, alloca); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + env_stack_.top().alloc_var(arg_name, alloca); + ++i; + } + } + + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + + if (retval) { + /* completes the function.. */ + ir_builder.CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*llvm_fn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); + + llvm_fn = nullptr; + } + + this->env_stack_.pop(); + + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ + + llvm::Value * + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) + { + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" + << xtag("x", var->name()) + << endl; + + return nullptr; + } + + llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; + + /* code to load value from stack */ + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); + } /*codegen_variable*/ + + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + + /** need test result in a variable **/ + llvm::Value * test_with_cmp_ir + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + ir_builder.SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = ir_builder.GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = ir_builder.GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + ir_builder.SetInsertPoint(merge_bb); + + /** TODO: switch to getInt1Ty here **/ + llvm::PHINode * phi_node + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } /*codegen_ifexpr*/ + + llvm::Value * + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + return this->codegen_apply(Apply::from(expr), ir_builder); + case exprtype::lambda: + return this->codegen_lambda_decl(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr), ir_builder); + case exprtype::ifexpr: + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + cerr << "MachPipeline::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + + return nullptr; + } /*codegen*/ + + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + + void + MachPipeline::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); + + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; + + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } /*machgen_current_module*/ + + std::string_view + MachPipeline::mangle(const std::string & sym) const + { + return this->jit_->mangle(sym); + } /*mangle*/ + + llvm::Expected + MachPipeline::lookup_symbol(const std::string & sym) + { + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym_expected = this->jit_->lookup(sym); + + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); + + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } + } /*lookup_symbol*/ + + void + MachPipeline::display(std::ostream & os) const { + os << ""; + } + + std::string + MachPipeline::display_string() const { + return tostr(*this); + } + } /*namespace jit*/ +} /*namespace xo*/ + +/* end MachPipeline.cpp */ diff --git a/src/jit/activation_record.new.cpp b/src/jit/activation_record.new.cpp new file mode 100644 index 00000000..cc1aaae3 --- /dev/null +++ b/src/jit/activation_record.new.cpp @@ -0,0 +1,47 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + int32_t + activation_record::lookup_var(const std::string & x) const { + + auto ix = name2ix_map_.find(x); + + if (ix == name2ix_map_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return -1; + } + + return ix->second; + } /*lookup_var*/ + +#ifdef OBSOLETE + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ +#endif + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */ diff --git a/src/jit/activation_record.orig.cpp b/src/jit/activation_record.orig.cpp new file mode 100644 index 00000000..c7f40362 --- /dev/null +++ b/src/jit/activation_record.orig.cpp @@ -0,0 +1,45 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + llvm::AllocaInst * + activation_record::lookup_var(const std::string & x) const { + + auto ix = frame_.find(x); + + if (ix == frame_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return nullptr; + } + + return ix->second; + } /*lookup_var*/ + + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */