diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 11589e8c..88c9f544 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -161,6 +161,7 @@ namespace xo { llvm::AllocaInst * create_entry_frame_alloca(llvm::Function * llvm_fn, llvm::StructType * frame_llvm_type); +#ifdef OBSOLETE // see activation_record::create_entry_block_alloca() /** codegen helper for a user-defined function (codegen_lambda()): * create stack slot on behalf of some formal parameter to a function, * so we can avoid SSA restriction on function body @@ -170,6 +171,7 @@ namespace xo { llvm::AllocaInst * create_entry_block_alloca(llvm::Function * llvm_fn, const std::string & var_name, TypeDescr var_type); +#endif private: /** (re)create pipeline to turn expressions into llvm IR code **/ diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 15ee76f5..82763e6c 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -28,31 +28,61 @@ namespace xo { int j_rt_slot) : i_rt_link_{i_rt_link}, j_rt_slot_{j_rt_slot} {} + static runtime_binding_path stackonly() { + return runtime_binding_path(0, -1); + } static runtime_binding_path local(int j_rt_slot) { return runtime_binding_path(0, j_rt_slot); } + bool is_stackonly() const { return (i_rt_link_ == 0) && (j_rt_slot_ == -1); } + bool is_captured() const { return !is_stackonly(); } + public: /** nnumber of parent runtime env links to traverse. -1 if global. -2 if sentinel **/ int i_rt_link_ = -2; - /** slot# within runtime environment where this variable bound. + /** >= 0: slot# within explicit runtime environment where this variable bound. * (local vars only -- ignored for global vars) + * -1: stack-only parameter **/ int j_rt_slot_ = 0; }; + struct runtime_binding_detail { + /** Formal index position for this formal parameter. + * Index into @ref activation_record::binding_v_, + * also for @ref Lambda::fn_arg + **/ + int i_argno_ = -1; + + /** instructions for establishing stack address of this variable + * In practice will be either an AllocaInst (for non-captured variables), + * or result of IRBuilder<>::CreateInBoundsGEP (for captured variables). + **/ + llvm::Value * llvm_addr_ = nullptr; + + /** llvm type associated with stack-allocated variable. + * Determines (when combined with llvm::DataLayout) how much space + * will be required for this particular variable + **/ + llvm::Type * llvm_type_ = nullptr; + }; + /** * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * * 2. each function needs its own IR builder, to keep track of things like insert point * * 3. simple case first. - * if lm->needs_closure_flag() is false, then + * if lm->needs_closure_flag() is false, then: * - * a. all formal parameters of lm + * a. still need a closure-shaped object, because when we invoke function, we may + * not know until runtime whether it relies on closure. + * For such function we will generate a closure with empty environment pointer. + * b. all formal parameters of lm * are used only in the layer associated with that lambda's body; in particular * they aren't free in any nested lambda - * b. conversely, the top layer of lm's body has no free variables. + * c. conversely, the top layer of lm's body has no free variables. * The only variables that *do* appear are lm's formal parameters. * * In this case, all of lm's formals will be allocated on the stack using regular @@ -70,20 +100,76 @@ namespace xo { class activation_record { public: using Lambda = xo::ast::Lambda; + using TypeDescr = xo::reflect::TypeDescr; public: - activation_record(const ref::rp & lm) - : lambda_{lm} {} + activation_record(const ref::rp & lm); - llvm::AllocaInst * lookup_var(const std::string & var_name) const; + const ref::rp lambda() const { return lambda_; } - /** - * @p j_slot index number (0-based) for var_name in formal parameter list for - * its originating lambda + /** retrieve @c llvm::Value* representing the primary stack location + * for formal parameter @p var_name **/ - llvm::AllocaInst * alloc_var(std::size_t j_slot, - const std::string & var_name, - llvm::AllocaInst * alloca); + const runtime_binding_detail * lookup_var(const std::string & var_name) const; + + /** Remember allocation of a function variable on the stack + * + * @param var_name. formal parameter name + * @param binding. address + supporting details for + * primary (stack-allocated) storage for this variable + **/ + const runtime_binding_detail * alloc_var(const std::string & var_name, + const runtime_binding_detail & binding); + +#ifdef NOT_USING + llvm::AllocaInst * create_runtime_localenv_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & fn_ir_builder); +#endif + + runtime_binding_detail create_entry_block_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & fn_ir_builder, + int i_arg, + const std::string & var_name, + TypeDescr var_td); + + /** generate instructions that establish stacck location for a local-environment slot + * + * @param llvm_cx. handle for context -- manages storage for llvm::Types + related + * @param localenv_llvm_type. describes contents of local environment + * for a particular function. Same as @c localenv_alloca->getAllocatedType() + * @param localenv_alloca. stack location for local environment + * @param i_slot. 0-based slot number within local environment, + * for which address is required + * @param fn_ir_builder. insertion point for generated instructions + * that compute target slot address (will be at/near top of function, + * since we will copy captured function arguments to localenv, + * then use the localenv copy exclusively. + * @return value representing localenv slot address + **/ + llvm::Value * runtime_localenv_slot_addr(ref::brw llvm_cx, + llvm::StructType * localenv_llvm_type, + llvm::AllocaInst * localenv_alloca, + int i_slot, + llvm::IRBuilder<> & fn_ir_builder); + + /** establish storage for formal parameters on behalf of a new-but-empty + * llvm function @p llvm_fn. Creates llvm IR instructions on function + * entry that + * 1. allocates stack space for function parameters. + * 2. stores incoming parameters in that stack space. + * + * Strategy: + * - for stackonly parameters, use individual @c llvm::AllocaInst instances + * - create custom @c llvm::StructType for captured parameters, also initially stack-allocated + **/ + bool bind_locals(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & ir_builder); private: /** this activation record created on behalf of a call to @ref lambda_. @@ -94,16 +180,27 @@ namespace xo { **/ ref::rp lambda_; - /** @c binding_v_[i] specifies how/where to get location for formal parameter number *i* - * of @ref lambda_. - * + /** @c binding_v_[i] specifies how/where we mean to navigate to + * location for formal parameter number *i* of @ref lambda_. **/ std::vector binding_v_; - /** maps named slots in a stack frame to logical addresses. - * Only applies to not-captured vars with i_rt_link_=0 + /** if this function requires an explicit environment, + * gives stack location for that environment. **/ - std::map frame_; /* <-> kaleidoscope NamedValues */ + llvm::AllocaInst * localenv_alloca_ = nullptr; + + /** maps named slots in a stack frame to logical addresses. + * + * - For captured arguments: will refer to slot within stack-allocated local environment + * (an llvm::StructType, created by type2llvm::create_localenv_llvm_type()) + * + * - For non-captured arguments: will refer to stack-allocated argument copy + * + * In either case using copy-to-stack to evade directly confronting + * so we don't have to comply with llvm IR's SSA requirement. + **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ }; /*activation_record*/ } /*namespace jit*/ diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp new file mode 100644 index 00000000..2d532893 --- /dev/null +++ b/include/xo/jit/type2llvm.hpp @@ -0,0 +1,181 @@ +/** @file type2llvm.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#include "xo/expression/Lambda.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include +//#include + +namespace xo { + namespace jit { + /** + **/ + struct type2llvm { + public: + using Lambda = xo::ast::Lambda; + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** establish suitable llvm representation for a c++ type (described by @p td) + * llvm types are unique'd, at least within @p llvm_cx + **/ + static llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr td); + + /** establish llvm representation for a function type + * described by @p fn_td + **/ + static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + /** establish llvm concrete representation for a particular lambda's + * runtime local environment: + * + * ^ + * | 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 + * + * arg[] comprises the subset of lambda arg names arg[j] for which + * lambda->is_captured(arg[j]) is true + **/ + static llvm::StructType * + create_localenv_llvm_type(xo::ref::brw llvm_cx, + xo::ref::brw lambda); + + /** establish llvm rep'n for a pointer to an abstract local environment: + * + * +-------+ + * | o-------------\ + * +-------+ | + * | + * | + * | + * v + * +-------+ + * parent_env [0] | o-------> _env_api* + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + **/ + static llvm::PointerType * + env_api_llvm_ptr_type(xo::ref::brw llvm_cx); + + /** function type: + * @code + * env_api_* (env_api* env, int ctl); + * @endcode + * + * 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 + * + * returns function-pointer type + **/ + static llvm::PointerType * + require_localenv_unwind_llvm_fnptr_type(xo::ref::brw llvm_cx, + llvm::PointerType * hint_envptr_llvm_type = nullptr); + + private: + /** establish llvm representation for a function-pointer type + * described by @p fn_td + **/ + static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + /** establish llvm representation for a struct type described by @p struct_td + **/ + static llvm::StructType * struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td); + + /** establish llvm representation for a pointer type described by @p pointer_td **/ + static llvm::PointerType * pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td); + + /** establish llvm abstract representation for a local environment: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * + * 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 + * + * Concrete implementation will probably occupy additional memory, + * to store captured lambda variables. + * + * @see type2llvm::function_td_to_llvm_closure_type + **/ + static llvm::StructType * + env_api_llvm_type(xo::ref::brw llvm_cx); + + /** establish llvm abstract representation for a closure: + * struct with + * - [0] function pointer + * - [1] runtime localenv pointer + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * 1. for primitives, localenv will be null pointer + * 2. for lambdas L with L->requires_closure_flag() = false, + * localenv will also be null pointer + * 3. for lambdas with L->requires_closure_flag() = true, + * + * localenv will (for lambdas requiring closures) + * in practice be struct: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * Implementation here will just use generic pointer for runtime + * localenv. + **/ + static llvm::StructType * + function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + }; /*type2llvm*/ + } /*namespace jit*/ +} /*namespace xo*/ + +/** end type2llvm.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 4168eebe..c52f7116 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS MachPipeline.cpp intrinsics.cpp activation_record.cpp + type2llvm.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 041f52b4..3bd895f0 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,8 @@ /* @file MachPipeline.cpp */ #include "MachPipeline.hpp" +#include "activation_record.hpp" +#include "type2llvm.hpp" #include namespace xo { @@ -156,187 +158,9 @@ namespace xo { return nullptr; } /*codegen_constant*/ - namespace { - /** REMINDER: - * 1. creation of llvm types is idempotent - * (duplicate calls will receive the same llvm::Type* pointer) - * 2. llvm::Types are never deleted. - **/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); - - /** obtain llvm representation for a function type with the same signature as - * that represented by @p fn_td - **/ - llvm::FunctionType * - function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - int n_fn_arg = fn_td->n_fn_arg(); - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - TypeDescr retval_td = fn_td->fn_retval(); - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); - - if (!llvm_retval) - return nullptr; - - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - return llvm_fn_type; - } - - llvm::PointerType * - function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); - - /** like C: llvm IR doesn't support function-valued variables; - * it does however support pointer-to-function-valued variables - **/ - auto * llvm_ptr_type - = llvm::PointerType::get(llvm_fn_type, - 0 /*numbered address space*/); - - return llvm_ptr_type; - } - - /** - * Generate llvm::Type correspoinding to a TypeDescr for a struct. - **/ - llvm::StructType * - struct_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr struct_td) - { - // see - // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] - - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - /* note: object pointer ignored for struct types, - * since number of members is known at compile time - */ - int n_member = struct_td->n_child(nullptr /*&object*/); - - /* one type for each struct member */ - std::vector llvm_membertype_v; - llvm_membertype_v.reserve(n_member); - - for (int i = 0; i < n_member; ++i) { - StructMember const & sm = struct_td->struct_member(i); - - llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, - sm.get_member_td())); - } - - std::string struct_name = std::string(struct_td->short_name()); - - /* structs with names: within an llvmcontext, must be unique - * - * We can however compare the offsets recorded in xo::reflect with - * offsets chosen by llvm, *once we've created the llvm type* - * - * Also, we can't guarantee that a c++ type was completely reflected -- - * it's possible one or more members were omitted, in which case - * it's unlikely at best that llvm chooses the same layout. - * - * Instead: tell llvm to make packed struct, - * and introduce dummy members for padding. - * - * A consequence is we have to maintain mapping between llvm's - * member numbering and xo::reflect's - */ - llvm::StructType * llvm_struct_type - = llvm::StructType::create(llvm_cx_ref, - llvm_membertype_v, - llvm::StringRef(struct_name), - false /*!isPacked*/); - - /* TODO: inspect (how) offsets that llvm is using - * we need them to match what C++ chose - * - * (because we want jitted llvm code to interoperate with - * C++ library code that has structs) - */ - - // GetElementPtrInst is interesting, - // but I think that's for generating code - - return llvm_struct_type; - } /*struct_td_to_llvm_type*/ - - llvm::PointerType * - pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) - { - assert(pointer_td->is_pointer()); - - TypeDescr dest_td = pointer_td->fixed_child_td(0); - - llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_dest_type); - - return llvm_ptr_type; - } /*pointer_td_llvm_type*/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - if (td->is_function()) { - /* 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); - } else if (td->is_struct()) { - return struct_td_to_llvm_type(llvm_cx, td); - } else if (td->is_pointer()) { - return pointer_td_to_llvm_type(llvm_cx, td); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt1Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt8Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt16Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt32Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt64Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getFloatTy(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getDoubleTy(llvm_cx_ref); - } else { - cerr << "td_to_llvm_type: no llvm type available for T" - << xtag("T", td->short_name()) - << endl; - return nullptr; - } - } - } - llvm::Type * MachPipeline::codegen_type(TypeDescr td) { - return td_to_llvm_type(llvm_cx_.borrow(), td); + return type2llvm::td_to_llvm_type(llvm_cx_.borrow(), td); } llvm::Function * @@ -368,7 +192,7 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); if (!llvm_fn_type) return nullptr; @@ -577,7 +401,7 @@ namespace xo { */ llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); return ir_builder.CreateCall(llvm_fn_type, llvm_fnval, @@ -586,6 +410,7 @@ namespace xo { } /*codegen_apply*/ +#ifdef OBSOLETE /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ llvm::AllocaInst * MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, @@ -603,8 +428,8 @@ namespace xo { llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), llvm_fn->getEntryBlock().begin()); - llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), - var_type); + 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) { @@ -627,6 +452,7 @@ namespace xo { return retval; } /*create_entry_block_alloca*/ +#endif std::vector> @@ -678,8 +504,8 @@ namespace xo { #endif llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, @@ -734,10 +560,18 @@ namespace xo { ir_builder.SetInsertPoint(block); /** Actual parameters will need their own activation record. - * Track its shape here. + * 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, ir_builder); + + if (!ok_flag) { + this->env_stack_.pop(); + return nullptr; + } + +#ifdef OBSOLETE { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -773,6 +607,7 @@ namespace xo { ++i; } } +#endif llvm::Value * retval = this->codegen(lambda->body(), ir_builder); @@ -827,14 +662,16 @@ namespace xo { return nullptr; } - llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + activation_record & ar = env_stack_.top(); - if (!alloca) + const runtime_binding_detail * binding = ar.lookup_var(var->name()); + + if (!binding) return nullptr; /* code to load value from stack */ - return ir_builder.CreateLoad(alloca->getAllocatedType(), - alloca, + return ir_builder.CreateLoad(binding->llvm_type_, + binding->llvm_addr_, var->name().c_str()); } /*codegen_variable*/ diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 4b6a77a0..413412af 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -1,6 +1,7 @@ /* @file activation_record.cpp */ #include "activation_record.hpp" +#include "type2llvm.hpp" #include "xo/indentlog/print/tag.hpp" #include @@ -9,7 +10,29 @@ namespace xo { using std::cerr; using std::endl; - llvm::AllocaInst * + activation_record::activation_record(const ref::rp & lm) + : lambda_{lm}, + binding_v_(lm->n_arg()) + { + /* populate binding_v_ */ + int n_arg = lm->n_arg(); + binding_v_.resize(n_arg); + + /* next slot# to use in explicit activation record */ + int rt_env_slot = 0; + + for (int i_arg = 0; i_arg < n_arg; ++i_arg) { + if (lm->is_captured(lm->i_argname(i_arg))) { + /* local param #i_arg needs a slot in explicit activation record */ + binding_v_[i_arg] = runtime_binding_path::local(rt_env_slot); + ++rt_env_slot; + } else { + binding_v_[i_arg] = runtime_binding_path::stackonly(); + } + } + } /*ctor*/ + + const runtime_binding_detail * activation_record::lookup_var(const std::string & x) const { auto ix = frame_.find(x); @@ -21,13 +44,12 @@ namespace xo { return nullptr; } - return ix->second; + return &(ix->second); } /*lookup_var*/ - llvm::AllocaInst * - activation_record::alloc_var(std::size_t j_slot, - const std::string & x, - llvm::AllocaInst * alloca) + const runtime_binding_detail * + activation_record::alloc_var(const std::string & x, + const runtime_binding_detail & binding) { if (frame_.find(x) != frame_.end()) { cerr << "activation_record::alloc_var: variable x already present in frame" @@ -36,14 +58,340 @@ namespace xo { return nullptr; } - if (j_slot >= binding_v_.size()) - binding_v_.resize(j_slot + 1); + frame_[x] = binding; - binding_v_[j_slot] = runtime_binding_path::local(j_slot); - - frame_[x] = alloca; - return alloca; + return &(frame_[x]); } /*alloc_var*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + runtime_binding_detail + activation_record::create_entry_block_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & fn_ir_builder, + int i_arg, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + 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::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, + 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 runtime_binding_detail{}; /*sentinel*/ + + llvm::AllocaInst * stackaddr = fn_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + + log && log(xtag("alloca", (void*)stackaddr), + xtag("align", stackaddr->getAlign().value()) + //xtag("size", retval->getAllocationSize(data_layout).value()) + ); + + return {i_arg, stackaddr, llvm_var_type}; + } /*create_entry_block_alloca*/ + +#ifdef NOT_USING + llvm::AllocaInst * + activation_record::create_runtime_localenv_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & ir_builder) + + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::StructType * localenv_llvm_type + = type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow()); + + if (!localenv_llvm_type) + return nullptr; + + llvm::AllocaInst * retval = ir_builder.CreateAlloca(localenv_llvm_type, + nullptr /*ArraySize*/, + "_localenv"); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()) + //xtag("size", retval->getAllocationSize(data_layout).value()) + ); + + return retval; + } /*create_runtime_localenv_alloca*/ +#endif + + llvm::Value * + activation_record::runtime_localenv_slot_addr(ref::brw llvm_cx, + llvm::StructType * localenv_llvm_type, + llvm::AllocaInst * localenv_alloca, + int i_slot, +#ifdef NOT_HERE + llvm::Value * llvm_slot_value, +#endif + llvm::IRBuilder<> & tmp_ir_builder) + { + llvm::Value * i32_slot + = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, + i_slot /*value*/)); + std::array index_v = { + {i32_slot /*environment slot #0*/}}; + + llvm::Value * llvm_localenv_slot_ptr + = tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type, + localenv_alloca, + index_v); + + return llvm_localenv_slot_ptr; + +#ifdef NOT_HERE + tmp_ir_builder.CreateStore(llvm_value, //llvm_0ptr, + llvm_parent_env_ptr); +#endif + } /*runtime_localenv_slot_addr*/ + + bool + activation_record::bind_locals(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda_->name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + /* 1st pass: handle stackonly variables + * + * We presume this must come first, + * for subsequent mem2reg optimization pass to consider + */ + { + 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 + */ + ir_builder.CreateStore(&arg, binding.llvm_addr_); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + this->alloc_var(arg_name, binding); + } + + ++i_arg; + } + } + + /* REMINDER: all functions need to follow the closure pattern, + * to accomodate cases where we don't know until runtime + * what kind of function we are invoking. + * + * This means: + * - always represent function in IR by a closure-shaped object + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * We hope to optimize away the closures in cases where we know + * their contents at compile time + * + */ + + /* 2nd pass: handle captured formal parameters */ + if (lambda_->needs_closure_flag()) { + llvm::StructType * localenv_llvm_type + = type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow()); +#ifdef NOT_USING + llvm::PointerType * envapiptr_llvm_type + = type2llvm::env_api_llvm_ptr_type(llvm_cx); +#endif + + if (!localenv_llvm_type) + return false; + + /* + * runtime localenv: ^ + * | 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 + * + * arg[] comprises the subset of lambda arg names arg[j] for which + * lambda->is_captured(arg[j]) is true + */ + llvm::AllocaInst * localenv_alloca + = tmp_ir_builder.CreateAlloca(localenv_llvm_type, + nullptr /*ArraySize*/, + "_localenv"); + + if (!localenv_alloca) + return false; + + /* remember environemnt location. + * Will need this if need to copy-to-stack + */ + this->localenv_alloca_ = localenv_alloca; + + int i_localenv_slot = 0; + + /* store localenv->parent_env */ + { + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + //llvm_0ptr, + tmp_ir_builder); + + if (!slot_addr) + return false; + + ++i_localenv_slot; + + /* null pointer for now */ + /* TODO: get parent environment (from runtime closure created for this function) */ + llvm::Value * llvm_0ptr + = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx)); + + tmp_ir_builder.CreateStore(llvm_0ptr, + slot_addr); + } + + /* store localenv->unwind_fn */ + { + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + //llvm_0ptr, + tmp_ir_builder); + + if (!slot_addr) + return false; + + ++i_localenv_slot; + + /* null function pointer for now */ + /* TODO: construct unwind function */ + llvm::Value * llvm_0ptr + = (llvm::ConstantPointerNull::get + (type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx))); + + tmp_ir_builder.CreateStore(llvm_0ptr, + slot_addr); + } + + 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. + * + */ + + TypeDescr arg_td = lambda_->fn_arg(i_arg); + + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + tmp_ir_builder); + + if (!slot_addr) + return false; + + ++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; + } + } + + return true; + } /*bind_locals*/ } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp new file mode 100644 index 00000000..23ee48c4 --- /dev/null +++ b/src/jit/type2llvm.cpp @@ -0,0 +1,305 @@ +/* @file type2llvm.cpp */ + +#include "type2llvm.hpp" +#include "xo/reflect/Reflect.hpp" +//#include "xo/reflect/struct/StructMember.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + using xo::reflect::StructMember; + using std::cerr; + using std::endl; + + namespace jit { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + type2llvm::td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* 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); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } /*td_to_llvm_type*/ + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = type2llvm::td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } /*function_td_to_llvm_type*/ + + llvm::PointerType * + type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + type2llvm::struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(type2llvm::td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + type2llvm::pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = type2llvm::td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::PointerType * + type2llvm::require_localenv_unwind_llvm_fnptr_type(xo::ref::brw llvm_cx, + llvm::PointerType * envptr_llvm_type) + { + if (!envptr_llvm_type) + envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is _env_api pointer */ + llvm_argtype_v.push_back(envptr_llvm_type); + + /* 2nd arg is i32 */ + llvm_argtype_v.push_back(llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is _env_api pointer */ + llvm::Type * retval_llvm_type = envptr_llvm_type; + + /* _env_api* (_env_api*, i32) */ + auto * unwind_llvm_type + = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + /* _env_api* (*)(_env_api*, i32) */ + auto * unwind_llvm_fnptr_type + = llvm::PointerType::getUnqual(unwind_llvm_type); + + return unwind_llvm_fnptr_type; + } /*require_localenv_unwind_llvm_fnptr_type*/ + + llvm::StructType * + type2llvm::env_api_llvm_type(xo::ref::brw llvm_cx) + { + /* _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[0]: pointer to a local environment */ + llvm::PointerType * envptr_llvm_type + = llvm::PointerType::getUnqual(env_llvm_type); + + /* _env_api[1]: unwwind/copy function */ + llvm::PointerType * unwind_llvm_fnptr_type + = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, + envptr_llvm_type); + + /* now supply _env_api members */ + env_llvm_type->setBody(envptr_llvm_type /*_env_api[0]*/, + unwind_llvm_fnptr_type /*_env_api[1]*/); + + return env_llvm_type; + } /*env_api_llvm_type*/ + + llvm::PointerType * + type2llvm::env_api_llvm_ptr_type(xo::ref::brw llvm_cx) + { + llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx); + + return llvm::PointerType::getUnqual(env_llvm_type); + } /*env_api_llvm_ptr_type*/ + +#ifdef NOT_USING + llvm::StructType * + type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* closure type doesn't need a name. + * (We might find it convenient to give one anyway) + */ + llvm::StructType * closure_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + llvm::PointerType * parent_llvm_type + } /*function_td_to_llvm_fnptr_type*/ +#endif + + llvm::StructType * + type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, + xo::ref::brw lambda) + { + llvm::PointerType * parentenvptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); + llvm::PointerType * unwind_llvm_fnptr_type + = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, parentenvptr_llvm_type); + + std::vector member_llvm_type_v; + member_llvm_type_v.push_back(parentenvptr_llvm_type); + member_llvm_type_v.push_back(unwind_llvm_fnptr_type); + + for (const auto & var : lambda->argv()) { + if (lambda->is_captured(var->name())) { + /* var needs a slot in localenv_llvm_type for lambda */ + + member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, + var->valuetype())); + } + } + + /* this type doesn't need a name, right? would be "_" + lambda name + "_localenv" */ + llvm::StructType * localenv_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + localenv_llvm_type->setBody(member_llvm_type_v, false /*!is_packed*/); + + return localenv_llvm_type; + } /*create_localenv_llvm_type*/ + + } /*namespace jit*/ +} /*namespace xo*/ + +/* end type2llvm.cpp */