xo-reader2/xo-jit/src/jit/MachPipeline.cpp

1147 lines
42 KiB
C++

/* @file MachPipeline.cpp */
#include "MachPipeline.hpp"
#include "activation_record.hpp"
#include "type2llvm.hpp"
#include "xo/expression/pretty_variable.hpp"
#include <string>
namespace xo {
using xo::scm::exprtype;
using xo::scm::Expression;
using xo::scm::ConstantInterface;
//using xo::scm::FunctionInterface;
using xo::scm::PrimitiveExprInterface;
using xo::scm::Lambda;
using xo::scm::Variable;
using xo::scm::Apply;
using xo::scm::IfExpr;
using xo::scm::GlobalSymtab;
using xo::scm::llvmintrinsic;
using xo::reflect::Reflect;
using xo::reflect::StructMember;
using xo::reflect::TypeDescr;
using xo::scope;
using llvm::orc::ExecutionSession;
using llvm::DataLayout;
using std::cerr;
using std::endl;
namespace jit {
void
MachPipeline::init_once() {
static bool s_init_once = false;
if (!s_init_once) {
s_init_once = true;
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
}
} /*init_once*/
/* tracking KaleidoscopeJIT::Create() here..
*
* Verified:
* + 'execution session' as per Kaleidoscope JIT,
* can instantiate from python
* + 'jit object layer'
* (realtime dynamic library object linking layer)
* + 'jit_compile_layer'
* + 'jit_our_dynamic_lib'
*/
llvm::Expected<std::unique_ptr<MachPipeline>>
MachPipeline::make_aux()
{
MachPipeline::init_once();
static llvm::ExitOnError llvm_exit_on_err;
std::unique_ptr<Jit> jit = llvm_exit_on_err(Jit::Create());
return std::unique_ptr<MachPipeline>(new MachPipeline(std::move(jit)
));
} /*make*/
rp<MachPipeline>
MachPipeline::make() {
static llvm::ExitOnError llvm_exit_on_err;
std::unique_ptr<MachPipeline> jit = llvm_exit_on_err(make_aux());
return jit.release();
} /*make*/
MachPipeline::MachPipeline(std::unique_ptr<Jit> jit)
: jit_{std::move(jit)},
global_env_{GlobalEnv::make_empty()}
{
this->recreate_llvm_ir_pipeline();
}
void
MachPipeline::recreate_llvm_ir_pipeline()
{
//llvm_cx_ = std::make_unique<llvm::LLVMContext>();
llvm_cx_ = LlvmContext::make();
llvm_toplevel_ir_builder_ = std::make_unique<llvm::IRBuilder<>>(llvm_cx_->llvm_cx_ref());
llvm_module_ = std::make_unique<llvm::Module>("xojit", llvm_cx_->llvm_cx_ref());
llvm_module_->setDataLayout(this->jit_->data_layout());
if (!llvm_cx_.get()) {
throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context");
}
if (!llvm_toplevel_ir_builder_.get()) {
throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder");
}
if (!llvm_module_.get()) {
throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module");
}
ir_pipeline_ = new IrPipeline(llvm_cx_);
} /*recreate_llvm_ir_pipeline*/
const DataLayout &
MachPipeline::data_layout() const {
return this->jit_->data_layout();
}
const ExecutionSession *
MachPipeline::xsession() const {
return this->jit_->xsession();
}
/** identifies target host/architecture for machine code.
* e.g. "x86_64-unknown-linux-gnu"
**/
const std::string &
MachPipeline::target_triple() const {
// although this getter is defined, seems to be empty in practice
//return llvm_module_->getTargetTriple();
return this->jit_->target_triple();
}
std::vector<std::string>
MachPipeline::get_function_name_v() {
std::vector<std::string> retval;
for (const auto & fn_name : *llvm_module_)
retval.push_back(fn_name.getName().str());
return retval;
} /*get_function_names*/
void
MachPipeline::dump_execution_session() {
this->jit_->dump_execution_session();
}
llvm::Value *
MachPipeline::codegen_constant(bp<ConstantInterface> expr)
{
TypeDescr td = expr->value_td();
if (Reflect::is_native<double>(td)) {
return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(),
llvm::APFloat(*(expr->value_tp().recover_native<double>())));
} else if (Reflect::is_native<float>(td)) {
return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(),
llvm::APFloat(*(expr->value_tp().recover_native<float>())));
} else if (Reflect::is_native<int>(td)) {
return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(),
llvm::APSInt(*(expr->value_tp().recover_native<int>())));
} else if (Reflect::is_native<unsigned int>(td)) {
return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(),
llvm::APSInt(*(expr->value_tp().recover_native<unsigned int>())));
}
return nullptr;
} /*codegen_constant*/
llvm::Type *
MachPipeline::codegen_type(TypeDescr td) {
return type2llvm::td_to_llvm_type(llvm_cx_.borrow(), td);
}
llvm::Function *
MachPipeline::codegen_primitive(bp<PrimitiveExprInterface> expr)
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag));
/** note: documentation (such as it is) for llvm::Function here:
*
* https://llvm.org/doxygenL/classllvm_1_1Function.html
**/
auto * fn = llvm_module_->getFunction(expr->name());
if (fn) {
/** function with this name already known to llvm module;
* use that definition
*
* TODO: verify that signatures match!
**/
return fn;
}
/** establish prototype for this function **/
TypeDescr fn_td = expr->valuetype();
llvm::FunctionType * llvm_fn_type
= type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td);
if (!llvm_fn_type)
return nullptr;
fn = llvm::Function::Create(llvm_fn_type,
llvm::Function::ExternalLinkage,
expr->name(),
llvm_module_.get());
#ifdef NOT_USING
// set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here
{
int i_arg = 0;
for (auto & arg : fn->args()) {
std::stringstream ss;
ss << "x_" << i_arg;
arg.setName(ss.str());
++i_arg;
}
}
#endif
if (expr->explicit_symbol_def()) {
static llvm::ExitOnError llvm_exit_on_err;
auto name = expr->name();
auto fn_addr = expr->function_address();
log && log(xtag("sym", name),
xtag("mangled_sym", this->jit_->mangle(name)));
llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr));
#ifdef NOT_USING
if (!llvm_result) {
cerr << "MachPipeline::codegen_primitive"
<< ": intern_symbol failed"
<< xtag("name", expr->name())
<< xtag("addr", expr->function_address())
<< endl;
return nullptr;
}
#endif
} else {
log && log("not requiring absolute address", xtag("sym", expr->name()));
}
#ifdef OBSOLETE
log && log("returning llvm function");
#endif
return fn;
} /*codegen_primitive*/
llvm::Function *
MachPipeline::codegen_primitive_wrapper(bp<PrimitiveExprInterface> expr,
llvm::IRBuilder<> & /*ir_builder*/)
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag),
xtag("primitive-name", expr->name()));
constexpr const char * c_prefix = "w.";
/* unique name for wrapper. Note we don't allow period in schematica identifiers
* (though we could if we replace . with .. when lowering)
*/
std::string wrap_name = std::string(c_prefix) + expr->name();
/* original primitive */
auto * native_lvfn = this->codegen_primitive(expr);
/* wrapped primitive */
auto * wrap_lvfn = llvm_module_->getFunction(wrap_name);
if (wrap_lvfn) {
/* wrapper already defined */
return wrap_lvfn;
}
TypeDescr fn_td = expr->valuetype();
llvm::FunctionType * native_lvtype
= type2llvm::function_td_to_lvtype(llvm_cx_.borrow(),
fn_td,
false /*!wrapper_flag*/);
if (!native_lvtype)
return nullptr;
llvm::FunctionType * wrapper_lvtype
= type2llvm::function_td_to_lvtype(llvm_cx_.borrow(),
fn_td,
true /*wrapper_flag (for closure)*/);
wrap_lvfn = llvm::Function::Create(wrapper_lvtype,
llvm::Function::ExternalLinkage,
wrap_name,
llvm_module_.get());
/* at least we know the name of the 1st argument :) */
auto ix = wrap_lvfn->args().begin();
ix->setName(".env");
auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(),
"entry", wrap_lvfn);
/* don't call SetInsertPoint() on incoming ir_builder argument.
* Want to avoid disturbing top-to-bottom flow
*/
llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref());
tmp_ir_builder.SetInsertPoint(block);
std::vector<llvm::Value *> args;
/* call to native_lvfn,
* forwarding all args of wrap_lvfn, except the first
*/
{
args.reserve(expr->n_arg());
int i_wrap_arg = 0;
for (auto & arg : wrap_lvfn->args()) {
if (i_wrap_arg > 0)
args.push_back(&arg);
++i_wrap_arg;
}
}
/* {caller,callee} must agree on calling convention,
* so for primitives we need to assume c.
*/
llvm::CallInst * call = tmp_ir_builder.CreateCall(native_lvtype,
native_lvfn,
args,
"w.calltmp");
if (call) {
call->setTailCall(true);
/* does this work if call returns void? Is this needed with tail call? */
tmp_ir_builder.CreateRet(call);
llvm::verifyFunction(*wrap_lvfn);
if (log) {
std::string buf;
llvm::raw_string_ostream ss(buf);
wrap_lvfn->print(ss);
log(xtag("IR-before-opt", buf));
}
/* optimize! */
ir_pipeline_->run_pipeline(*wrap_lvfn);
if (log) {
std::string buf;
llvm::raw_string_ostream ss(buf);
wrap_lvfn->print(ss);
log(xtag("IR-after-opt", buf));
}
} else {
wrap_lvfn->eraseFromParent();
wrap_lvfn = nullptr;
}
return wrap_lvfn;
} /*codegen_primitive_wrapper*/
llvm::Value *
MachPipeline::codegen_primitive_closure(bp<xo::scm::PrimitiveExprInterface> expr,
llvm::IRBuilder<> & ir_builder)
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag));
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(bp<Apply> apply,
llvm::Value * envptr,
llvm::IRBuilder<> & ir_builder)
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag),
xtag("apply", apply));
// see here:
// https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api
using std::cerr;
using std::endl;
/* IR for closure in function position
* see:
* - MachPipeline::codegen_primitive_closure
* - MachPipeline::codegen_lambda_closure
* - type2llvm::create_closure_lvtype
*
* although this refers to a closure, llvm doesn't know that
*/
llvm::Value * llvm_closure = nullptr;
llvmintrinsic intrinsic = llvmintrinsic::invalid;
{
/* special treatement for primitive in apply position:
* allows substituting LLVM intrinsic
*/
if (apply->fn()->extype() == exprtype::primitive) {
auto pm = PrimitiveExprInterface::from(apply->fn());
if (pm) {
llvm_closure = this->codegen_primitive_closure(pm, ir_builder);
/* hint, when available. use faster alternative to IRBuilder::CreateCall below */
intrinsic = pm->intrinsic();
}
} else {
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.
*
* Specifically, xo::scm::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_closure) {
return nullptr;
}
/* function type in apply node's function position */
TypeDescr ast_fn_td = apply->fn()->valuetype();
if (log) {
log("MachPipeline::codegen_apply: fn in apply pos...");
llvm_closure->print(llvm::errs());
log("...done");
log("llvm type...");
llvm_closure->getType()->dump();
log("...done");
}
/* checks here will be redundant */
#ifdef OBSOLETE
llvm::StructType * closure_lvtype
= type2llvm::function_td_to_closureapi_lvtype(llvm_cx_,
ast_fn_td,
"" /*name - not required*/);
#endif
llvm::Value * lv_fnptr = nullptr;
{
#ifdef MAYBE_VERBOSE
llvm::Value * i0_slot
= llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(),
llvm::APInt(32 /*bits*/, 0 /*value*/));
llvm::Value * fnptr_slot
= llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(),
llvm::APInt(32 /*bits*/, 0 /*value*/));
std::array<llvm::Value*, 2> index_v
= {{i0_slot,
fnptr_slot /*fnptr slot = closure[0]*/}};
llvm::Value * lv_fnptr_addr
= ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype,
llvm_closure,
index_v);
llvm::Type * fnptr_lvtype
= type2llvm::function_td_to_llvm_fnptr_type(llvm_cx_,
apply->fn()->valuetype(),
true /*wrapper_flag*/);
/* the thing we're going to call */
lv_fnptr = ir_builder.CreateLoad(fnptr_lvtype, lv_fnptr_addr);
#endif
std::array<unsigned int, 1> index_v = {{ 0 }};
//ir_builder.CreateExtractValue(Value *Agg, ArrayRef<unsigned int> Idxs)
lv_fnptr = ir_builder.CreateExtractValue(llvm_closure,
index_v,
"fnptr");
}
llvm::Value * lv_fnenvptr = nullptr;
{
#ifdef MAYBE_VERBOSE
llvm::Value * i0_slot
= llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(),
llvm::APInt(32 /*bits*/, 0 /*value*/));
llvm::Value * envptr_slot
= llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(),
llvm::APInt(32 /*bits*/, 1 /*value*/));
std::array<llvm::Value*, 2> index_v
= {{i0_slot,
envptr_slot /*envptr slot = closure[1]*/}};
llvm::Value * lv_fnenvptr_addr
= ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype,
llvm_closure,
index_v);
llvm::Type * fnenvptr_lvtype
= type2llvm::env_api_llvm_ptr_type(llvm_cx_);
lv_fnenvptr = ir_builder.CreateLoad(fnenvptr_lvtype, lv_fnenvptr_addr);
#endif
std::array<unsigned int, 1> index_v = {{ 1 }};
lv_fnenvptr = ir_builder.CreateExtractValue(llvm_closure,
index_v,
"envptr");
}
std::vector<llvm::Value *> args;
/* +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, envptr, ir_builder);
if (log) {
/* TODO: print helper for llvm::Value* */
std::string llvm_value_str;
if (arg) {
llvm::raw_string_ostream ss(llvm_value_str);
arg->print(ss);
} else {
llvm_value_str = "<null llvm::Value>";
}
log(xtag("i_arg", i),
xtag("arg", llvm_value_str));
}
args.push_back(arg);
++i;
if (!arg) {
cerr << "MachPipeline::codegen_apply: failed for i'th argument"
<< xtag("i", i)
<< endl;
return nullptr;
}
}
/* 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[1]);
case llvmintrinsic::i_add:
return ir_builder.CreateAdd(args[1], args[2]);
case llvmintrinsic::i_sub:
return ir_builder.CreateSub(args[1], args[2]);
case llvmintrinsic::i_mul:
return ir_builder.CreateMul(args[1], args[2]);
case llvmintrinsic::i_sdiv:
return ir_builder.CreateSDiv(args[1], args[2]);
case llvmintrinsic::i_udiv:
return ir_builder.CreateUDiv(args[1], args[2]);
case llvmintrinsic::i_eq:
return ir_builder.CreateICmpEQ(args[1], args[2]);
case llvmintrinsic::i_ne:
return ir_builder.CreateICmpNE(args[1], args[2]);
case llvmintrinsic::i_sgt:
return ir_builder.CreateICmpSGT(args[1], args[2]);
case llvmintrinsic::i_sge:
return ir_builder.CreateICmpSGE(args[1], args[2]);
case llvmintrinsic::i_slt:
return ir_builder.CreateICmpSLT(args[1], args[2]);
case llvmintrinsic::i_sle:
return ir_builder.CreateICmpSLE(args[1], args[2]);
case llvmintrinsic::fp_add:
return ir_builder.CreateFAdd(args[1], args[2]);
case llvmintrinsic::fp_sub:
return ir_builder.CreateFSub(args[1], args[2]);
case llvmintrinsic::fp_mul:
return ir_builder.CreateFMul(args[1], args[2]);
case llvmintrinsic::fp_div:
return ir_builder.CreateFDiv(args[1], args[2]);
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;
}
llvm::FunctionType * llvm_fn_type
= type2llvm::function_td_to_lvtype(this->llvm_cx_,
ast_fn_td,
true /*wrapper_flag*/);
return ir_builder.CreateCall(llvm_fn_type,
lv_fnptr,
args,
"calltmp");
} /*codegen_apply*/
std::vector<bp<Lambda>>
MachPipeline::find_lambdas(bp<Expression> expr) const
{
std::vector<bp<Lambda>> retval_v;
expr->visit_preorder(
[&retval_v](bp<Expression> x)
{
if (x->extype() == exprtype::lambda) {
retval_v.push_back(Lambda::from(x));
}
});
return retval_v;
} /*find_lambdas*/
llvm::Function *
MachPipeline::codegen_lambda_decl(bp<Lambda> lambda)
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag),
xtag("lambda-name", lambda->name()));
this->global_env_->require_global(lambda->name(), lambda);
/* do we already know a function with this name? */
auto * fn = llvm_module_->getFunction(lambda->name());
if (fn) {
return fn;
}
/* establish prototype for this function */
/* 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 * fn_lvtype
= type2llvm::function_td_to_lvtype(llvm_cx_.borrow(),
lambda->valuetype(),
true /*wrapper_flag*/);
/* create (initially empty) function */
fn = llvm::Function::Create(fn_lvtype,
llvm::Function::ExternalLinkage,
lambda->name(),
llvm_module_.get());
/* also adopt lambda's formal argument names */
{
int i = 0;
for (auto & arg : fn->args()) {
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());
}
++i;
}
}
return fn;
} /*codegen_lambda_decl*/
llvm::Function *
MachPipeline::codegen_lambda_defn(bp<Lambda> lambda,
llvm::IRBuilder<> & /*ir_builder*/)
{
constexpr bool c_debug_flag = true;
scope log(XO_DEBUG(c_debug_flag),
xtag("lambda-name", lambda->name()));
global_env_->require_global(lambda->name(), lambda.get());
/* correct PROVIDED this is a toplevel lambda */
lambda->attach_envs(this->global_env_);
/* 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;
}
/* environment for this lambda's clsoure
* passed as extra 1st argument
*/
llvm::Value * envptr = llvm_fn->args().begin();
/* generate function body */
auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(),
"entry",
llvm_fn);
/* since we need to explictly set builder's insert point,
* make a new builder instead of disturbing the top-to-bottom flow of the
* called ir_builder
*/
llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref());
tmp_ir_builder.SetInsertPoint(block);
/** Actual parameters will need their own activation record.
* Track its shape + setup/teardown here.
**/
this->env_stack_.push(activation_record(lambda.get()));
bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, tmp_ir_builder);
if (!ok_flag) {
this->env_stack_.pop();
return nullptr;
}
llvm::Value * retval = this->codegen(lambda->body(),
envptr,
tmp_ir_builder);
if (retval) {
/* completes the function.. */
tmp_ir_builder.CreateRet(retval);
/* validate! always validate! */
llvm::verifyFunction(*llvm_fn);
if (log) {
std::string buf;
llvm::raw_string_ostream ss(buf);
llvm_fn->print(ss);
log(xtag("IR-before-opt", buf));
}
/* optimize! improves IR */
ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_);
if (log) {
std::string buf;
llvm::raw_string_ostream ss(buf);
llvm_fn->print(ss);
log(xtag("IR-after-opt", buf));
}
} else {
/* oops, something went wrong */
llvm_fn->eraseFromParent();
llvm_fn = nullptr;
}
this->env_stack_.pop();
log && log("after pop, env stack size Z",
xtag("Z", env_stack_.size()),
xtag("llvm_fn", (void*)llvm_fn));
return llvm_fn;
} /*codegen_lambda_defn*/
llvm::Value *
MachPipeline::codegen_lambda_closure(bp<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_defn(lambda, ir_builder);
if (!lvfn) {
cerr << "MachPipeline::codegen_lambda_closure: codegen lambda failed"
<< endl;
return nullptr;
}
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}, "closure" /*name*/);
}
return lv_closure;
} /*codegen_lambda_closure*/
llvm::Value *
MachPipeline::codegen_variable(bp<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())
<< endl;
return nullptr;
}
activation_record & ar = env_stack_.top();
const runtime_binding_detail * binding = ar.lookup_var(var->name());
if (!binding)
return nullptr;
/* code to load value from stack */
return ir_builder.CreateLoad(binding->llvm_type_,
binding->llvm_addr_,
var->name().c_str());
} /*codegen_variable*/
llvm::Value *
MachPipeline::codegen_ifexpr(bp<IfExpr> expr,
llvm::Value * envptr,
llvm::IRBuilder<> & 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
= ir_builder.CreateFCmpONE(test_ir,
llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(),
llvm::APFloat(0.0)),
"iftest");
llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent();
/* when_true_bb, when_false_bb, merge_bb:
* initially-empty basic-blocks for {when_true, when_false, merged} codegen
*/
/* when_true branch inserted at (current) end of function */
llvm::BasicBlock * when_true_bb
= llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(),
"when_true",
parent_fn);
llvm::BasicBlock * when_false_bb
= llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(),
"when_false");
llvm::BasicBlock * merge_bb
= llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(),
"merge");
/* IR to direct control flow to one of {when_true_bb, when_false_bb},
* depending on result of test_with_cmp_ir
*/
ir_builder.CreateCondBr(test_with_cmp_ir,
when_true_bb,
when_false_bb);
/* populate when_true_bb */
llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref());
tmp_ir_builder.SetInsertPoint(when_true_bb);
llvm::Value * when_true_ir = this->codegen(expr->when_true(),
envptr,
tmp_ir_builder);
if (!when_true_ir)
return nullptr;
/* at end of when-true sequence, jump to merge suffix */
tmp_ir_builder.CreateBr(merge_bb);
/* note: codegen for expr->when_true() may have altered builder's "current block" */
when_true_bb = tmp_ir_builder.GetInsertBlock();
/* populate when_false_bb */
parent_fn->insert(parent_fn->end(), when_false_bb);
tmp_ir_builder.SetInsertPoint(when_false_bb);
llvm::Value * when_false_ir = this->codegen(expr->when_false(),
envptr,
tmp_ir_builder);
if (!when_false_ir)
return nullptr;
/* at end of when-false sequence, jump to merge suffix */
tmp_ir_builder.CreateBr(merge_bb);
/* note: codegen for expr->when_false() may have altered builder's "current block" */
when_false_bb = tmp_ir_builder.GetInsertBlock();
/* merged suffix sequence */
parent_fn->insert(parent_fn->end(), merge_bb);
tmp_ir_builder.SetInsertPoint(merge_bb);
/** TODO: switch to getInt1Ty here **/
llvm::PHINode * phi_node
= tmp_ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()),
2 /*#of branches being merged (?)*/,
"iftmp");
phi_node->addIncoming(when_true_ir, when_true_bb);
phi_node->addIncoming(when_false_ir, when_false_bb);
return phi_node;
} /*codegen_ifexpr*/
llvm::Value *
MachPipeline::codegen(bp<Expression> expr,
llvm::Value * envptr,
llvm::IRBuilder<> & ir_builder)
{
switch(expr->extype()) {
case exprtype::define:
case exprtype::assign:
case exprtype::sequence:
case exprtype::convert:
break;
case exprtype::constant:
return this->codegen_constant(ConstantInterface::from(expr));
case exprtype::primitive:
return this->codegen_primitive_closure(PrimitiveExprInterface::from(expr), ir_builder);
case exprtype::apply:
return this->codegen_apply(Apply::from(expr), envptr, ir_builder);
case exprtype::lambda:
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), envptr, ir_builder);
case exprtype::ifexpr:
return this->codegen_ifexpr(IfExpr::from(expr), envptr, ir_builder);
case exprtype::invalid:
case exprtype::n_expr:
return nullptr;
break;
}
cerr << "MachPipeline::codegen: error: no handler for expression of type T"
<< xtag("T", expr->extype())
<< endl;
return nullptr;
} /*codegen*/
llvm::Value *
MachPipeline::codegen_toplevel(bp<Expression> 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
*/
/* WIP. STRATEGY:
* - xo::scm::ClosureExpr (an expression that generates a closure)
* closure = {lambda, env}
*
* - pass 1:
* return list of closure expressions;
* codegen the lambda decls using lambda from each closure
* - pass 2:
* codegen closures: use env chain to resolve variables
*/
/* Pass 1. */
auto fn_v = this->find_lambdas(expr);
for (auto lambda : fn_v) {
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,
*(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()));
}
#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
MachPipeline::dump_current_module()
{
/* dump module contents to console */
llvm_module_->dump();
}
void
MachPipeline::machgen_current_module()
{
static llvm::ExitOnError llvm_exit_on_err;
auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker();
/* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create
*
* Note that @ref ir_pipeline_ holds reference, which is invalidated here
*/
auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_),
std::move(llvm_cx_->llvm_cx()));
/* note does not discard llvm_cx_->llvm_cx(), it's already been moved */
this->llvm_cx_ = nullptr;
llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker));
this->recreate_llvm_ir_pipeline();
} /*machgen_current_module*/
std::string_view
MachPipeline::mangle(const std::string & sym) const
{
return this->jit_->mangle(sym);
} /*mangle*/
llvm::Expected<llvm::orc::ExecutorAddr>
MachPipeline::lookup_symbol(const std::string & sym)
{
/* llvm_sym: ExecutorSymbolDef */
auto llvm_sym_expected = this->jit_->lookup(sym);
if (llvm_sym_expected) {
auto llvm_addr = llvm_sym_expected.get().getAddress();
return llvm_addr;
} else {
return llvm_sym_expected.takeError();
}
} /*lookup_symbol*/
void
MachPipeline::display(std::ostream & os) const {
os << "<MachPipeline>";
}
std::string
MachPipeline::display_string() const {
return tostr(*this);
}
} /*namespace jit*/
} /*namespace xo*/
/* end MachPipeline.cpp */