From 921c70dcd74ccc8a7469aab32687df0cee32f91e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 18:51:23 -0400 Subject: [PATCH 01/14] xo-jit: basically drop ex_kaleidoscope4, jit .h removed --- example/ex_kaleidoscope4/ex_kaleidoscope4.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp index 63926aad..46f0c81d 100644 --- a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp +++ b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp @@ -1,6 +1,6 @@ /** ex_kaleidoscop4.cpp **/ -#include "xo/jit/KaleidoscopeJit.hpp" +#include "xo/jit/Jit.hpp" #include int @@ -8,9 +8,9 @@ main() { using std::cerr; using std::endl; - auto jit = xo::jit::KaleidoscopeJIT::Create(); + //auto jit = xo::jit::KaleidoscopeJIT::Create(); - cerr << "created kaleidoscope jit successfully" << endl; + //cerr << "created kaleidoscope jit successfully" << endl; } /** end ex_kaleidoscope4.cpp **/ From fdc5d46fd7b340c716ee4a26aa89c96516fc0241 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 20:26:07 -0400 Subject: [PATCH 02/14] xo-jit: + runtime_binding_path, ++ to activation_record --- include/xo/jit/activation_record.hpp | 82 ++++++++++++++++++++++++++-- src/jit/MachPipeline.cpp | 4 +- src/jit/activation_record.cpp | 8 ++- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index c2aba2dd..15ee76f5 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -6,6 +6,7 @@ #pragma once #include "LlvmContext.hpp" +#include "xo/expression/Lambda.hpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" # include @@ -16,21 +17,92 @@ namespace xo { namespace jit { - /** scope for a stack frame associated with a user-defined function + /** analagous to xo::ast::binding_path, + * but with locations renumbered to include only vars that belong to an explict runtime + * environment object; in other words we exclude vars with stack-only storage + **/ + struct runtime_binding_path { + public: + runtime_binding_path() = default; + runtime_binding_path(int i_rt_link, + int j_rt_slot) + : i_rt_link_{i_rt_link}, j_rt_slot_{j_rt_slot} {} + + static runtime_binding_path local(int j_rt_slot) { + return runtime_binding_path(0, j_rt_slot); + } + + 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. + * (local vars only -- ignored for global vars) + **/ + int j_rt_slot_ = 0; + }; + + /** + * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * - * each function needs its own IR builder, to keep track of things like insert point + * 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 + * + * a. 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. + * 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 + * allocInst, and we don't need a closure for lm. + * + * 4. complex case second + * If lm->needs_closure_flag() is true, then either: + * + * a. at least one formal parameter of lm appears free in some nested lambda. + * b. lambda's top layer itself contains one or more free variables. + * + * In either case we will create an explicit environment for lm, + * containing all the variables needed by some nested lambda **/ class activation_record { public: - activation_record() = default; + using Lambda = xo::ast::Lambda; + + public: + activation_record(const ref::rp & lm) + : lambda_{lm} {} llvm::AllocaInst * lookup_var(const std::string & var_name) const; - llvm::AllocaInst * alloc_var(const std::string & var_name, + /** + * @p j_slot index number (0-based) for var_name in formal parameter list for + * its originating lambda + **/ + llvm::AllocaInst * alloc_var(std::size_t j_slot, + const std::string & var_name, llvm::AllocaInst * alloca); private: - /** maps named slots in a stack frame to logical addresses **/ + /** this activation record created on behalf of a call to @ref lambda_. + * @ref Variable::path_ specifies a logical path to a variable, + * but does not distinguish stack-native variables from variables in explicit + * runtime environment records. + * + **/ + ref::rp lambda_; + + /** @c binding_v_[i] specifies how/where to get 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 + **/ std::map frame_; /* <-> kaleidoscope NamedValues */ }; /*activation_record*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9c6bb1cd..041f52b4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -736,7 +736,7 @@ namespace xo { /** Actual parameters will need their own activation record. * Track its shape here. **/ - this->env_stack_.push(activation_record()); + this->env_stack_.push(activation_record(lambda.get())); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -769,7 +769,7 @@ namespace xo { * in lambda body. * */ - env_stack_.top().alloc_var(arg_name, alloca); + env_stack_.top().alloc_var(i, arg_name, alloca); ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index c7f40362..4b6a77a0 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -25,7 +25,8 @@ namespace xo { } /*lookup_var*/ llvm::AllocaInst * - activation_record::alloc_var(const std::string & x, + activation_record::alloc_var(std::size_t j_slot, + const std::string & x, llvm::AllocaInst * alloca) { if (frame_.find(x) != frame_.end()) { @@ -35,6 +36,11 @@ namespace xo { return nullptr; } + if (j_slot >= binding_v_.size()) + binding_v_.resize(j_slot + 1); + + binding_v_[j_slot] = runtime_binding_path::local(j_slot); + frame_[x] = alloca; return alloca; } /*alloc_var*/ From d7192c1d97405a46e93157d35a5fe0c9d0fb3a99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 13:27:12 -0400 Subject: [PATCH 03/14] xo-jit: + explicit env for captured function args [wip, not tested] --- include/xo/jit/MachPipeline.hpp | 2 + include/xo/jit/activation_record.hpp | 135 ++++++++-- include/xo/jit/type2llvm.hpp | 181 +++++++++++++ src/jit/CMakeLists.txt | 1 + src/jit/MachPipeline.cpp | 217 ++-------------- src/jit/activation_record.cpp | 372 ++++++++++++++++++++++++++- src/jit/type2llvm.cpp | 305 ++++++++++++++++++++++ 7 files changed, 992 insertions(+), 221 deletions(-) create mode 100644 include/xo/jit/type2llvm.hpp create mode 100644 src/jit/type2llvm.cpp 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 */ From 4c8289336d4a291432f2fa4fcb0f2c113e3bad91 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 16:57:05 -0400 Subject: [PATCH 04/14] xo-jit: + primitive wrapper (accept+ignore envptr as 1st argument) --- include/xo/jit/MachPipeline.hpp | 10 +++ include/xo/jit/type2llvm.hpp | 10 ++- src/jit/MachPipeline.cpp | 130 +++++++++++++++++++++++++++----- src/jit/type2llvm.cpp | 8 +- 4 files changed, 138 insertions(+), 20 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 88c9f544..ed81c6c8 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -111,6 +111,16 @@ namespace xo { llvm::Type * codegen_type(TypeDescr td); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); + + /** like @ref codegen_primitive , but create wrapper function that accepts (and discards) + * environment pointer as first argument. + * + * Implementation consists of tail call to natural primitive, that skips the unused + * environment pointer + **/ + llvm::Function * codegen_primitive_wrapper(ref::brw expr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); /* NOTE: codegen_lambda() needs to be reentrant too. * for example can have a lambda in apply position. diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 2d532893..dffe155b 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -29,9 +29,17 @@ namespace xo { /** establish llvm representation for a function type * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime **/ static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag = false); /** establish llvm concrete representation for a particular lambda's * runtime local environment: diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 3bd895f0..fc44ae88 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -20,6 +20,7 @@ namespace xo { 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; @@ -167,7 +168,6 @@ namespace xo { MachPipeline::codegen_primitive(ref::brw expr) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag)); @@ -249,12 +249,123 @@ namespace xo { return fn; } /*codegen_primitive*/ + llvm::Function * + MachPipeline::codegen_primitive_wrapper(ref::brw 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 = 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_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!native_lvtype) + return nullptr; + + llvm::FunctionType * wrapper_lvtype + = type2llvm::function_td_to_llvm_type(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); + + 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 = 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? */ + 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_apply(ref::brw apply, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("apply", apply)); @@ -418,7 +529,6 @@ namespace xo { 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), @@ -475,7 +585,6 @@ namespace xo { MachPipeline::codegen_lambda_decl(ref::brw lambda) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); @@ -491,18 +600,6 @@ namespace xo { /* establish prototype for this function */ -#ifdef OBSOLETE - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_retval()); - - std::vector arg_type_v(lambda->n_arg()); - - for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_arg(i)); - } -#endif - llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), lambda->valuetype()); @@ -533,7 +630,6 @@ namespace xo { 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())); diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 23ee48c4..fdfda7d5 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -58,12 +58,16 @@ namespace xo { **/ llvm::FunctionType * type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { int n_fn_arg = fn_td->n_fn_arg(); std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); + llvm_argtype_v.reserve(n_fn_arg + (wrapper_flag ? 1 : 0)); + + if (wrapper_flag) + llvm_argtype_v.push_back(env_api_llvm_ptr_type(llvm_cx)); /** check function args are all known **/ for (int i = 0; i < n_fn_arg; ++i) { From f2fa9978cf558ade904b083d401c12ed75da9dd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 18:54:56 -0400 Subject: [PATCH 05/14] xo-jit: + unit test for primitive wrapper --- utest/MachPipeline.test.cpp | 61 ++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 5e0fafc4..880aabf1 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -183,7 +183,66 @@ namespace xo { REQUIRE(actual == expected); } } - } /*TEST_CASE(machpipeline)*/ + } /*TEST_CASE(machpipeline.fptr)*/ + + TEST_CASE("machpipeline.wrap", "[llvm][llvm_closure]") { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipelin.wrap")); + + auto jit = MachPipeline::make(); + + auto root = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + + llvm::Value * llvm_ircode + = jit->codegen_primitive_wrapper(root, *(jit->llvm_current_ir_builder())); + + /* TODO: printer for llvm::Value* */ + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "llvm_ircode for primitive wrapper:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "code generation failed" + << xtag("root", root) + << endl; + } + + REQUIRE(llvm_ircode); + + std::string wrapper_name = std::string("w.") + root->name(); + + jit->machgen_current_module(); + + auto llvm_addr = jit->lookup_symbol(wrapper_name); + + bool llvm_addr_flag = static_cast(llvm_addr); + + if (!llvm_addr_flag) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", wrapper_name) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", wrapper_name) + << endl; + } + + REQUIRE(llvm_addr_flag); + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + auto actual = (*fn_ptr)(nullptr, 4.0); + + REQUIRE(actual == 2.0); + } rp make_ratio() { From 792dcf015713e9817ab1f3988c82a1bba44850b3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:45:58 -0400 Subject: [PATCH 06/14] xo-jit: + type2llvm::create_closure_lvtype() --- include/xo/jit/type2llvm.hpp | 24 ++++++++++++++++++++++++ src/jit/type2llvm.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index dffe155b..75f33576 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -41,6 +41,28 @@ namespace xo { TypeDescr fn_td, bool wrapper_flag = false); + /** establish llvm concrete representation for a closure. + * + * +-------+ + * [0] | o-------> fnptr T (*)(envptr, ...) + * +-------+ + * [1] | o-------\ + * +-------+ | + * | + * | + * v + * +-------+ + * parent_env [0] | o-------> _env_api* + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * + * @return struct type. typename will be @c c.foo for lambda with name @c foo + **/ + static llvm::StructType * + create_closure_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw lambda); + /** establish llvm concrete representation for a particular lambda's * runtime local environment: * @@ -62,6 +84,8 @@ namespace xo { * * arg[] comprises the subset of lambda arg names arg[j] for which * lambda->is_captured(arg[j]) is true + * + * @return struct type. typename will be @c e.foo for lambda with name @c foo **/ static llvm::StructType * create_localenv_llvm_type(xo::ref::brw llvm_cx, diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index fdfda7d5..a5405f86 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -303,6 +303,36 @@ namespace xo { return localenv_llvm_type; } /*create_localenv_llvm_type*/ + llvm::StructType * + type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw lambda) + { + constexpr const char * c_prefix = "c."; + + /* would be precisely correct to use create_localenv_llvm_type() + * here. However judged not sufficiently helpful. + * Would still + * need environment cast whenever closure in apply position is + * not known at compile time. + */ + llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, + lambda->valuetype()); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); + + std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + lambda->name(); + + llvm::StructType * closure_lvtype + = llvm::StructType::create(llvm_cx->llvm_cx_ref(), + member_lvtype_v, + llvm::StringRef(closure_name), + false /*!is_packed*/); + + return closure_lvtype; + } /*create_closure_lvtype*/ + } /*namespace jit*/ } /*namespace xo*/ From 56b924a286ce277db51fb4cc23e220ac761d7c87 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:46:18 -0400 Subject: [PATCH 07/14] xo-jit: gen lvtype name in type2llvm::create_localenv_llvm_type() --- src/jit/type2llvm.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index a5405f86..231d6de4 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -277,6 +277,8 @@ namespace xo { type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, xo::ref::brw lambda) { + constexpr const char * c_prefix = "e."; + 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); @@ -294,13 +296,16 @@ namespace xo { } } - /* this type doesn't need a name, right? would be "_" + lambda name + "_localenv" */ - llvm::StructType * localenv_llvm_type + /* e.g. "e.foo" */ + std::string env_name = std::string(c_prefix) + lambda->name(); + + llvm::StructType * localenv_lvtype = llvm::StructType::get(llvm_cx->llvm_cx_ref()); - localenv_llvm_type->setBody(member_llvm_type_v, false /*!is_packed*/); + localenv_lvtype->setName(env_name); + localenv_lvtype->setBody(member_llvm_type_v, false /*!is_packed*/); - return localenv_llvm_type; + return localenv_lvtype; } /*create_localenv_llvm_type*/ llvm::StructType * From 19d8a5e846d5b09a2b787b05bdb413963bfc2e40 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:47:03 -0400 Subject: [PATCH 08/14] xo-jit: doc: + glossary entries --- docs/glossary.rst | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index c5a22ec1..37b98af9 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -4,6 +4,49 @@ Glossary -------- .. glossary:: + c.foo + | llvm typename for automatically-generated closure type for a lambda + | with name `foo`. + + e.foo + | llvm typename for automatically-generated local environment for a + | lambda with name `foo`. + + w.foo + | llvm typename for automatically-generated wrapper function for a + | primitive function `foo`. The wrapper function accepts and ignores + | an extra initial argument supplying an environment pointer. + | + | We apply this practice so that lambdas and primitives support the + | same ABI, so that we can support pointers to abstract functions + | that might at runtime turn out to be either primitives or lambdas + + lambda + | Common use is for lambda to refer to an anonymous function. + | In llvm we need all functions to be named, and those names + | have to be unique. + | + | Since all functions have to be named, we cheerfully adopt + | the oxymoron 'named lambda' + | + | Practices: + | 1. Automatically generate unique names for anonymous lambdas. + | 2. Incorporate user-provided names for convenience, when provided. + | 3. Still have to uniqueify names for user-provided nested lambdas. + + localenv + | Shorthand for local environment. + | Represents an explicit runtime repsentation for a struct that + | holds captured function arguments with the ability to be persisted + | (for example, moved to the the heap). + | + | Note that library `xo-expression` also uses the term environment, but differently. + | In that context describes all function arguments. + + lvtype + | Shorthand for `llvm::Type`: + | llvm-owned representation for a datatype + xsession | Shorthand for `llvm::orc::ExecutionSession`. | Manages running JIT-generated machine code in the host process From 659c0c400b3fe5478d2689bb78078a46260b6005 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:06 -0400 Subject: [PATCH 09/14] xo-jit: refactor: + closures [wip: not tested] --- include/xo/jit/MachPipeline.hpp | 37 +++- include/xo/jit/type2llvm.hpp | 103 ++++++----- src/jit/MachPipeline.cpp | 297 ++++++++++++++++++-------------- src/jit/activation_record.cpp | 129 +++++++------- src/jit/type2llvm.cpp | 90 +++++----- 5 files changed, 376 insertions(+), 280 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ed81c6c8..4e0714e9 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -121,16 +121,45 @@ namespace xo { llvm::Function * codegen_primitive_wrapper(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); + /** Generate closure for invoking a primitive function. + * Primitives don't benefit from a closure, but we need a consistent ABI + * to support function-pointer-like behavior for a target function + * that may resolve to primitive-or-lambda at runtime + **/ + llvm::Value * codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_apply(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); /* NOTE: codegen_lambda() needs to be reentrant too. * for example can have a lambda in apply position. */ llvm::Function * codegen_lambda_decl(ref::brw expr); llvm::Function * codegen_lambda_defn(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_variable(ref::brw var, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_ifexpr(ref::brw ifexpr, llvm::IRBuilder<> & ir_builder); + /** Generate closure for invoking a lambda (user-defined function). + * See @ref MachPipeline::codegen_apply for invocation + * Same ABI as @ref MachPipeline::codegen_primitive_closure + * + * @param envptr. Environment from surrounding lexical scope. + * This will be captured as envptr member by + * the IR code for creating a closure. + * @ref MachPipeline::codegen_toplevel and friends are responsible for + * assembling and propagating this. + **/ + llvm::Value * codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); llvm::Value * codegen_toplevel(ref::brw expr); diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 75f33576..d7a04f05 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -17,6 +17,7 @@ namespace xo { **/ struct type2llvm { public: + using FunctionInterface = xo::ast::FunctionInterface; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; @@ -57,11 +58,55 @@ namespace xo { * unwind_fn [1] | o-------> env * (*)(env*, ctl) * +-------+ * - * @return struct type. typename will be @c c.foo for lambda with name @c foo + * @return struct type. typename will be @c c.foo for a function + * (primitive or lambda) with name @c foo **/ static llvm::StructType * - create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda); + create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn); + + /** establish llvm abstract representation for a closure: + * struct with + * - [0] function pointer + * - [1] runtime localenv pointer + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * 1. for primitives, localenv will be null pointer + * 2. for lambdas L with L->requires_closure_flag() = false, + * localenv will also be null pointer + * 3. for lambdas with L->requires_closure_flag() = true, + * + * localenv will (for lambdas requiring closures) + * in practice be struct: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * Implementation here will just use generic pointer for runtime + * localenv. + **/ + static llvm::StructType * + function_td_to_closureapi_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + const std::string & hint_name); /** establish llvm concrete representation for a particular lambda's * runtime local environment: @@ -128,9 +173,17 @@ namespace xo { private: /** establish llvm representation for a function-pointer type * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime **/ static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ @@ -164,48 +217,6 @@ namespace xo { static llvm::StructType * env_api_llvm_type(xo::ref::brw 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*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index fc44ae88..b458f9fb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -361,8 +361,29 @@ namespace xo { return wrap_lvfn; } /*codegen_primitive_wrapper*/ + llvm::Value * + MachPipeline::codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); + + llvm::Function * pm_wrapper = codegen_primitive_wrapper(expr, ir_builder); + llvm::Value * env_0ptr = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx_)); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + pm_wrapper, {0}, "wrapfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + env_0ptr, {1}, "nullenvptr" /*name*/); + + return lv_closure; + } /*codegen_primitive_closure*/ + llvm::Value * MachPipeline::codegen_apply(ref::brw apply, + llvm::Value * envptr, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; @@ -376,14 +397,14 @@ namespace xo { using std::cerr; using std::endl; - /* IR for value in function position. - * Although it will generate a function (or pointer-to-function), - * it need not have inherited type llvm::Function. + /* IR for closure in function position + * see: + * - MachPipeline::codegen_primitive_closure + * - MachPipeline::codegen_lambda_closure + * - type2llvm::create_closure_lvtype */ - llvm::Value * llvm_fnval = nullptr; + llvm::Value * llvm_closure = nullptr; llvmintrinsic intrinsic = llvmintrinsic::invalid; - /* function type in apply node's function position */ - TypeDescr ast_fn_td = apply->fn()->valuetype(); { /* special treatement for primitive in apply position: * allows substituting LLVM intrinsic @@ -392,12 +413,12 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_fnval = this->codegen_primitive(pm); + llvm_closure = this->codegen_primitive(pm); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } } else { - llvm_fnval = this->codegen(apply->fn(), ir_builder); + llvm_closure = this->codegen(apply->fn(), envptr, ir_builder); /* we don't need any special checking here. * already know (from xo-level checking) that pointer has the right type. @@ -414,10 +435,13 @@ namespace xo { } } - if (!llvm_fnval) { + if (!llvm_closure) { return nullptr; } + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + #ifdef NOT_USING_DEBUG cerr << "MachPipeline::codegen_apply: fn:" << endl; fn->print(llvm::errs()); @@ -452,12 +476,50 @@ namespace xo { } #endif + llvm::StructType * closure_lvtype + = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, + ast_fn_td, + "" /*name - not required*/); + + llvm::Value * lv_fnptr = nullptr; + { + llvm::Value * fnptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + + std::array index_v = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + + lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + + llvm::Value * lv_fnenvptr = nullptr; + { + llvm::Value * envptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 1 /*value*/)); + + std::array index_v = {{envptr_slot /*envptr slot = closure[1]*/}}; + + lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + std::vector args; - args.reserve(apply->argv().size()); + /* +1 for envptr */ + args.reserve(1 + apply->argv().size()); + + + /* we must take envptr from closure, + * and we need to do this using some version of getelementptr + */ + args.push_back(lv_fnenvptr); int i = 0; for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, ir_builder); + auto * arg = this->codegen(arg_expr, envptr, ir_builder); if (log) { /* TODO: print helper for llvm::Value* */ @@ -476,26 +538,28 @@ namespace xo { /* if we have an intrinsic hint, * then instead of invoking a function, * we use some native machine instruction instead. + * + * args[0] not used here, that holds envptr from faux closure */ switch(intrinsic) { case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[0]); + return ir_builder.CreateNeg(args[1]); case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[0], args[1]); + return ir_builder.CreateAdd(args[1], args[2]); case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[0], args[1]); + return ir_builder.CreateSub(args[1], args[2]); case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[0], args[1]); + return ir_builder.CreateMul(args[1], args[2]); case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[0], args[1]); + return ir_builder.CreateSDiv(args[1], args[2]); case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[0], args[1]); + return ir_builder.CreateUDiv(args[1], args[2]); case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[0], args[1]); + return ir_builder.CreateFAdd(args[1], args[2]); case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[0], args[1]); + return ir_builder.CreateFMul(args[1], args[2]); case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[0], args[1]); + return ir_builder.CreateFDiv(args[1], args[2]); case llvmintrinsic::invalid: case llvmintrinsic::fp_sqrt: case llvmintrinsic::fp_pow: @@ -506,65 +570,18 @@ namespace xo { break; } - /* At least as of 18.1.5, LLVM needs us to supply function type - * when making a function call. In particular it doesn't remember - * the function type with each function pointer - */ - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, - llvm_fnval, + lv_fnptr, args, "calltmp"); } /*codegen_apply*/ -#ifdef OBSOLETE - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, - const std::string & var_name, - TypeDescr var_type) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx_.borrow(), - var_type); - - log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); - if (log) { - std::string llvm_var_type_str; - llvm::raw_string_ostream ss(llvm_var_type_str); - llvm_var_type->print(ss); - - log(xtag("llvm_var_type", llvm_var_type_str)); - } - - if (!llvm_var_type) - return nullptr; - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_block_alloca*/ -#endif - - std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -600,24 +617,40 @@ namespace xo { /* establish prototype for this function */ + /* wrapper_flag: llvm function type takes extra first argument, + * supplying environment pointer from surrounding closure. + * + * Note that this argument is not present in lambda, + * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] + */ llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + lambda->valuetype(), + true /*wrapper_flag*/); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); - /* also capture argument names */ + + /* also adopt lambda's formal argument names */ { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm formal param names", - xtag("i", i), - xtag("param", lambda->argv().at(i))); + if (i == 0) { + log && log("llvm inserted env param", + xtag("i", i)); + + arg.setName(".env"); + } else { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i-1))); + + arg.setName(lambda->argv().at(i-1)->name()); + } - arg.setName(lambda->argv().at(i)->name()); ++i; } } @@ -648,6 +681,10 @@ namespace xo { return nullptr; } + /* environment for this lambda's clsoure + * passed as extra 1st argument + */ + llvm::Value * envptr = llvm_fn->args().begin(); /* generate function body */ @@ -667,45 +704,9 @@ namespace xo { return nullptr; } -#ifdef OBSOLETE - { - log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); - - int i = 0; - for (auto & arg : llvm_fn->args()) { - log && log("nested environment", - xtag("i", i), - xtag("param", std::string(arg.getName()))); - - std::string arg_name = std::string(arg.getName()); - - /* stack location for arg[i] */ - llvm::AllocaInst * alloca - = create_entry_block_alloca(llvm_fn, - arg_name, - lambda->fn_arg(i)); - - if (!alloca) { - this->env_stack_.pop(); - return nullptr; - } - - /* store on function entry - * see codegen_variable() for corresponding load - */ - ir_builder.CreateStore(&arg, alloca); - - /* remember stack location for reference + assignment - * in lambda body. - * - */ - env_stack_.top().alloc_var(i, arg_name, alloca); - ++i; - } - } -#endif - - llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + llvm::Value * retval = this->codegen(lambda->body(), + envptr, + ir_builder); if (retval) { /* completes the function.. */ @@ -746,10 +747,33 @@ namespace xo { return llvm_fn; } /*codegen_lambda_defn*/ + llvm::Value * + MachPipeline::codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); + + llvm::Function * lvfn = codegen_lambda_decl(lambda); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + lvfn, {0}, "lmfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + envptr, {1}, "envptr" /*name*/); + + return lv_closure; + } /*codegen_lambda_closure*/ + llvm::Value * MachPipeline::codegen_variable(ref::brw var, + llvm::Value * /*envptr*/, llvm::IRBuilder<> & ir_builder) { + /* TODO: navigate envptr to handle non-local variables */ + if (env_stack_.empty()) { cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) @@ -772,9 +796,11 @@ namespace xo { } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen_ifexpr(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + llvm::Value * test_ir = this->codegen(expr->test(), envptr, ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir @@ -813,6 +839,7 @@ namespace xo { ir_builder.SetInsertPoint(when_true_bb); llvm::Value * when_true_ir = this->codegen(expr->when_true(), + envptr, ir_builder); if (!when_true_ir) @@ -827,7 +854,9 @@ namespace xo { parent_fn->insert(parent_fn->end(), when_false_bb); ir_builder.SetInsertPoint(when_false_bb); - llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + llvm::Value * when_false_ir = this->codegen(expr->when_false(), + envptr, + ir_builder); if (!when_false_ir) return nullptr; @@ -852,21 +881,24 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { case exprtype::constant: return this->codegen_constant(ConstantInterface::from(expr)); case exprtype::primitive: - return this->codegen_primitive(PrimitiveInterface::from(expr)); + return this->codegen_primitive_closure(PrimitiveInterface::from(expr), ir_builder); case exprtype::apply: - return this->codegen_apply(Apply::from(expr), ir_builder); + return this->codegen_apply(Apply::from(expr), envptr, ir_builder); case exprtype::lambda: - return this->codegen_lambda_decl(Lambda::from(expr)); + return this->codegen_lambda_closure(Lambda::from(expr), envptr, ir_builder); + //return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr), ir_builder); + return this->codegen_variable(Variable::from(expr), envptr, ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + return this->codegen_ifexpr(IfExpr::from(expr), envptr, ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -910,6 +942,8 @@ namespace xo { this->codegen_lambda_decl(lambda); } +#ifdef OBSOLETE /* don't do this anymore, obscures lexical context */ + /* Pass 2 */ for (auto lambda : fn_v) { this->codegen_lambda_defn(lambda, @@ -931,6 +965,19 @@ namespace xo { return this->codegen(expr, *(this->llvm_toplevel_ir_builder_.get())); } +#endif + + /* 1. using nullptr as runtime representation for global environment + * 2. may have to elaborate this later? not clear to me + */ + + llvm::Value * env_0ptr + = (llvm::ConstantPointerNull::get + (type2llvm::env_api_llvm_ptr_type(llvm_cx_))); + + return this->codegen(expr, + env_0ptr, + *(this->llvm_toplevel_ir_builder_.get())); } /*codegen_toplevel*/ void diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 413412af..be1275ef 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -155,7 +155,7 @@ namespace xo { = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), llvm::APInt(32 /*bits*/, i_slot /*value*/)); - std::array index_v = { + std::array index_v = { {i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr @@ -194,37 +194,44 @@ namespace xo { { int i_arg = 0; for (auto & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("stackonly(i)", binding_v_[i_arg].is_stackonly())); - - if (binding_v_[i_arg].is_stackonly()) { - /* stack location for arg[i] */ - runtime_binding_detail binding - = create_entry_block_alloca(llvm_cx, - //data_layout, - llvm_fn, - tmp_ir_builder, - i_arg, - arg_name, - lambda_->fn_arg(i_arg)); - - if (!binding.llvm_addr_) - return false; - - /* store on function entry - * see codegen_variable() for corresponding load + if (i_arg == 0) { + /* 1st argument is injected environment pointer. + * we don't need that to be on the stack, + * since not modifiable and not user-referencable. */ - ir_builder.CreateStore(&arg, binding.llvm_addr_); + } else { + std::string arg_name = std::string(arg.getName()); - /* remember stack location for reference + assignment - * in lambda body. - * - */ - this->alloc_var(arg_name, binding); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("stackonly(i)", binding_v_[i_arg-1].is_stackonly())); + + if (binding_v_[i_arg-1].is_stackonly()) { + /* stack location for arg[i] */ + runtime_binding_detail binding + = create_entry_block_alloca(llvm_cx, + //data_layout, + llvm_fn, + tmp_ir_builder, + i_arg, + arg_name, + lambda_->fn_arg(i_arg)); + + if (!binding.llvm_addr_) + return false; + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, binding.llvm_addr_); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + this->alloc_var(arg_name, binding); + } } ++i_arg; @@ -297,7 +304,7 @@ namespace xo { int i_localenv_slot = 0; /* store localenv->parent_env */ - { + { llvm::Value * slot_addr = runtime_localenv_slot_addr(llvm_cx, localenv_llvm_type, @@ -348,42 +355,48 @@ namespace xo { int i_arg = 0; for (llvm::Argument & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("captured(i)", binding_v_[i_arg].is_captured())); - - if (binding_v_[i_arg].is_captured()) { - // do something with runtime-local-env for this llvm_fn - - /* remember stack location for reference + assignment - * in lambda body. - * + if (i_arg == 0) { + /* to remove all doubt, ignore first arg here. + * it's non-captureable environment pointer */ + } else { + std::string arg_name = std::string(arg.getName()); - TypeDescr arg_td = lambda_->fn_arg(i_arg); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("captured(i)", binding_v_[i_arg-1].is_captured())); - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + if (binding_v_[i_arg-1].is_captured()) { + // do something with runtime-local-env for this llvm_fn - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - tmp_ir_builder); + /* remember stack location for reference + assignment + * in lambda body. + * + */ - if (!slot_addr) - return false; + TypeDescr arg_td = lambda_->fn_arg(i_arg-1); - ++i_localenv_slot; + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); - tmp_ir_builder.CreateStore(&arg, slot_addr); + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + tmp_ir_builder); - runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + if (!slot_addr) + return false; - this->alloc_var(arg_name, binding); + ++i_localenv_slot; + + tmp_ir_builder.CreateStore(&arg, slot_addr); + + runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + + this->alloc_var(arg_name, binding); + } } ++i_arg; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 231d6de4..6a982b89 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -26,7 +26,8 @@ namespace xo { /* in this context, we're looking for a representation for a value, * i.e. something that can be stored in a variable */ - return function_td_to_llvm_fnptr_type(llvm_cx, td); + //return function_td_to_llvm_fnptr_type(llvm_cx, td); + return function_td_to_closureapi_lvtype(llvm_cx, td, ""); } else if (td->is_struct()) { return struct_td_to_llvm_type(llvm_cx, td); } else if (td->is_pointer()) { @@ -95,9 +96,10 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, wrapper_flag); /** like C: llvm IR doesn't support function-valued variables; * it does however support pointer-to-function-valued variables @@ -227,7 +229,7 @@ namespace xo { /* _env_api: base type for a local environment */ llvm::StructType * env_llvm_type = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_env_api"); + "_env_api"); /* _env_api[0]: pointer to a local environment */ llvm::PointerType * envptr_llvm_type @@ -253,25 +255,48 @@ namespace xo { return llvm::PointerType::getUnqual(env_llvm_type); } /*env_api_llvm_ptr_type*/ -#ifdef NOT_USING llvm::StructType * - type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + type2llvm::create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn) { - constexpr bool c_debug_flag = true; - using xo::scope; + constexpr const char * c_prefix = "c."; - scope log(XO_DEBUG(c_debug_flag)); + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + fn->name(); - /* closure type doesn't need a name. - * (We might find it convenient to give one anyway) + return function_td_to_closureapi_lvtype(llvm_cx, + fn->valuetype(), + closure_name); + } /*create_closureapi_lvtype*/ + + llvm::StructType * + type2llvm::function_td_to_closureapi_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + const std::string & hint_name) + { + /* would be precisely correct to use create_localenv_llvm_type() + * here. However judged not sufficiently helpful. + * Would still + * need environment cast whenever closure in apply position is + * not known at compile time. */ - llvm::StructType * closure_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, + fn_td, + true /*wrapper_flag*/); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - llvm::PointerType * parent_llvm_type - } /*function_td_to_llvm_fnptr_type*/ -#endif + std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + + llvm::StructType * closure_lvtype + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); + + //closure_lvtype->setBody(member_lvtype_v); + + if (!hint_name.empty()) + closure_lvtype->setName(llvm::StringRef(hint_name)); + + return closure_lvtype; + } /*function_td_to_closureapi_lvtype*/ llvm::StructType * type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, @@ -289,7 +314,7 @@ namespace xo { for (const auto & var : lambda->argv()) { if (lambda->is_captured(var->name())) { - /* var needs a slot in localenv_llvm_type for lambda */ + /* var is captured -> needs a slot in the localenv_llvm_type belonging to this lambda */ member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, var->valuetype())); @@ -308,35 +333,6 @@ namespace xo { return localenv_lvtype; } /*create_localenv_llvm_type*/ - llvm::StructType * - type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda) - { - constexpr const char * c_prefix = "c."; - - /* would be precisely correct to use create_localenv_llvm_type() - * here. However judged not sufficiently helpful. - * Would still - * need environment cast whenever closure in apply position is - * not known at compile time. - */ - llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, - lambda->valuetype()); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; - - /* e.g. "c.foo" */ - std::string closure_name = std::string(c_prefix) + lambda->name(); - - llvm::StructType * closure_lvtype - = llvm::StructType::create(llvm_cx->llvm_cx_ref(), - member_lvtype_v, - llvm::StringRef(closure_name), - false /*!is_packed*/); - - return closure_lvtype; - } /*create_closure_lvtype*/ } /*namespace jit*/ } /*namespace xo*/ From 26a055eb1c526c82e468f43437829da88c472eeb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:37 -0400 Subject: [PATCH 10/14] xo-jit: docs: ++ ABI in glossary --- docs/glossary.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index 37b98af9..6af7073a 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -4,6 +4,10 @@ Glossary -------- .. glossary:: + ABI + | Short for Application Binary Interface. + | In this context applies to conventions adopted by `xo-jit`. + c.foo | llvm typename for automatically-generated closure type for a lambda | with name `foo`. From 09f5c141dfd9eaf8561d6472a1f10a31cc8c5470 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:05:00 -0400 Subject: [PATCH 11/14] xo-jit: fnptr -> closures for primitives+lambdas throughout --- include/xo/jit/activation_record.hpp | 11 ++ include/xo/jit/type2llvm.hpp | 33 ++-- src/jit/MachPipeline.cpp | 233 +++++++++++++++++---------- src/jit/activation_record.cpp | 39 ++++- src/jit/type2llvm.cpp | 92 +++++++++-- utest/MachPipeline.test.cpp | 11 +- 6 files changed, 295 insertions(+), 124 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 82763e6c..e3eb7a66 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -68,6 +68,17 @@ namespace xo { llvm::Type * llvm_type_ = nullptr; }; + inline std::ostream & + operator<<(std::ostream & os, const runtime_binding_detail & x) { + os << ""; + + return os; + } + /** * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index d7a04f05..35a6e739 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -38,9 +38,23 @@ namespace xo { * to either {primitive functions, functions-requiring-closures}, * with choice deferred until runtime **/ - static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag = false); + static llvm::FunctionType * function_td_to_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag = false); + + /** establish llvm representation for a function-pointer type + * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime + **/ + static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm concrete representation for a closure. * @@ -171,19 +185,6 @@ namespace xo { llvm::PointerType * hint_envptr_llvm_type = nullptr); private: - /** establish llvm representation for a function-pointer type - * described by @p fn_td - * - * @param wrapper_flag If true, create function type for a wrapper - * to be associated with a closure. - * The wrapper accepts (and ignores) an envapi pointer as first argument. - * Necessary to (for example) support function pointers that may refer - * to either {primitive functions, functions-requiring-closures}, - * with choice deferred until runtime - **/ - static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index b458f9fb..671f38fa 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -192,7 +192,7 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td); if (!llvm_fn_type) return nullptr; @@ -251,7 +251,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive_wrapper(ref::brw expr, - llvm::IRBuilder<> & ir_builder) + llvm::IRBuilder<> & /*ir_builder*/) { constexpr bool c_debug_flag = true; @@ -266,7 +266,7 @@ namespace xo { std::string wrap_name = std::string(c_prefix) + expr->name(); /* original primitive */ - auto * native_lvfn = codegen_primitive(expr); + auto * native_lvfn = this->codegen_primitive(expr); /* wrapped primitive */ auto * wrap_lvfn = llvm_module_->getFunction(wrap_name); @@ -279,13 +279,15 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * native_lvtype - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = 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_llvm_type(llvm_cx_.borrow(), + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td, true /*wrapper_flag (for closure)*/); @@ -301,7 +303,11 @@ namespace xo { auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", wrap_lvfn); - ir_builder.SetInsertPoint(block); + /* 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; @@ -323,15 +329,15 @@ namespace xo { /* {caller,callee} must agree on calling convention, * so for primitives we need to assume c. */ - llvm::CallInst * call = ir_builder.CreateCall(native_lvtype, - native_lvfn, - args, - "w.calltmp"); + 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? */ - ir_builder.CreateRet(call); + tmp_ir_builder.CreateRet(call); llvm::verifyFunction(*wrap_lvfn); @@ -365,6 +371,9 @@ namespace xo { MachPipeline::codegen_primitive_closure(ref::brw 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); @@ -402,6 +411,8 @@ namespace xo { * - 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; @@ -413,7 +424,7 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_closure = this->codegen_primitive(pm); + llvm_closure = this->codegen_primitive_closure(pm, ir_builder); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } @@ -442,69 +453,93 @@ namespace xo { /* function type in apply node's function position */ TypeDescr ast_fn_td = apply->fn()->valuetype(); -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif + 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 REDUNDANT_TYPECHECK - if (apply->argv().size() != ast_fn_td->n_fn_arg()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - //<< xtag("f", ast_fn->name()) - << xtag("n1", ast_fn_td->n_fn_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" - << xtag("F", apply->fn()) - << xtag("I", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", ast_fn_td->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } -#endif - +#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 = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + std::array index_v + = {{i0_slot, + fnptr_slot /*fnptr slot = closure[0]*/}}; - lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, - llvm_closure, - index_v); + 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 = {{envptr_slot /*envptr slot = closure[1]*/}}; + std::array index_v + = {{i0_slot, + envptr_slot /*envptr slot = closure[1]*/}}; - lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, - llvm_closure, - index_v); + 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; @@ -524,8 +559,13 @@ namespace xo { if (log) { /* TODO: print helper for llvm::Value* */ std::string llvm_value_str; - llvm::raw_string_ostream ss(llvm_value_str); - arg->print(ss); + + 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)); @@ -533,6 +573,14 @@ namespace xo { 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, @@ -571,9 +619,9 @@ namespace xo { } llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, - ast_fn_td, - true /*wrapper_flag*/); + = type2llvm::function_td_to_lvtype(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, lv_fnptr, @@ -623,13 +671,13 @@ namespace xo { * Note that this argument is not present in lambda, * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] */ - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype(), - true /*wrapper_flag*/); + 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(llvm_fn_type, + fn = llvm::Function::Create(fn_lvtype, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); @@ -660,7 +708,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, - llvm::IRBuilder<> & ir_builder) + llvm::IRBuilder<> & /*ir_builder*/) { constexpr bool c_debug_flag = true; @@ -690,14 +738,19 @@ namespace xo { auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - ir_builder.SetInsertPoint(block); + /* 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, ir_builder); + bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, tmp_ir_builder); if (!ok_flag) { this->env_stack_.pop(); @@ -706,11 +759,11 @@ namespace xo { llvm::Value * retval = this->codegen(lambda->body(), envptr, - ir_builder); + tmp_ir_builder); if (retval) { /* completes the function.. */ - ir_builder.CreateRet(retval); + tmp_ir_builder.CreateRet(retval); /* validate! always validate! */ llvm::verifyFunction(*llvm_fn); @@ -742,7 +795,9 @@ namespace xo { this->env_stack_.pop(); - log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + 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*/ @@ -755,14 +810,21 @@ namespace xo { llvm::StructType * closure_lvtype = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); - llvm::Function * lvfn = codegen_lambda_decl(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}, "envptr" /*name*/); + { + 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*/ @@ -836,44 +898,45 @@ namespace xo { when_false_bb); /* populate when_true_bb */ - ir_builder.SetInsertPoint(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, - ir_builder); + tmp_ir_builder); if (!when_true_ir) return nullptr; /* at end of when-true sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); + tmp_ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = ir_builder.GetInsertBlock(); + when_true_bb = tmp_ir_builder.GetInsertBlock(); /* populate when_false_bb */ parent_fn->insert(parent_fn->end(), when_false_bb); - ir_builder.SetInsertPoint(when_false_bb); + tmp_ir_builder.SetInsertPoint(when_false_bb); llvm::Value * when_false_ir = this->codegen(expr->when_false(), envptr, - ir_builder); + tmp_ir_builder); if (!when_false_ir) return nullptr; /* at end of when-false sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); + tmp_ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = ir_builder.GetInsertBlock(); + when_false_bb = tmp_ir_builder.GetInsertBlock(); /* merged suffix sequence */ parent_fn->insert(parent_fn->end(), merge_bb); - ir_builder.SetInsertPoint(merge_bb); + tmp_ir_builder.SetInsertPoint(merge_bb); /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node - = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); + = 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); diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index be1275ef..7b1228e6 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -33,7 +33,12 @@ namespace xo { } /*ctor*/ const runtime_binding_detail * - activation_record::lookup_var(const std::string & x) const { + activation_record::lookup_var(const std::string & x) const + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); auto ix = frame_.find(x); @@ -41,6 +46,10 @@ namespace xo { cerr << "activation_record::lookup_var: no binding for variable x" << xtag("x", x) << endl; + cerr << "frame:"; + for (const auto & ix : frame_) + cerr << xtag("var", ix.first) << xtag("->", ix.second) << endl; + return nullptr; } @@ -51,6 +60,14 @@ namespace xo { activation_record::alloc_var(const std::string & x, const runtime_binding_detail & binding) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("var", x), + xtag("binding", binding)); + if (frame_.find(x) != frame_.end()) { cerr << "activation_record::alloc_var: variable x already present in frame" << xtag("x", x) @@ -76,10 +93,12 @@ namespace xo { 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())); + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("llvm_fn", (void*)llvm_fn), + xtag("i_arg", i_arg), + 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); @@ -151,12 +170,16 @@ namespace xo { #endif llvm::IRBuilder<> & tmp_ir_builder) { + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0)); + 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*/}}; + std::array index_v = { + {i0_slot, i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr = tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type, @@ -216,7 +239,7 @@ namespace xo { tmp_ir_builder, i_arg, arg_name, - lambda_->fn_arg(i_arg)); + lambda_->fn_arg(i_arg-1)); if (!binding.llvm_addr_) return false; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 6a982b89..e5c07cac 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -58,20 +58,29 @@ namespace xo { * that represented by @p fn_td **/ llvm::FunctionType * - type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) + type2llvm::function_td_to_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag) { - int n_fn_arg = fn_td->n_fn_arg(); + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG(c_debug_flag)); + + int n_ast_fn_arg = fn_td->n_fn_arg(); + + if (log) { + log(xtag("fn_td", fn_td->short_name())); + log(xtag("n_ast_fn_arg", n_ast_fn_arg)); + } std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg + (wrapper_flag ? 1 : 0)); + llvm_argtype_v.reserve(n_ast_fn_arg + (wrapper_flag ? 1 : 0)); if (wrapper_flag) llvm_argtype_v.push_back(env_api_llvm_ptr_type(llvm_cx)); /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { + for (int i = 0; i < n_ast_fn_arg; ++i) { TypeDescr arg_td = fn_td->fn_arg(i); llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td); @@ -79,12 +88,26 @@ namespace xo { if (!llvm_argtype) return nullptr; + if (log) { + log(xtag("arg_td", arg_td->short_name())); + log(xtag("llvm_argtype", "...")); + llvm_argtype->dump(); + log("...done"); + } + 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 (log) { + log(xtag("retval_td", retval_td->short_name())); + log(xtag("llvm_retval", "...")); + llvm_retval->dump(); + log("...done"); + } + if (!llvm_retval) return nullptr; @@ -96,17 +119,23 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) + TypeDescr /*fn_td*/, + bool /*wrapper_flag*/) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, wrapper_flag); +#ifdef OBSOLETE + auto * llvm_fn_type = function_td_to_lvtype(llvm_cx, fn_td, wrapper_flag); +#endif /** 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::getUnqual(llvm_cx->llvm_cx_ref()); +#ifdef OBSOLETE auto * llvm_ptr_type = llvm::PointerType::get(llvm_fn_type, 0 /*numbered address space*/); +#endif return llvm_ptr_type; } @@ -181,20 +210,26 @@ namespace xo { { assert(pointer_td->is_pointer()); +#ifdef OBSOLETE 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); +#endif + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); 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) + llvm::PointerType * /*envptr_llvm_type*/) { +#ifdef OBSOLETE if (!envptr_llvm_type) envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); @@ -219,6 +254,9 @@ namespace xo { /* _env_api* (*)(_env_api*, i32) */ auto * unwind_llvm_fnptr_type = llvm::PointerType::getUnqual(unwind_llvm_type); +#endif + auto * unwind_llvm_fnptr_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); return unwind_llvm_fnptr_type; } /*require_localenv_unwind_llvm_fnptr_type*/ @@ -231,9 +269,13 @@ namespace xo { = llvm::StructType::get(llvm_cx->llvm_cx_ref(), "_env_api"); +#ifdef OBSOLETE /* _env_api[0]: pointer to a local environment */ llvm::PointerType * envptr_llvm_type = llvm::PointerType::getUnqual(env_llvm_type); +#endif + llvm::PointerType * envptr_llvm_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); /* _env_api[1]: unwwind/copy function */ llvm::PointerType * unwind_llvm_fnptr_type @@ -250,9 +292,11 @@ namespace xo { llvm::PointerType * type2llvm::env_api_llvm_ptr_type(xo::ref::brw llvm_cx) { +#ifdef OBSOLETE llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx); - return llvm::PointerType::getUnqual(env_llvm_type); +#endif + return llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); } /*env_api_llvm_ptr_type*/ llvm::StructType * @@ -274,6 +318,10 @@ namespace xo { TypeDescr fn_td, const std::string & hint_name) { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG(c_debug_flag)); + /* would be precisely correct to use create_localenv_llvm_type() * here. However judged not sufficiently helpful. * Would still @@ -283,9 +331,21 @@ namespace xo { llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, fn_td, true /*wrapper_flag*/); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); + if (log) { + log(xtag("fn_lvtype", "...")); + fn_lvtype->dump(); + log("...done"); + } - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + llvm::PointerType * envptr_lvtype = env_api_llvm_ptr_type(llvm_cx); + + if (log) { + log(xtag("env_lvtype", "...")); + envptr_lvtype->dump(); + log("...done"); + } + + std::vector member_lvtype_v = { fn_lvtype, envptr_lvtype }; llvm::StructType * closure_lvtype = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); @@ -295,6 +355,12 @@ namespace xo { if (!hint_name.empty()) closure_lvtype->setName(llvm::StringRef(hint_name)); + if (log) { + log(xtag("closure_lvtype", "...")); + closure_lvtype->dump(); + log("...done"); + } + return closure_lvtype; } /*function_td_to_closureapi_lvtype*/ diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 880aabf1..ad14ac32 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -116,12 +116,19 @@ namespace xo { //auto rng = xo::rng::xoshiro256ss(seed); - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline")); + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.fptr")); //log && log("(A)", xtag("foo", foo)); - auto jit = MachPipeline::make(); for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + /** can't share jit across examples, + * until we fix treatment of primitives: + * now that we build a wrapper for each primitive, + * need some bookkeeping to avoid trying to build + * the same wrapper twice. + **/ + auto jit = MachPipeline::make(); + TestCase const & testcase = s_testcase_v[i_tc]; INFO(tostr(xtag("i_tc", i_tc))); From 97d095a0556e19cdc12eab6f5e5aa5920273e461 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:09:35 -0400 Subject: [PATCH 12/14] xo-jit: ++ HOWTO --- HOWTO | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/HOWTO b/HOWTO index 6965105f..c3eed238 100644 --- a/HOWTO +++ b/HOWTO @@ -43,3 +43,30 @@ mp.machgen_current_module() fn=mp.lookup_fn('int (*)(int)', 'sq') fn(16) # -> 256 + +** to figure out what 'IR should look like' for something simple + + write some c++: + + #include + + struct env_type { + env_type * parent; + env_type * (*unwind)(env_type *, int); + }; + + double wrap_sqrt(env_type * env, double x) { + return ::sqrt(x); + } + + int main() { + wrap_sqrt(nullptr, 2.0); + } + + compile to emit IR + + $ clang -cc1 ex_cpp.cpp -emit-llvm + + inspect + + ex_cpp.ll From 0ee004cec685bca1bc5d267af473335d57900070 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:09:52 -0400 Subject: [PATCH 13/14] xo-jit: + example/ex_cpp --- example/ex_cpp/ex_cpp.cpp | 40 ++++++++++++++ example/ex_cpp/ex_cpp.ll | 108 ++++++++++++++++++++++++++++++++++++++ example/ex_cpp/tmp.ll | 58 ++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 example/ex_cpp/ex_cpp.cpp create mode 100644 example/ex_cpp/ex_cpp.ll create mode 100644 example/ex_cpp/tmp.ll diff --git a/example/ex_cpp/ex_cpp.cpp b/example/ex_cpp/ex_cpp.cpp new file mode 100644 index 00000000..65ce9934 --- /dev/null +++ b/example/ex_cpp/ex_cpp.cpp @@ -0,0 +1,40 @@ +struct env_type; + +struct closure_type { + double (*fnptr)(env_type * env, double x); + env_type * envptr; +}; + +double +sqrt(double x) { + return x/100; +} + +double +wrap_sqrt(env_type * env, double x) { + return ::sqrt(x); +} + +double twice(env_type * env, closure_type fnclosure, double x) { + double tmp1 = (*fnclosure.fnptr)(fnclosure.envptr, x); + double tmp2 = (*fnclosure.fnptr)(fnclosure.envptr, tmp1); + + return tmp2; +} + +closure_type make_some_closure() +{ + closure_type closure; + closure.fnptr = &wrap_sqrt; + closure.envptr = nullptr; + + return closure; +} + +int main() { + closure_type closure = make_some_closure(); + + double y = twice(nullptr, closure, 4.0); + + //std::cout << "y=" << y << std::endl; +} diff --git a/example/ex_cpp/ex_cpp.ll b/example/ex_cpp/ex_cpp.ll new file mode 100644 index 00000000..28434d62 --- /dev/null +++ b/example/ex_cpp/ex_cpp.ll @@ -0,0 +1,108 @@ +; ModuleID = 'ex_cpp.cpp' +source_filename = "ex_cpp.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%struct.closure_type = type { ptr, ptr } + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local noundef double @_Z4sqrtd(double noundef %x) #0 { +entry: + %x.addr = alloca double, align 8 + store double %x, ptr %x.addr, align 8 + %0 = load double, ptr %x.addr, align 8 + %div = fdiv double %0, 1.000000e+02 + ret double %div +} + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local noundef double @_Z9wrap_sqrtP8env_typed(ptr noundef %env, double noundef %x) #0 { +entry: + %env.addr = alloca ptr, align 8 + %x.addr = alloca double, align 8 + store ptr %env, ptr %env.addr, align 8 + store double %x, ptr %x.addr, align 8 + %0 = load double, ptr %x.addr, align 8 + %call = call noundef double @_Z4sqrtd(double noundef %0) + ret double %call +} + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef %env, ptr %fnclosure.coerce0, ptr %fnclosure.coerce1, double noundef %x) #0 { +entry: + %fnclosure = alloca %struct.closure_type, align 8 + %env.addr = alloca ptr, align 8 + %x.addr = alloca double, align 8 + %tmp1 = alloca double, align 8 + %tmp2 = alloca double, align 8 + %0 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 0 + store ptr %fnclosure.coerce0, ptr %0, align 8 + %1 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 1 + store ptr %fnclosure.coerce1, ptr %1, align 8 + store ptr %env, ptr %env.addr, align 8 + store double %x, ptr %x.addr, align 8 + %fnptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0 + %2 = load ptr, ptr %fnptr, align 8 + %envptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1 + %3 = load ptr, ptr %envptr, align 8 + %4 = load double, ptr %x.addr, align 8 + %call = call noundef double %2(ptr noundef %3, double noundef %4) + store double %call, ptr %tmp1, align 8 + %fnptr1 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0 + %5 = load ptr, ptr %fnptr1, align 8 + %envptr2 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1 + %6 = load ptr, ptr %envptr2, align 8 + %7 = load double, ptr %tmp1, align 8 + %call3 = call noundef double %5(ptr noundef %6, double noundef %7) + store double %call3, ptr %tmp2, align 8 + %8 = load double, ptr %tmp2, align 8 + ret double %8 +} + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local { ptr, ptr } @_Z17make_some_closurev() #0 { +entry: + %retval = alloca %struct.closure_type, align 8 + %fnptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 0 + store ptr @_Z9wrap_sqrtP8env_typed, ptr %fnptr, align 8 + %envptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 1 + store ptr null, ptr %envptr, align 8 + %0 = load { ptr, ptr }, ptr %retval, align 8 + ret { ptr, ptr } %0 +} + +; Function Attrs: mustprogress noinline norecurse nounwind optnone +define dso_local noundef i32 @main() #1 { +entry: + %closure = alloca %struct.closure_type, align 8 + %y = alloca double, align 8 + %agg.tmp = alloca %struct.closure_type, align 8 + %call = call { ptr, ptr } @_Z17make_some_closurev() + %0 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 0 + %1 = extractvalue { ptr, ptr } %call, 0 + store ptr %1, ptr %0, align 8 + %2 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 1 + %3 = extractvalue { ptr, ptr } %call, 1 + store ptr %3, ptr %2, align 8 + call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %closure, i64 16, i1 false) + %4 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 0 + %5 = load ptr, ptr %4, align 8 + %6 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 1 + %7 = load ptr, ptr %6, align 8 + %call1 = call noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef null, ptr %5, ptr %7, double noundef 4.000000e+00) + store double %call1, ptr %y, align 8 + ret i32 0 +} + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2 + +attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" } +attributes #1 = { mustprogress noinline norecurse nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" } +attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 18.1.5"} diff --git a/example/ex_cpp/tmp.ll b/example/ex_cpp/tmp.ll new file mode 100644 index 00000000..b8b8993a --- /dev/null +++ b/example/ex_cpp/tmp.ll @@ -0,0 +1,58 @@ +define double @root4(ptr %.env, double %x) { +entry: + %x1 = alloca double, align 8 + store double %x, ptr %x1, align 8 + %x2 = load double, ptr %x1, align 8 + %calltmp = call double @w.sqrt(ptr null, double %x2) + %calltmp3 = call double @w.sqrt(ptr null, double %calltmp) + ret double %calltmp3 +} + +; ---------------------------------------------------------------- + +define double @twice(ptr %.env, { ptr, ptr } %f, double %x) { +entry: + %f1 = alloca { ptr, ptr }, align 8 + store { ptr, ptr } %f, ptr %f1, align 8 + %x2 = alloca double, align 8 + store double %x, ptr %x2, align 8 + %f3 = load { ptr, ptr }, ptr %f1, align 8 + %fnptr = extractvalue { ptr, ptr } %f3, 0 + %envptr = extractvalue { ptr, ptr } %f3, 1 + %f4 = load { ptr, ptr }, ptr %f1, align 8 + %fnptr5 = extractvalue { ptr, ptr } %f4, 0 + %envptr6 = extractvalue { ptr, ptr } %f4, 1 + %x7 = load double, ptr %x2, align 8 + %calltmp = call double %fnptr5(ptr %envptr6, double %x7) + %calltmp8 = call double %fnptr(ptr %envptr, double %calltmp) + ret double %calltmp8 +} + + +define double @twice(ptr %.env, { ptr, ptr } %f, double %x) { +entry: + %f1 = alloca { ptr, ptr }, align 8 + %f.elt = extractvalue { ptr, ptr } %f, 0 + store ptr %f.elt, ptr %f1, align 8 + %f1.repack9 = getelementptr inbounds { ptr, ptr }, ptr %f1, i64 0, i32 1 + %f.elt10 = extractvalue { ptr, ptr } %f, 1 + store ptr %f.elt10, ptr %f1.repack9, align 8 + %calltmp = call double %f.elt(ptr %f.elt10, double %x) + %calltmp8 = call double %f.elt(ptr %f.elt10, double %calltmp) + ret double %calltmp8 +} + + +;; ---------------------------------------------------------------- + +define double @root_2x(ptr %.env, double %x2) { +entry: + %x21 = alloca double, align 8 + store double %x2, ptr %x21, align 8 + %envptr = insertvalue { ptr, ptr } { ptr @twice, ptr undef }, ptr %.env, 1 + %fnptr = extractvalue { ptr, ptr } %envptr, 0 + %envptr2 = extractvalue { ptr, ptr } %envptr, 1 + %x23 = load double, ptr %x21, align 8 + %calltmp = call double %fnptr(ptr %envptr2, { ptr, ptr } { ptr @w.sqrt, ptr null }, double %x23) + ret double %calltmp +} From 3fa4029bc222f70c5c05d64c0fe2303a4de26112 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:11:20 -0400 Subject: [PATCH 14/14] xo-jit: + example/ex_cpp/README --- example/ex_cpp/README | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 example/ex_cpp/README diff --git a/example/ex_cpp/README b/example/ex_cpp/README new file mode 100644 index 00000000..b67ad2e8 --- /dev/null +++ b/example/ex_cpp/README @@ -0,0 +1,6 @@ +Not including this in build for now. +Instead, use to manually generate .ll output: + +$ clang -cc1 ex_cpp.cpp -emit-llvm + +and inspect ex_cpp.ll