xo-jit: + runtime_binding_path, ++ to activation_record

This commit is contained in:
Roland Conybeare 2024-07-05 20:26:07 -04:00
commit fdc5d46fd7
3 changed files with 86 additions and 8 deletions

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include "LlvmContext.hpp" #include "LlvmContext.hpp"
#include "xo/expression/Lambda.hpp"
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-parameter"
# include <llvm/IR/IRBuilder.h> # include <llvm/IR/IRBuilder.h>
@ -16,21 +17,92 @@
namespace xo { namespace xo {
namespace jit { 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 { class activation_record {
public: public:
activation_record() = default; using Lambda = xo::ast::Lambda;
public:
activation_record(const ref::rp<Lambda> & lm)
: lambda_{lm} {}
llvm::AllocaInst * lookup_var(const std::string & var_name) const; 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); llvm::AllocaInst * alloca);
private: 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> lambda_;
/** @c binding_v_[i] specifies how/where to get 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
**/
std::map<std::string, llvm::AllocaInst*> frame_; /* <-> kaleidoscope NamedValues */ std::map<std::string, llvm::AllocaInst*> frame_; /* <-> kaleidoscope NamedValues */
}; /*activation_record*/ }; /*activation_record*/

View file

@ -736,7 +736,7 @@ namespace xo {
/** Actual parameters will need their own activation record. /** Actual parameters will need their own activation record.
* Track its shape here. * 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())); log && log("lambda: stack size Z", xtag("Z", env_stack_.size()));
@ -769,7 +769,7 @@ namespace xo {
* in lambda body. * in lambda body.
* *
*/ */
env_stack_.top().alloc_var(arg_name, alloca); env_stack_.top().alloc_var(i, arg_name, alloca);
++i; ++i;
} }
} }

View file

@ -25,7 +25,8 @@ namespace xo {
} /*lookup_var*/ } /*lookup_var*/
llvm::AllocaInst * 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) llvm::AllocaInst * alloca)
{ {
if (frame_.find(x) != frame_.end()) { if (frame_.find(x) != frame_.end()) {
@ -35,6 +36,11 @@ namespace xo {
return nullptr; 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; frame_[x] = alloca;
return alloca; return alloca;
} /*alloc_var*/ } /*alloc_var*/