From fdc5d46fd7b340c716ee4a26aa89c96516fc0241 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 20:26:07 -0400 Subject: [PATCH] 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*/