/* @file MachPipeline.cpp */ #include "MachPipeline.hpp" #include "activation_record.hpp" #include "type2llvm.hpp" #include "xo/expression/pretty_variable.hpp" #include 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> MachPipeline::make_aux() { MachPipeline::init_once(); static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); return std::unique_ptr(new MachPipeline(std::move(jit) )); } /*make*/ rp MachPipeline::make() { static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr jit = llvm_exit_on_err(make_aux()); return jit.release(); } /*make*/ MachPipeline::MachPipeline(std::unique_ptr 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_cx_ = LlvmContext::make(); llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); llvm_module_->setDataLayout(this->jit_->data_layout()); 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 MachPipeline::get_function_name_v() { std::vector 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 expr) { TypeDescr td = expr->value_td(); if (Reflect::is_native(td)) { return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); } else if (Reflect::is_native(td)) { return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); } else if (Reflect::is_native(td)) { return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), llvm::APSInt(*(expr->value_tp().recover_native()))); } else if (Reflect::is_native(td)) { return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), llvm::APSInt(*(expr->value_tp().recover_native()))); } 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 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 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 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 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, 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 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 index_v = {{ 0 }}; //ir_builder.CreateExtractValue(Value *Agg, ArrayRef 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 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 index_v = {{ 1 }}; lv_fnenvptr = ir_builder.CreateExtractValue(llvm_closure, index_v, "envptr"); } std::vector 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 = ""; } 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> MachPipeline::find_lambdas(bp expr) const { std::vector> retval_v; expr->visit_preorder( [&retval_v](bp 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) { 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, 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, 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 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 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 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 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 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 << ""; } std::string MachPipeline::display_string() const { return tostr(*this); } } /*namespace jit*/ } /*namespace xo*/ /* end MachPipeline.cpp */