diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 0214a265..22e1b0b7 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -67,7 +67,7 @@ main() { log && log(xtag("expr", expr)); - auto llvm_ircode = jit->codegen(expr); + auto llvm_ircode = jit->codegen_toplevel(expr); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -88,7 +88,7 @@ main() { log && log(xtag("expr", expr)); - auto llvm_ircode = jit->codegen(expr); + auto llvm_ircode = jit->codegen_toplevel(expr); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -114,7 +114,7 @@ main() { log && log(xtag("expr", call)); - auto llvm_ircode = jit->codegen(call); + auto llvm_ircode = jit->codegen_toplevel(call); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -151,7 +151,7 @@ main() { log && log(xtag("expr", lambda)); - auto llvm_ircode = jit->codegen(lambda); + auto llvm_ircode = jit->codegen_toplevel(lambda); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 75244b93..2497fb5f 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -11,6 +11,7 @@ #include "IrPipeline.hpp" #include "LlvmContext.hpp" #include "Jit.hpp" +#include "activation_record.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" @@ -54,6 +55,7 @@ namespace xo { class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; + using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; //using ConstantInterface = xo::ast::ConstantInterface; @@ -64,6 +66,10 @@ namespace xo { // ----- module access ----- + llvm::Module * current_module() { return llvm_module_.get(); } + ref::brw llvm_cx() { return llvm_cx_; } + llvm::IRBuilder<> * llvm_current_ir_builder() { return llvm_toplevel_ir_builder_.get(); } + /** target triple = string describing target host for codegen **/ const std::string & target_triple() const; /** append function names defined in attached module to *p_v @@ -75,16 +81,22 @@ namespace xo { /** write state of execution session (all the associated dynamic libraries) **/ void dump_execution_session(); - // ----- jit code generation ----- + // ----- code generation ----- llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); - llvm::Value * codegen_apply(ref::brw expr); - llvm::Function * codegen_lambda(ref::brw expr); - llvm::Value * codegen_variable(ref::brw var); - llvm::Value * codegen_ifexpr(ref::brw ifexpr); + llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); + /* NOTE: codegen_lambda() needs to be reentrant too. + * for example can have a lambda in apply position. + */ + llvm::Function * codegen_lambda_decl(ref::brw expr); + llvm::Function * codegen_lambda_defn(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr); + llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_toplevel(ref::brw expr); // ----- jit online execution ----- @@ -112,6 +124,10 @@ namespace xo { /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); + /** helper function. find all lambda expressions in AST @p expr **/ + std::vector> find_lambdas(ref::brw expr) const; + + public: /** codegen helper for a user-defined function (codegen_lambda()): * create stack slot on behalf of some formal parameter to a function, * so we can avoid SSA restriction on function body @@ -122,6 +138,7 @@ namespace xo { const std::string & var_name, TypeDescr var_type); + private: /** (re)create pipeline to turn expressions into llvm IR code **/ void recreate_llvm_ir_pipeline(); @@ -135,6 +152,7 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- + public: /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * @@ -144,6 +162,7 @@ namespace xo { **/ xo::ref::rp ir_pipeline_; + private: /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -151,8 +170,10 @@ namespace xo { * each with its own LLVMContext **/ ref::rp llvm_cx_; + /** builder for intermediate-representation objects **/ - std::unique_ptr> llvm_ir_builder_; + std::unique_ptr> llvm_toplevel_ir_builder_; + /** a module (1:1 with library ?) being prepared by llvm. * IR-level -- does not contain machine code * @@ -162,6 +183,8 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; + + public: /** map variable names (formal parameters) to * corresponding llvm IR. * @@ -172,8 +195,7 @@ namespace xo { * * rhs identifies logical stack location of a variable **/ - std::map nested_env_; /* <-> kaleidoscope NamedValues */ - + std::stack env_stack_; /* <-> kaleidoscope NamedValues */ }; /*MachPipeline*/ inline std::ostream & diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp new file mode 100644 index 00000000..eb98975e --- /dev/null +++ b/include/xo/jit/activation_record.hpp @@ -0,0 +1,38 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#include +#include +#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/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index aa0a3deb..fe0b9e3d 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -6,6 +6,7 @@ set(SELF_SRCS IrPipeline.cpp MachPipeline.cpp intrinsics.cpp + activation_record.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 293f8625..f19d92b1 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -76,7 +76,7 @@ namespace xo { { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); - llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + 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()); @@ -84,7 +84,7 @@ namespace xo { if (!llvm_cx_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); } - if (!llvm_ir_builder_.get()) { + if (!llvm_toplevel_ir_builder_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); } if (!llvm_module_.get()) { @@ -142,11 +142,71 @@ namespace xo { } /*codegen_constant*/ namespace { + 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; + } + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - if (Reflect::is_native(td)) { + 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 (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { return llvm::Type::getInt8Ty(llvm_cx_ref); @@ -172,7 +232,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { - constexpr bool c_debug_flag = true; + //constexpr bool c_debug_flag = true; using xo::scope; /** note: documentation (such as it is) for llvm::Function here: @@ -193,49 +253,14 @@ namespace xo { /** establish prototype for this function **/ - // PLACEHOLDER - // just make prototype for function :: double^n -> double - TypeDescr fn_td = expr->valuetype(); - int n_fn_arg = fn_td->n_fn_arg(); - scope log(XO_DEBUG(c_debug_flag), - xtag("fn_td", fn_td->short_name()), - xtag("n_fn_arg", n_fn_arg)); + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); - 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); - - log && log(xtag("i_arg", i), - xtag("arg_td", arg_td->short_name())); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); - - TypeDescr retval_td = fn_td->fn_retval(); - - log && log(xtag("retval_td", retval_td->short_name())); - - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), retval_td); - - if (!llvm_retval) + if (!llvm_fn_type) return nullptr; - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, expr->name(), @@ -274,121 +299,158 @@ namespace xo { #endif } +#ifdef OBSOLETE log && log("returning llvm function"); +#endif return fn; } /*codegen_primitive*/ llvm::Value * - MachPipeline::codegen_apply(ref::brw apply) + 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; - /* editorial: - * - * to handle (computed functions) properly, - * we will need a runtime representation for a 'primitive function pointer' - * - * For now, finesse by only handling PrimitiveInterface in function-callee position + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. */ - if (apply->fn()->extype() == exprtype::primitive - || apply->fn()->extype() == exprtype::lambda) + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); { - llvm::Function * llvm_fn = nullptr; - llvmintrinsic intrinsic = llvmintrinsic::invalid; - FunctionInterface * fn = nullptr; - { - // TODO: codgen_function() - + /* 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) { - fn = pm.get(); - llvm_fn = this->codegen_primitive(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); - auto lm = Lambda::from(apply->fn()); - if (lm) { - fn = lm.get(); - llvm_fn = this->codegen_lambda(lm); - } + /* 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_fn) { - return nullptr; - } - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif - - if (llvm_fn->arg_size() != apply->argv().size()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - << xtag("f", fn->name()) - << xtag("n1", fn->n_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = fn->n_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != fn->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee f for arg i seeeing U instead of expected T" - << xtag("f", fn->name()) - << xtag("i", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", fn->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } - - std::vector args; - args.reserve(apply->argv().size()); - - for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr); - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: arg:" << endl; - arg->print(llvm::errs()); - cerr << endl; -#endif - - args.push_back(arg); - } - - switch(intrinsic) { - case llvmintrinsic::i_add: - return llvm_ir_builder_->CreateAdd(args[0], args[1]); - case llvmintrinsic::i_mul: - return llvm_ir_builder_->CreateMul(args[0], args[1]); - case llvmintrinsic::fp_add: - return llvm_ir_builder_->CreateFAdd(args[0], args[1]); - case llvmintrinsic::fp_mul: - return llvm_ir_builder_->CreateFMul(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; - } - - return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); - } else { - cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; + 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; + } + + switch(intrinsic) { + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(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::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 */ @@ -397,21 +459,61 @@ namespace xo { 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; - return tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); + 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(ref::brw lambda) + MachPipeline::codegen_lambda_decl(ref::brw lambda) { constexpr bool c_debug_flag = true; using xo::scope; @@ -419,27 +521,18 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - /* reminder! this is the *expression*, not the *closure* */ - global_env_[lambda->name()] = lambda.get(); /* do we already know a function with this name? */ auto * fn = llvm_module_->getFunction(lambda->name()); if (fn) { - /** function with this name already defined?? **/ - cerr << "MachPipeline::codegen_lambda: function f already defined" - << xtag("f", lambda->name()) - << endl; - - return nullptr; + return fn; } /* establish prototype for this function */ - // PLACEHOLDER - // just handle double arguments + return type for now - +#ifdef OBSOLETE llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); @@ -449,10 +542,11 @@ namespace xo { arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); } +#endif - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - arg_type_v, - false /*!varargs*/); + 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, @@ -463,7 +557,7 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm format param names", + log && log("llvm formal param names", xtag("i", i), xtag("param", lambda->argv().at(i))); @@ -472,114 +566,158 @@ namespace xo { } } + 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", fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - llvm_ir_builder_->SetInsertPoint(block); + ir_builder.SetInsertPoint(block); + + /** Actual parameters will need their own activation record. + * Track its shape here. + **/ + this->env_stack_.push(activation_record()); - /* formal parameters need to appear in named_value_map_ */ - nested_env_.clear(); { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + int i = 0; - for (auto & arg : fn->args()) { + 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(fn, - std::string(arg.getName()), + = create_entry_block_alloca(llvm_fn, + arg_name, lambda->fn_arg(i)); - if (!alloca) + if (!alloca) { + this->env_stack_.pop(); return nullptr; + } /* store on function entry * see codegen_variable() for corresponding load */ - this->llvm_ir_builder_->CreateStore(&arg, alloca); + ir_builder.CreateStore(&arg, alloca); /* remember stack location for reference + assignment * in lambda body. * */ - nested_env_[std::string(arg.getName())] = alloca; + env_stack_.top().alloc_var(arg_name, alloca); ++i; } } - llvm::Value * retval = this->codegen(lambda->body()); + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); if (retval) { /* completes the function.. */ - llvm_ir_builder_->CreateRet(retval); + ir_builder.CreateRet(retval); /* validate! always validate! */ - llvm::verifyFunction(*fn); + llvm::verifyFunction(*llvm_fn); if (log) { std::string buf; llvm::raw_string_ostream ss(buf); - fn->print(ss); + llvm_fn->print(ss); log(xtag("IR-before-opt", buf)); } /* optimize! improves IR */ - ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); if (log) { std::string buf; llvm::raw_string_ostream ss(buf); - fn->print(ss); + llvm_fn->print(ss); log(xtag("IR-after-opt", buf)); } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); - return fn; + llvm_fn = nullptr; } - /* oops, something went wrong */ - fn->eraseFromParent(); + this->env_stack_.pop(); - return nullptr; - } /*codegen_lambda*/ + 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) + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) { - auto ix = nested_env_.find(var->name()); - - if (ix == nested_env_.end()) { - cerr << "MachPipeline::codegen_variable: no binding for variable x" + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) << endl; + return nullptr; } - llvm::AllocaInst * alloca = ix->second; + llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; /* code to load value from stack */ - return this->llvm_ir_builder_->CreateLoad(alloca->getAllocatedType(), - alloca, - var->name().c_str()); + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr) + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test()); + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir - = llvm_ir_builder_->CreateFCmpONE(test_ir, - llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(0.0)), - "iftest"); + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); - llvm::Function * parent_fn = llvm_ir_builder_->GetInsertBlock()->getParent(); + 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 @@ -601,45 +739,46 @@ namespace xo { /* IR to direct control flow to one of {when_true_bb, when_false_bb}, * depending on result of test_with_cmp_ir */ - llvm_ir_builder_->CreateCondBr(test_with_cmp_ir, - when_true_bb, - when_false_bb); + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); /* populate when_true_bb */ - llvm_ir_builder_->SetInsertPoint(when_true_bb); + ir_builder.SetInsertPoint(when_true_bb); - llvm::Value * when_true_ir = this->codegen(expr->when_true()); + 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 */ - llvm_ir_builder_->CreateBr(merge_bb); + ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = llvm_ir_builder_->GetInsertBlock(); + when_true_bb = ir_builder.GetInsertBlock(); /* populate when_false_bb */ parent_fn->insert(parent_fn->end(), when_false_bb); - llvm_ir_builder_->SetInsertPoint(when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); - llvm::Value * when_false_ir = this->codegen(expr->when_false()); + 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 */ - llvm_ir_builder_->CreateBr(merge_bb); + ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = llvm_ir_builder_->GetInsertBlock(); + when_false_bb = ir_builder.GetInsertBlock(); /* merged suffix sequence */ parent_fn->insert(parent_fn->end(), merge_bb); - llvm_ir_builder_->SetInsertPoint(merge_bb); + ir_builder.SetInsertPoint(merge_bb); /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node - = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); + = 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); @@ -647,7 +786,7 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr) + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { case exprtype::constant: @@ -655,13 +794,13 @@ namespace xo { case exprtype::primitive: return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: - return this->codegen_apply(Apply::from(expr)); + return this->codegen_apply(Apply::from(expr), ir_builder); case exprtype::lambda: - return this->codegen_lambda(Lambda::from(expr)); + return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr)); + return this->codegen_variable(Variable::from(expr), ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr)); + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -675,6 +814,59 @@ namespace xo { 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() { diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp new file mode 100644 index 00000000..c7f40362 --- /dev/null +++ b/src/jit/activation_record.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 */