From 659c0c400b3fe5478d2689bb78078a46260b6005 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:06 -0400 Subject: [PATCH] xo-jit: refactor: + closures [wip: not tested] --- include/xo/jit/MachPipeline.hpp | 37 +++- include/xo/jit/type2llvm.hpp | 103 ++++++----- src/jit/MachPipeline.cpp | 297 ++++++++++++++++++-------------- src/jit/activation_record.cpp | 129 +++++++------- src/jit/type2llvm.cpp | 90 +++++----- 5 files changed, 376 insertions(+), 280 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ed81c6c8..4e0714e9 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -121,16 +121,45 @@ namespace xo { llvm::Function * codegen_primitive_wrapper(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); + /** Generate closure for invoking a primitive function. + * Primitives don't benefit from a closure, but we need a consistent ABI + * to support function-pointer-like behavior for a target function + * that may resolve to primitive-or-lambda at runtime + **/ + llvm::Value * codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_apply(ref::brw expr, + llvm::Value * envptr, + 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); + /** Generate closure for invoking a lambda (user-defined function). + * See @ref MachPipeline::codegen_apply for invocation + * Same ABI as @ref MachPipeline::codegen_primitive_closure + * + * @param envptr. Environment from surrounding lexical scope. + * This will be captured as envptr member by + * the IR code for creating a closure. + * @ref MachPipeline::codegen_toplevel and friends are responsible for + * assembling and propagating this. + **/ + llvm::Value * codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); llvm::Value * codegen_toplevel(ref::brw expr); diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 75f33576..d7a04f05 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -17,6 +17,7 @@ namespace xo { **/ struct type2llvm { public: + using FunctionInterface = xo::ast::FunctionInterface; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; @@ -57,11 +58,55 @@ namespace xo { * unwind_fn [1] | o-------> env * (*)(env*, ctl) * +-------+ * - * @return struct type. typename will be @c c.foo for lambda with name @c foo + * @return struct type. typename will be @c c.foo for a function + * (primitive or lambda) with name @c foo **/ static llvm::StructType * - create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda); + create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn); + + /** establish llvm abstract representation for a closure: + * struct with + * - [0] function pointer + * - [1] runtime localenv pointer + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * 1. for primitives, localenv will be null pointer + * 2. for lambdas L with L->requires_closure_flag() = false, + * localenv will also be null pointer + * 3. for lambdas with L->requires_closure_flag() = true, + * + * localenv will (for lambdas requiring closures) + * in practice be struct: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * Implementation here will just use generic pointer for runtime + * localenv. + **/ + static llvm::StructType * + function_td_to_closureapi_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + const std::string & hint_name); /** establish llvm concrete representation for a particular lambda's * runtime local environment: @@ -128,9 +173,17 @@ namespace xo { private: /** establish llvm representation for a function-pointer type * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime **/ static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ @@ -164,48 +217,6 @@ namespace xo { static llvm::StructType * env_api_llvm_type(xo::ref::brw llvm_cx); - /** establish llvm abstract representation for a closure: - * struct with - * - [0] function pointer - * - [1] runtime localenv pointer - * - * +-------+ - * | o---------> native function - * +-------+ - * | o---------> runtime localenv - * +-------+ (possibly nullptr) - * - * 1. for primitives, localenv will be null pointer - * 2. for lambdas L with L->requires_closure_flag() = false, - * localenv will also be null pointer - * 3. for lambdas with L->requires_closure_flag() = true, - * - * localenv will (for lambdas requiring closures) - * in practice be struct: - * - * ^ - * | parent - * +-------+ | - * parent_env [0] | o-------/ - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * arg[i] [2+i] . ... . - * . ... . - * +-------+ - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * Implementation here will just use generic pointer for runtime - * localenv. - **/ - static llvm::StructType * - function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); - }; /*type2llvm*/ } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index fc44ae88..b458f9fb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -361,8 +361,29 @@ namespace xo { return wrap_lvfn; } /*codegen_primitive_wrapper*/ + llvm::Value * + MachPipeline::codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); + + llvm::Function * pm_wrapper = codegen_primitive_wrapper(expr, ir_builder); + llvm::Value * env_0ptr = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx_)); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + pm_wrapper, {0}, "wrapfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + env_0ptr, {1}, "nullenvptr" /*name*/); + + return lv_closure; + } /*codegen_primitive_closure*/ + llvm::Value * MachPipeline::codegen_apply(ref::brw apply, + llvm::Value * envptr, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; @@ -376,14 +397,14 @@ namespace xo { 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. + /* IR for closure in function position + * see: + * - MachPipeline::codegen_primitive_closure + * - MachPipeline::codegen_lambda_closure + * - type2llvm::create_closure_lvtype */ - llvm::Value * llvm_fnval = nullptr; + llvm::Value * llvm_closure = 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 @@ -392,12 +413,12 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_fnval = this->codegen_primitive(pm); + llvm_closure = 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); + llvm_closure = this->codegen(apply->fn(), envptr, ir_builder); /* we don't need any special checking here. * already know (from xo-level checking) that pointer has the right type. @@ -414,10 +435,13 @@ namespace xo { } } - if (!llvm_fnval) { + if (!llvm_closure) { return nullptr; } + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + #ifdef NOT_USING_DEBUG cerr << "MachPipeline::codegen_apply: fn:" << endl; fn->print(llvm::errs()); @@ -452,12 +476,50 @@ namespace xo { } #endif + llvm::StructType * closure_lvtype + = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, + ast_fn_td, + "" /*name - not required*/); + + llvm::Value * lv_fnptr = nullptr; + { + llvm::Value * fnptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + + std::array index_v = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + + lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + + llvm::Value * lv_fnenvptr = nullptr; + { + llvm::Value * envptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 1 /*value*/)); + + std::array index_v = {{envptr_slot /*envptr slot = closure[1]*/}}; + + lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + std::vector args; - args.reserve(apply->argv().size()); + /* +1 for envptr */ + args.reserve(1 + apply->argv().size()); + + + /* we must take envptr from closure, + * and we need to do this using some version of getelementptr + */ + args.push_back(lv_fnenvptr); int i = 0; for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, ir_builder); + auto * arg = this->codegen(arg_expr, envptr, ir_builder); if (log) { /* TODO: print helper for llvm::Value* */ @@ -476,26 +538,28 @@ namespace xo { /* if we have an intrinsic hint, * then instead of invoking a function, * we use some native machine instruction instead. + * + * args[0] not used here, that holds envptr from faux closure */ switch(intrinsic) { case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[0]); + return ir_builder.CreateNeg(args[1]); case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[0], args[1]); + return ir_builder.CreateAdd(args[1], args[2]); case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[0], args[1]); + return ir_builder.CreateSub(args[1], args[2]); case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[0], args[1]); + return ir_builder.CreateMul(args[1], args[2]); case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[0], args[1]); + return ir_builder.CreateSDiv(args[1], args[2]); case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[0], args[1]); + return ir_builder.CreateUDiv(args[1], args[2]); case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[0], args[1]); + return ir_builder.CreateFAdd(args[1], args[2]); case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[0], args[1]); + return ir_builder.CreateFMul(args[1], args[2]); case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[0], args[1]); + return ir_builder.CreateFDiv(args[1], args[2]); case llvmintrinsic::invalid: case llvmintrinsic::fp_sqrt: case llvmintrinsic::fp_pow: @@ -506,65 +570,18 @@ namespace xo { 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 - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, - llvm_fnval, + lv_fnptr, args, "calltmp"); } /*codegen_apply*/ -#ifdef OBSOLETE - /* 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; - - 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 = type2llvm::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*/ -#endif - - std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -600,24 +617,40 @@ namespace xo { /* establish prototype for this function */ + /* wrapper_flag: llvm function type takes extra first argument, + * supplying environment pointer from surrounding closure. + * + * Note that this argument is not present in lambda, + * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] + */ llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + lambda->valuetype(), + true /*wrapper_flag*/); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); - /* also capture argument names */ + + /* also adopt lambda's formal 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))); + if (i == 0) { + log && log("llvm inserted env param", + xtag("i", i)); + + arg.setName(".env"); + } else { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i-1))); + + arg.setName(lambda->argv().at(i-1)->name()); + } - arg.setName(lambda->argv().at(i)->name()); ++i; } } @@ -648,6 +681,10 @@ namespace xo { return nullptr; } + /* environment for this lambda's clsoure + * passed as extra 1st argument + */ + llvm::Value * envptr = llvm_fn->args().begin(); /* generate function body */ @@ -667,45 +704,9 @@ namespace xo { return nullptr; } -#ifdef OBSOLETE - { - 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(i, arg_name, alloca); - ++i; - } - } -#endif - - llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + llvm::Value * retval = this->codegen(lambda->body(), + envptr, + ir_builder); if (retval) { /* completes the function.. */ @@ -746,10 +747,33 @@ namespace xo { return llvm_fn; } /*codegen_lambda_defn*/ + llvm::Value * + MachPipeline::codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); + + llvm::Function * lvfn = codegen_lambda_decl(lambda); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + lvfn, {0}, "lmfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + envptr, {1}, "envptr" /*name*/); + + return lv_closure; + } /*codegen_lambda_closure*/ + llvm::Value * MachPipeline::codegen_variable(ref::brw var, + llvm::Value * /*envptr*/, llvm::IRBuilder<> & ir_builder) { + /* TODO: navigate envptr to handle non-local variables */ + if (env_stack_.empty()) { cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) @@ -772,9 +796,11 @@ namespace xo { } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen_ifexpr(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + llvm::Value * test_ir = this->codegen(expr->test(), envptr, ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir @@ -813,6 +839,7 @@ namespace xo { ir_builder.SetInsertPoint(when_true_bb); llvm::Value * when_true_ir = this->codegen(expr->when_true(), + envptr, ir_builder); if (!when_true_ir) @@ -827,7 +854,9 @@ namespace xo { 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); + llvm::Value * when_false_ir = this->codegen(expr->when_false(), + envptr, + ir_builder); if (!when_false_ir) return nullptr; @@ -852,21 +881,24 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen(ref::brw expr, + llvm::Value * envptr, + 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)); + return this->codegen_primitive_closure(PrimitiveInterface::from(expr), ir_builder); case exprtype::apply: - return this->codegen_apply(Apply::from(expr), ir_builder); + return this->codegen_apply(Apply::from(expr), envptr, ir_builder); case exprtype::lambda: - return this->codegen_lambda_decl(Lambda::from(expr)); + return this->codegen_lambda_closure(Lambda::from(expr), envptr, ir_builder); + //return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr), ir_builder); + return this->codegen_variable(Variable::from(expr), envptr, ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + return this->codegen_ifexpr(IfExpr::from(expr), envptr, ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -910,6 +942,8 @@ namespace xo { this->codegen_lambda_decl(lambda); } +#ifdef OBSOLETE /* don't do this anymore, obscures lexical context */ + /* Pass 2 */ for (auto lambda : fn_v) { this->codegen_lambda_defn(lambda, @@ -931,6 +965,19 @@ namespace xo { return this->codegen(expr, *(this->llvm_toplevel_ir_builder_.get())); } +#endif + + /* 1. using nullptr as runtime representation for global environment + * 2. may have to elaborate this later? not clear to me + */ + + llvm::Value * env_0ptr + = (llvm::ConstantPointerNull::get + (type2llvm::env_api_llvm_ptr_type(llvm_cx_))); + + return this->codegen(expr, + env_0ptr, + *(this->llvm_toplevel_ir_builder_.get())); } /*codegen_toplevel*/ void diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 413412af..be1275ef 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -155,7 +155,7 @@ namespace xo { = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), llvm::APInt(32 /*bits*/, i_slot /*value*/)); - std::array index_v = { + std::array index_v = { {i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr @@ -194,37 +194,44 @@ namespace xo { { int i_arg = 0; for (auto & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("stackonly(i)", binding_v_[i_arg].is_stackonly())); - - if (binding_v_[i_arg].is_stackonly()) { - /* stack location for arg[i] */ - runtime_binding_detail binding - = create_entry_block_alloca(llvm_cx, - //data_layout, - llvm_fn, - tmp_ir_builder, - i_arg, - arg_name, - lambda_->fn_arg(i_arg)); - - if (!binding.llvm_addr_) - return false; - - /* store on function entry - * see codegen_variable() for corresponding load + if (i_arg == 0) { + /* 1st argument is injected environment pointer. + * we don't need that to be on the stack, + * since not modifiable and not user-referencable. */ - ir_builder.CreateStore(&arg, binding.llvm_addr_); + } else { + std::string arg_name = std::string(arg.getName()); - /* remember stack location for reference + assignment - * in lambda body. - * - */ - this->alloc_var(arg_name, binding); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("stackonly(i)", binding_v_[i_arg-1].is_stackonly())); + + if (binding_v_[i_arg-1].is_stackonly()) { + /* stack location for arg[i] */ + runtime_binding_detail binding + = create_entry_block_alloca(llvm_cx, + //data_layout, + llvm_fn, + tmp_ir_builder, + i_arg, + arg_name, + lambda_->fn_arg(i_arg)); + + if (!binding.llvm_addr_) + return false; + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, binding.llvm_addr_); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + this->alloc_var(arg_name, binding); + } } ++i_arg; @@ -297,7 +304,7 @@ namespace xo { int i_localenv_slot = 0; /* store localenv->parent_env */ - { + { llvm::Value * slot_addr = runtime_localenv_slot_addr(llvm_cx, localenv_llvm_type, @@ -348,42 +355,48 @@ namespace xo { int i_arg = 0; for (llvm::Argument & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("captured(i)", binding_v_[i_arg].is_captured())); - - if (binding_v_[i_arg].is_captured()) { - // do something with runtime-local-env for this llvm_fn - - /* remember stack location for reference + assignment - * in lambda body. - * + if (i_arg == 0) { + /* to remove all doubt, ignore first arg here. + * it's non-captureable environment pointer */ + } else { + std::string arg_name = std::string(arg.getName()); - TypeDescr arg_td = lambda_->fn_arg(i_arg); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("captured(i)", binding_v_[i_arg-1].is_captured())); - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + if (binding_v_[i_arg-1].is_captured()) { + // do something with runtime-local-env for this llvm_fn - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - tmp_ir_builder); + /* remember stack location for reference + assignment + * in lambda body. + * + */ - if (!slot_addr) - return false; + TypeDescr arg_td = lambda_->fn_arg(i_arg-1); - ++i_localenv_slot; + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); - tmp_ir_builder.CreateStore(&arg, slot_addr); + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + tmp_ir_builder); - runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + if (!slot_addr) + return false; - this->alloc_var(arg_name, binding); + ++i_localenv_slot; + + tmp_ir_builder.CreateStore(&arg, slot_addr); + + runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + + this->alloc_var(arg_name, binding); + } } ++i_arg; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 231d6de4..6a982b89 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -26,7 +26,8 @@ namespace xo { /* 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); + //return function_td_to_llvm_fnptr_type(llvm_cx, td); + return function_td_to_closureapi_lvtype(llvm_cx, td, ""); } else if (td->is_struct()) { return struct_td_to_llvm_type(llvm_cx, td); } else if (td->is_pointer()) { @@ -95,9 +96,10 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, wrapper_flag); /** like C: llvm IR doesn't support function-valued variables; * it does however support pointer-to-function-valued variables @@ -227,7 +229,7 @@ namespace xo { /* _env_api: base type for a local environment */ llvm::StructType * env_llvm_type = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_env_api"); + "_env_api"); /* _env_api[0]: pointer to a local environment */ llvm::PointerType * envptr_llvm_type @@ -253,25 +255,48 @@ namespace xo { return llvm::PointerType::getUnqual(env_llvm_type); } /*env_api_llvm_ptr_type*/ -#ifdef NOT_USING llvm::StructType * - type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + type2llvm::create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn) { - constexpr bool c_debug_flag = true; - using xo::scope; + constexpr const char * c_prefix = "c."; - scope log(XO_DEBUG(c_debug_flag)); + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + fn->name(); - /* closure type doesn't need a name. - * (We might find it convenient to give one anyway) + return function_td_to_closureapi_lvtype(llvm_cx, + fn->valuetype(), + closure_name); + } /*create_closureapi_lvtype*/ + + llvm::StructType * + type2llvm::function_td_to_closureapi_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + const std::string & hint_name) + { + /* would be precisely correct to use create_localenv_llvm_type() + * here. However judged not sufficiently helpful. + * Would still + * need environment cast whenever closure in apply position is + * not known at compile time. */ - llvm::StructType * closure_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, + fn_td, + true /*wrapper_flag*/); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - llvm::PointerType * parent_llvm_type - } /*function_td_to_llvm_fnptr_type*/ -#endif + std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + + llvm::StructType * closure_lvtype + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); + + //closure_lvtype->setBody(member_lvtype_v); + + if (!hint_name.empty()) + closure_lvtype->setName(llvm::StringRef(hint_name)); + + return closure_lvtype; + } /*function_td_to_closureapi_lvtype*/ llvm::StructType * type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, @@ -289,7 +314,7 @@ namespace xo { for (const auto & var : lambda->argv()) { if (lambda->is_captured(var->name())) { - /* var needs a slot in localenv_llvm_type for lambda */ + /* var is captured -> needs a slot in the localenv_llvm_type belonging to this lambda */ member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, var->valuetype())); @@ -308,35 +333,6 @@ namespace xo { return localenv_lvtype; } /*create_localenv_llvm_type*/ - llvm::StructType * - type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda) - { - constexpr const char * c_prefix = "c."; - - /* would be precisely correct to use create_localenv_llvm_type() - * here. However judged not sufficiently helpful. - * Would still - * need environment cast whenever closure in apply position is - * not known at compile time. - */ - llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, - lambda->valuetype()); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; - - /* e.g. "c.foo" */ - std::string closure_name = std::string(c_prefix) + lambda->name(); - - llvm::StructType * closure_lvtype - = llvm::StructType::create(llvm_cx->llvm_cx_ref(), - member_lvtype_v, - llvm::StringRef(closure_name), - false /*!is_packed*/); - - return closure_lvtype; - } /*create_closure_lvtype*/ } /*namespace jit*/ } /*namespace xo*/