xo-interpreter2 stack: invoke closures w/ tail-call opt [WIP]

This commit is contained in:
Roland Conybeare 2026-02-13 02:05:47 -05:00
commit af4c37c575
15 changed files with 175 additions and 87 deletions

View file

@ -63,6 +63,7 @@ namespace xo {
DLocalSymtab * local_symtab() const noexcept { return local_symtab_; } DLocalSymtab * local_symtab() const noexcept { return local_symtab_; }
size_type n_args() const noexcept { return local_symtab_->size(); } size_type n_args() const noexcept { return local_symtab_->size(); }
obj<AExpression> body_expr() const noexcept { return body_expr_; }
// get_free_variables() // get_free_variables()
// visit_preorder() // visit_preorder()

View file

@ -104,16 +104,17 @@ xo_add_genfacetimpl(
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# note: manual target; generated code committed to git # note: manual target; generated code committed to git
xo_add_genfacetimpl( #
TARGET xo-interpreter2-facetimpl-procedure-closure #xo_add_genfacetimpl(
FACET_PKG xo_procedure2 # TARGET xo-interpreter2-facetimpl-procedure-closure
FACET Procedure # FACET_PKG xo_procedure2
REPR Closure # FACET Procedure
INPUT idl/IProcedure_DClosure.json5 # REPR Closure
OUTPUT_HPP_DIR include/xo/interpreter2 # INPUT idl/IProcedure_DClosure.json5
OUTPUT_IMPL_SUBDIR detail # OUTPUT_HPP_DIR include/xo/interpreter2
OUTPUT_CPP_DIR src/interpreter2 # OUTPUT_IMPL_SUBDIR detail
) # OUTPUT_CPP_DIR src/interpreter2
#)
# note: manual target; generated code committed to git # note: manual target; generated code committed to git
xo_add_genfacetimpl( xo_add_genfacetimpl(

View file

@ -6,7 +6,7 @@
#pragma once #pragma once
#include "DClosure.hpp" #include "DClosure.hpp"
#include "detail/IProcedure_DClosure.hpp" //#include "detail/IProcedure_DClosure.hpp"
#include "detail/IGCObject_DClosure.hpp" #include "detail/IGCObject_DClosure.hpp"
#include "detail/IPrintable_DClosure.hpp" #include "detail/IPrintable_DClosure.hpp"

View file

@ -28,6 +28,16 @@ namespace xo {
VsmInstr cont, VsmInstr cont,
DLocalEnv * env); DLocalEnv * env);
/** create instance, using memory from @p mm **/
static DVsmApplyClosureFrame * make(obj<AAllocator> mm,
obj<AGCObject> stack,
VsmInstr cont,
DLocalEnv * env);
obj<AGCObject> stack() const { return stack_; }
VsmInstr cont() const { return cont_; }
DLocalEnv * local_env() const { return local_env_; }
/** gcobject facet **/ /** gcobject facet **/
std::size_t shallow_size() const noexcept; std::size_t shallow_size() const noexcept;
DVsmApplyClosureFrame * shallow_copy(obj<AAllocator> mm) const noexcept; DVsmApplyClosureFrame * shallow_copy(obj<AAllocator> mm) const noexcept;

View file

@ -30,10 +30,10 @@ namespace xo {
obj<AGCObject> parent() const noexcept { return parent_; } obj<AGCObject> parent() const noexcept { return parent_; }
VsmInstr cont() const noexcept { return cont_; } VsmInstr cont() const noexcept { return cont_; }
obj<AProcedure> fn() const noexcept { return fn_; } obj<AGCObject> fn() const noexcept { return fn_; }
DArray * args() const noexcept { return args_; } DArray * args() const noexcept { return args_; }
void assign_fn(obj<AProcedure> x) { this->fn_ = x; } void assign_fn(obj<AGCObject> x) { this->fn_ = x; }
std::size_t shallow_size() const noexcept; std::size_t shallow_size() const noexcept;
DVsmApplyFrame * shallow_copy(obj<AAllocator> mm) const noexcept; DVsmApplyFrame * shallow_copy(obj<AAllocator> mm) const noexcept;
@ -51,9 +51,13 @@ namespace xo {
* *
* note: when initially created, this will be unpopulated; * note: when initially created, this will be unpopulated;
* don't know correct value until we evaluate * don't know correct value until we evaluate
* expression in head position * expression in head position.
*
* Must exhibit either:
* 1. AProcedure facet (runs natively)
* 2. AVsmProcedure facet (requires schematika runtime)
**/ **/
obj<AProcedure> fn_; obj<AGCObject> fn_;
/** evaluated arguments (to target procedure) **/ /** evaluated arguments (to target procedure) **/
DArray * args_; DArray * args_;
}; };

View file

@ -176,16 +176,28 @@ namespace xo {
**/ **/
void _do_evalargs_op(); void _do_evalargs_op();
/** call closure @ref fn_ with arguments @ref args_ **/
void _do_call_closure_op();
/** call primitive @ref fn_ with arguments @ref args_ **/
void _do_call_primitive_op();
/** restore registers from stack frame
* (specifically: local_env_, stack_, cont_)
* after invoking a schematika closure
**/
void _do_applycoda_op();
private: private:
/* /*
* Some registers are preserved by evaluation: * Some registers are preserved by evaluation:
* stack_ * stack_
* cont_ * cont_
* local_env_
* *
* Other registers not preserved * Other registers not preserved
* pc_ * pc_
* expr_ * expr_
* local_env_
* fn_ * fn_
* args_ * args_
* value_ * value_
@ -244,8 +256,8 @@ namespace xo {
DGlobalEnv * global_env_ = nullptr; DGlobalEnv * global_env_ = nullptr;
private: private:
/** function to call **/ /** evaluated function to call **/
obj<AProcedure> fn_; obj<AGCObject> fn_;
/** evaluated argument list **/ /** evaluated argument list **/
DArray * args_; DArray * args_;

View file

@ -19,12 +19,20 @@ namespace xo {
static VsmInstr c_apply; static VsmInstr c_apply;
static VsmInstr c_evalargs; static VsmInstr c_evalargs;
/** restore registers after calling a schematika closure **/
static VsmInstr c_applycoda;
vsm_opcode opcode() const noexcept { return opcode_; } vsm_opcode opcode() const noexcept { return opcode_; }
private: private:
vsm_opcode opcode_; vsm_opcode opcode_;
}; };
inline bool
operator==(VsmInstr x, VsmInstr y) noexcept {
return x.opcode() == y.opcode();
}
inline std::ostream & inline std::ostream &
operator<<(std::ostream & os, VsmInstr x) { operator<<(std::ostream & os, VsmInstr x) {
os << x.opcode(); os << x.opcode();

View file

@ -28,6 +28,11 @@ namespace xo {
**/ **/
evalargs, evalargs,
/** Coda to restore vsm registers (local_env, stack, cont)
* after invoking a closure
**/
applycoda,
/** sentinel, counts number of opcodes **/ /** sentinel, counts number of opcodes **/
N, N,
}; };

View file

@ -21,7 +21,6 @@ set(SELF_SRCS
IPrintable_DVsmApplyClosureFrame.cpp IPrintable_DVsmApplyClosureFrame.cpp
DClosure.cpp DClosure.cpp
IProcedure_DClosure.cpp
IGCObject_DClosure.cpp IGCObject_DClosure.cpp
IPrintable_DClosure.cpp IPrintable_DClosure.cpp

View file

@ -36,6 +36,14 @@ namespace xo {
DClosure::apply_nocheck(obj<ARuntimeContext> rcx, DClosure::apply_nocheck(obj<ARuntimeContext> rcx,
const DArray * args) const DArray * args)
{ {
// control here only if you try to invoke a closure
// as a procedure.
//
// May support this later, but requires
// nesting VSM (because call consumes c++ stack)
//
// typically prefer trampoline built into VSM
(void)args; (void)args;
scope log(XO_DEBUG(true)); scope log(XO_DEBUG(true));
@ -45,23 +53,6 @@ namespace xo {
log && log(xtag("vsm_rcx.data", (void*)vsm_rcx.data())); log && log(xtag("vsm_rcx.data", (void*)vsm_rcx.data()));
// we already checked this stuff before calling apply_nocheck()
// assert (n_args == args->size());
#ifdef NOT_YET
auto local_env
= DLocalEnv::_make(vsm_rcx->allocator(),
env_,
lambda_->local_symtab(),
args);
#endif
// plan:
// 1. push current local environment to vsm stack_
// 2. set expr_ to lambda body
// 2. set pc_ to eval
// 3. set cont_ to restore local_env_
auto err_mm auto err_mm
= vsm_rcx->error_allocator(); = vsm_rcx->error_allocator();

View file

@ -7,6 +7,7 @@
namespace xo { namespace xo {
using xo::mm::AGCObject; using xo::mm::AGCObject;
using xo::reflect::typeseq;
namespace scm { namespace scm {
@ -18,6 +19,18 @@ namespace xo {
local_env_{local_env} local_env_{local_env}
{} {}
DVsmApplyClosureFrame *
DVsmApplyClosureFrame::make(obj<AAllocator> mm,
obj<AGCObject> stack,
VsmInstr cont,
DLocalEnv * local_env)
{
void * mem = mm.alloc(typeseq::id<DVsmApplyClosureFrame>(),
sizeof(DVsmApplyClosureFrame));
return new (mem) DVsmApplyClosureFrame(stack, cont, local_env);
}
std::size_t std::size_t
DVsmApplyClosureFrame::shallow_size() const noexcept DVsmApplyClosureFrame::shallow_size() const noexcept
{ {

View file

@ -1,39 +0,0 @@
/** @file IProcedure_DClosure.cpp
*
* Generated automagically from ingredients:
* 1. code generator:
* [xo-facet/codegen/genfacet]
* arguments:
* --input [idl/IProcedure_DClosure.json5]
* 2. jinja2 template for abstract facet .hpp file:
* [iface_facet_any.hpp.j2]
* 3. idl for facet methods
* [idl/IProcedure_DClosure.json5]
**/
#include "detail/IProcedure_DClosure.hpp"
namespace xo {
namespace scm {
auto
IProcedure_DClosure::is_nary(const DClosure & self) noexcept -> bool
{
return self.is_nary();
}
auto
IProcedure_DClosure::n_args(const DClosure & self) noexcept -> std::int32_t
{
return self.n_args();
}
auto
IProcedure_DClosure::apply_nocheck(DClosure & self, obj<ARuntimeContext> rcx, const DArray * args) -> obj<AGCObject>
{
return self.apply_nocheck(rcx, args);
}
} /*namespace scm*/
} /*namespace xo*/
/* end IProcedure_DClosure.cpp */

View file

@ -6,6 +6,7 @@
#include "VirtualSchematikaMachine.hpp" #include "VirtualSchematikaMachine.hpp"
#include "VsmApplyFrame.hpp" #include "VsmApplyFrame.hpp"
#include "VsmEvalArgsFrame.hpp" #include "VsmEvalArgsFrame.hpp"
#include "VsmApplyClosureFrame.hpp"
#include "VsmRcx.hpp" #include "VsmRcx.hpp"
#include "Closure.hpp" #include "Closure.hpp"
#include <xo/expression2/ApplyExpr.hpp> #include <xo/expression2/ApplyExpr.hpp>
@ -164,9 +165,9 @@ namespace xo {
log && log(xtag("pc", pc_), log && log(xtag("pc", pc_),
xtag("cont", cont_)); xtag("cont", cont_));
obj<APrintable> stack_pr obj<APrintable> stack_pr = stack_.to_facet<APrintable>();
= (FacetRegistry::instance() // = (FacetRegistry::instance()
.try_variant<APrintable,AGCObject>(stack_)); // .try_variant<APrintable,AGCObject>(stack_));
if (stack_pr) if (stack_pr)
log && log(xtag("stack", stack_pr)); log && log(xtag("stack", stack_pr));
@ -184,6 +185,9 @@ namespace xo {
case vsm_opcode::evalargs: case vsm_opcode::evalargs:
_do_evalargs_op(); _do_evalargs_op();
break; break;
case vsm_opcode::applycoda:
_do_applycoda_op();
break;
} }
return true; return true;
@ -370,10 +374,68 @@ namespace xo {
// TODO: check argument types // TODO: check argument types
this->value_ = VsmResult(fn_.apply_nocheck(rcx_.to_op(), args_)); auto closure = obj<AGCObject,DClosure>::from(fn_);
this->pc_ = cont_;
return; if (closure) {
_do_call_closure_op();
return;
} else {
_do_call_closure_op();
return;
}
}
void
VirtualSchematikaMachine::_do_call_closure_op()
{
// We need to preserve registers while evaluating
// lambda body
auto closure = obj<AGCObject,DClosure>::from(fn_);
// TODO: for tail recursion:
// check whether stack_ already refers to a
// DVsmApplyClosureFrame instance, in which case
// we can just refer to it instead of pushing a new one
if (cont_ == VsmInstr::c_applycoda) {
// we are making a tail call.
// No need to preserve (stack, cont, local_env),
// since continuation will restore on top of them
// frame top stackframe anyway
} else {
obj<AGCObject,
DVsmApplyClosureFrame> frame(
DVsmApplyClosureFrame::make(mm_.to_op(),
stack_,
cont_,
local_env_));
// push frame w/ saved vsm registers
this->stack_ = frame;
this->cont_ = VsmInstr::c_applycoda;
}
auto lambda = closure->lambda();
auto local_env
= DLocalEnv::_make(mm_.to_op(),
local_env_,
lambda->local_symtab(),
args_);
this->local_env_ = local_env;
this->expr_ = lambda->body_expr();
this->pc_ = VsmInstr::c_eval;
}
void
VirtualSchematikaMachine::_do_call_primitive_op()
{
auto fn = fn_.to_facet<AProcedure>();
this->value_ = VsmResult(fn.apply_nocheck(rcx_.to_op(), args_));
this->pc_ = cont_;
} }
void void
@ -434,10 +496,11 @@ namespace xo {
= evalargs_frame->apply_expr(); = evalargs_frame->apply_expr();
if (i_arg == -1) { if (i_arg == -1) {
auto fn = value.to_facet<AProcedure>(); bool is_native_fn = value.to_facet<AProcedure>();
bool is_closure = obj<AGCObject,DClosure>::from(value);
if (fn) { if (is_native_fn || is_closure) {
apply_frame->assign_fn(fn); apply_frame->assign_fn(value);
i_arg = evalargs_frame->increment_arg(); i_arg = evalargs_frame->increment_arg();
@ -493,6 +556,22 @@ namespace xo {
assert(false); assert(false);
} }
void
VirtualSchematikaMachine::_do_applycoda_op()
{
// see DVsmApplyClosureFrame
auto frame = obj<AGCObject,DVsmApplyClosureFrame>::from(stack_);
assert(frame);
this->stack_ = frame->stack();
this->local_env_ = frame->local_env();
this->pc_ = frame->cont();
// not implemented
assert(false);
}
} /*namespace scm*/ } /*namespace scm*/
} /*namespace xo*/ } /*namespace xo*/

View file

@ -15,6 +15,7 @@ namespace xo {
case vsm_opcode::eval: return "eval"; case vsm_opcode::eval: return "eval";
case vsm_opcode::apply: return "apply"; case vsm_opcode::apply: return "apply";
case vsm_opcode::evalargs: return "evalargs"; case vsm_opcode::evalargs: return "evalargs";
case vsm_opcode::applycoda: return "applycoda";
case vsm_opcode::N: case vsm_opcode::N:
break; break;
} }
@ -33,6 +34,9 @@ namespace xo {
VsmInstr VsmInstr
VsmInstr::c_evalargs = VsmInstr(vsm_opcode::evalargs); VsmInstr::c_evalargs = VsmInstr(vsm_opcode::evalargs);
VsmInstr
VsmInstr::c_applycoda = VsmInstr(vsm_opcode::applycoda);
} /*namespace scm*/ } /*namespace scm*/
} /*namespace xo*/ } /*namespace xo*/

View file

@ -59,9 +59,9 @@ namespace xo {
FacetRegistry::register_impl<AGCObject, DPrimitive_gco_2_gco_gco>(); FacetRegistry::register_impl<AGCObject, DPrimitive_gco_2_gco_gco>();
FacetRegistry::register_impl<APrintable, DPrimitive_gco_2_gco_gco>(); FacetRegistry::register_impl<APrintable, DPrimitive_gco_2_gco_gco>();
FacetRegistry::register_impl<AProcedure, DClosure>(); // FacetRegistry::register_impl<AProcedure, DClosure>();
FacetRegistry::register_impl<AGCObject, DClosure>(); // FacetRegistry::register_impl<AGCObject, DClosure>();
FacetRegistry::register_impl<APrintable, DClosure>(); // FacetRegistry::register_impl<APrintable, DClosure>();
// RuntimeContext // RuntimeContext
// \- VsmRcx // \- VsmRcx