xo-jit: + explicit env for captured function args [wip, not tested]

This commit is contained in:
Roland Conybeare 2024-07-07 13:27:12 -04:00
commit d7192c1d97
7 changed files with 991 additions and 220 deletions

View file

@ -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 **/

View file

@ -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<Lambda> & lm)
: lambda_{lm} {}
activation_record(const ref::rp<Lambda> & lm);
llvm::AllocaInst * lookup_var(const std::string & var_name) const;
const ref::rp<Lambda> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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> 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<runtime_binding_path> 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<std::string, llvm::AllocaInst*> 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<std::string, runtime_binding_detail> frame_; /* <-> kaleidoscope NamedValues */
}; /*activation_record*/
} /*namespace jit*/

View file

@ -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 <llvm/IR/DerivedTypes.h>
//#include <cstdint>
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<LlvmContext> 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<LlvmContext> 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<LlvmContext> llvm_cx,
xo::ref::brw<Lambda> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> llvm_cx);
/** establish llvm abstract representation for a closure:
* struct with
* - [0] function pointer
* - [1] runtime localenv pointer
*
* +-------+
* | o---------> native function
* +-------+
* | o---------> runtime localenv
* +-------+ (possibly nullptr)
*
* 1. for primitives, localenv will be null pointer
* 2. for lambdas L with L->requires_closure_flag() = false,
* localenv will also be null pointer
* 3. for lambdas with L->requires_closure_flag() = true,
*
* localenv will (for lambdas requiring closures)
* in practice be struct:
*
* ^
* | parent
* +-------+ |
* parent_env [0] | o-------/
* +-------+
* unwind_fn [1] | o-------> env * (*)(env*, ctl)
* +-------+
* arg[i] [2+i] . ... .
* . ... .
* +-------+
*
* ctl=0 unwind. finalization for any arg[i] that requires it.
* returns nullptr
* ctl=1 copy. copy runtime environment to heap destination
* and return address of the copy
*
* Implementation here will just use generic pointer for runtime
* localenv.
**/
static llvm::StructType *
function_td_to_llvm_closure_type(xo::ref::brw<LlvmContext> llvm_cx,
TypeDescr fn_td);
}; /*type2llvm*/
} /*namespace jit*/
} /*namespace xo*/
/** end type2llvm.hpp **/

View file

@ -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})

View file

@ -1,6 +1,8 @@
/* @file MachPipeline.cpp */
#include "MachPipeline.hpp"
#include "activation_record.hpp"
#include "type2llvm.hpp"
#include <string>
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<LlvmContext> 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<LlvmContext> llvm_cx,
TypeDescr fn_td)
{
int n_fn_arg = fn_td->n_fn_arg();
std::vector<llvm::Type *> 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<LlvmContext> 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<LlvmContext> 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::Type *> 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<LlvmContext> 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<LlvmContext> 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<bool>(td)) {
return llvm::Type::getInt1Ty(llvm_cx_ref);
} else if (Reflect::is_native<char>(td)) {
return llvm::Type::getInt8Ty(llvm_cx_ref);
} else if (Reflect::is_native<short>(td)) {
return llvm::Type::getInt16Ty(llvm_cx_ref);
} else if (Reflect::is_native<int>(td)) {
return llvm::Type::getInt32Ty(llvm_cx_ref);
} else if (Reflect::is_native<long>(td)) {
return llvm::Type::getInt64Ty(llvm_cx_ref);
} else if (Reflect::is_native<float>(td)) {
return llvm::Type::getFloatTy(llvm_cx_ref);
} else if (Reflect::is_native<double>(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<ref::brw<Lambda>>
@ -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*/

View file

@ -1,6 +1,7 @@
/* @file activation_record.cpp */
#include "activation_record.hpp"
#include "type2llvm.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <iostream>
@ -9,7 +10,29 @@ namespace xo {
using std::cerr;
using std::endl;
llvm::AllocaInst *
activation_record::activation_record(const ref::rp<Lambda> & 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<llvm::Value*,2> 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<LlvmContext> 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*/

305
src/jit/type2llvm.cpp Normal file
View file

@ -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<LlvmContext> 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<bool>(td)) {
return llvm::Type::getInt1Ty(llvm_cx_ref);
} else if (Reflect::is_native<char>(td)) {
return llvm::Type::getInt8Ty(llvm_cx_ref);
} else if (Reflect::is_native<short>(td)) {
return llvm::Type::getInt16Ty(llvm_cx_ref);
} else if (Reflect::is_native<int>(td)) {
return llvm::Type::getInt32Ty(llvm_cx_ref);
} else if (Reflect::is_native<long>(td)) {
return llvm::Type::getInt64Ty(llvm_cx_ref);
} else if (Reflect::is_native<float>(td)) {
return llvm::Type::getFloatTy(llvm_cx_ref);
} else if (Reflect::is_native<double>(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<LlvmContext> llvm_cx,
TypeDescr fn_td)
{
int n_fn_arg = fn_td->n_fn_arg();
std::vector<llvm::Type *> 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<LlvmContext> 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<LlvmContext> 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::Type *> 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<LlvmContext> 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<LlvmContext> llvm_cx,
llvm::PointerType * envptr_llvm_type)
{
if (!envptr_llvm_type)
envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx);
std::vector<llvm::Type *> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> 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<LlvmContext> llvm_cx,
xo::ref::brw<Lambda> 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<llvm::Type *> 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 */