xo-jit: refactor: + closures [wip: not tested]

This commit is contained in:
Roland Conybeare 2024-07-08 18:31:06 -04:00
commit 659c0c400b
5 changed files with 376 additions and 280 deletions

View file

@ -121,16 +121,45 @@ namespace xo {
llvm::Function * codegen_primitive_wrapper(ref::brw<xo::ast::PrimitiveInterface> expr,
llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_apply(ref::brw<xo::ast::Apply> 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<xo::ast::PrimitiveInterface> expr,
llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_apply(ref::brw<xo::ast::Apply> 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<xo::ast::Lambda> expr);
llvm::Function * codegen_lambda_defn(ref::brw<xo::ast::Lambda> expr, llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_variable(ref::brw<xo::ast::Variable> var, llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_ifexpr(ref::brw<xo::ast::IfExpr> 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<xo::ast::Lambda> lambda,
llvm::Value * envptr,
llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_variable(ref::brw<xo::ast::Variable> var,
llvm::Value * envptr,
llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_ifexpr(ref::brw<xo::ast::IfExpr> ifexpr,
llvm::Value * envptr,
llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen(ref::brw<Expression> expr, llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen(ref::brw<Expression> expr,
llvm::Value * envptr,
llvm::IRBuilder<> & ir_builder);
llvm::Value * codegen_toplevel(ref::brw<Expression> expr);

View file

@ -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<LlvmContext> llvm_cx,
xo::ref::brw<Lambda> lambda);
create_closureapi_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
xo::ref::brw<FunctionInterface> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> llvm_cx,
TypeDescr fn_td);
}; /*type2llvm*/
} /*namespace jit*/
} /*namespace xo*/

View file

@ -361,8 +361,29 @@ namespace xo {
return wrap_lvfn;
} /*codegen_primitive_wrapper*/
llvm::Value *
MachPipeline::codegen_primitive_closure(ref::brw<xo::ast::PrimitiveInterface> 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> 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<llvm::Value*, 1> 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<llvm::Value*, 1> index_v = {{envptr_slot /*envptr slot = closure[1]*/}};
lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype,
llvm_closure,
index_v);
}
std::vector<llvm::Value *> 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<ref::brw<Lambda>>
MachPipeline::find_lambdas(ref::brw<Expression> 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> 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<Variable> 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<IfExpr> expr, llvm::IRBuilder<> & ir_builder)
MachPipeline::codegen_ifexpr(ref::brw<IfExpr> 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<Expression> expr, llvm::IRBuilder<> & ir_builder)
MachPipeline::codegen(ref::brw<Expression> 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

View file

@ -155,7 +155,7 @@ namespace xo {
= llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(),
llvm::APInt(32 /*bits*/,
i_slot /*value*/));
std::array<llvm::Value*,2> index_v = {
std::array<llvm::Value*, 1> 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;

View file

@ -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<LlvmContext> 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<LlvmContext> llvm_cx,
TypeDescr fn_td)
type2llvm::create_closureapi_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
xo::ref::brw<FunctionInterface> 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<LlvmContext> 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<llvm::Type *> 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<LlvmContext> 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<LlvmContext> llvm_cx,
xo::ref::brw<Lambda> 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<llvm::Type *> 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*/