xo-jit: refactor: + closures [wip: not tested]
This commit is contained in:
parent
19d8a5e846
commit
659c0c400b
5 changed files with 376 additions and 280 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue