xo-interpreter: + toplevel env in VSM

This commit is contained in:
Roland Conybeare 2025-11-23 21:41:14 -05:00
commit 56a1c3bc75
11 changed files with 218 additions and 18 deletions

View file

@ -7,6 +7,7 @@
#include "ArenaAlloc.hpp"
#include "GcStatistics.hpp"
#include "Object.hpp"
#include "xo/callback/UpCallbackSet.hpp"
#include "xo/indentlog/print/array.hpp"
#include <vector>
@ -154,6 +155,9 @@ namespace xo {
**/
static up<GC> make(const Config & config);
/** runtime downcast **/
static GC * from(IAlloc * mm);
const Config & config() const { return config_; }
std::uint8_t nursery_polarity() const { return nursery_polarity_; }
std::uint8_t tenured_polarity() const { return tenured_polarity_; }
@ -230,6 +234,15 @@ namespace xo {
* from @c *addr
**/
void add_gc_root(Object ** addr);
/** reverse the effect of previous call to @ref add_gc_root **/
void remove_gc_root(Object ** addr);
/** convenience wrapper **/
template <typename T>
void add_gc_root_dwim(gp<T> * p) { this->add_gc_root(reinterpret_cast<Object**>(p->ptr_address())); }
template <typename T>
void remove_gc_root_dwim(gp<T> * p) { this->remove_gc_root(reinterpret_cast<Object**>(p->ptr_address())); }
/** may optionally use this to observe GC copy phase.
* Will be invoked once _per surviving object_, so not cheap.
* Intended for GC visualization.

View file

@ -132,11 +132,15 @@ namespace xo {
up<GC>
GC::make(const Config & config)
{
//GC * gc = new GC(config);
return std::make_unique<GC>(config);
}
GC *
GC::from(IAlloc * mm)
{
return dynamic_cast<GC *>(mm);
}
const std::string &
GC::name() const
{
@ -390,6 +394,19 @@ namespace xo {
gc_root_v_.push_back(addr);
}
void
GC::remove_gc_root(Object ** addr)
{
/* Multithreaded GC not supported */
assert(!this->gc_in_progress());
auto new_end_ix = std::remove(gc_root_v_.begin(), gc_root_v_.end(), addr);
/* erase now-unused slots */
gc_root_v_.erase(new_end_ix, gc_root_v_.end());
}
auto
GC::add_gc_copy_callback(up<GcCopyCallback> fn) -> CallbackId
{

View file

@ -39,7 +39,7 @@ namespace xo {
if (ix == global_map_.end()) {
/* not found */
return bp<Variable>::from_native(nullptr);
return bp<Variable>();
}
return ix->second;

View file

@ -6,9 +6,12 @@
#pragma once
#include "xo/alloc/Object.hpp"
#include "xo/refcnt/Refcounted.hpp"
namespace xo {
namespace scm {
class Variable; // see xo::scm::Variable in xo/expression/Variable.hpp
/** @class Env
* @brief runtime environment, holding variable bindings for schematika interpreter
*
@ -18,6 +21,19 @@ namespace xo {
**/
class Env : public Object {
public:
/** true iff @p vname is present in Symtab for innermost environment **/
virtual bool local_contains_var(const std::string & vname) const = 0;
/** require storage for variable @p v.
* will also establish binding path.
*
* Intended for introducing a new variable,
* replacing any previous variable with the same name.
*
* Beware of invalidating type correctness
**/
virtual void establish_var(bp<Variable> v) = 0;
//gp<Object> lookup_symbol(const std::string & name) const;
};
} /*namespace scm*/

View file

@ -21,6 +21,10 @@ namespace xo {
gc::IAlloc * get_mm() const { return mm_; }
// inherited from Env..
virtual bool local_contains_var(const std::string & vname) const final override;
virtual void establish_var(bp<Variable> var) final override;
// inherited from Object..
virtual TaggedPtr self_tp() const final override;
virtual void display(std::ostream & os) const final override;

View file

@ -42,7 +42,7 @@ namespace xo {
* @brief Represent a single runtime stack frame for a Schematika function
*
* LocalEnv intended to be used for interpreted functions.
*
*
* Compiled functions will still likely have stack frames, but need not use the
* @ref LocalEnv class
*
@ -89,6 +89,15 @@ namespace xo {
gp<Object> operator[](std::size_t i) const { return slot_v_[i]; }
gp<Object> & operator[](std::size_t i) { return slot_v_[i]; }
// inherited from Env..
virtual bool local_contains_var(const std::string & vname) const final override;
/** LocalEnv policy is that variable can be established once only.
* For example function arguments must all have distinct names
**/
virtual void establish_var(bp<Variable> v) final override;
// inherited from Object..
virtual TaggedPtr self_tp() const final override;
virtual void display(std::ostream & os) const final override;

View file

@ -4,6 +4,7 @@
#include "VsmInstr.hpp"
#include "SchematikaError.hpp"
#include "Env.hpp"
#include "xo/expression/Expression.hpp"
#include "xo/object/ObjectConverter.hpp"
#include "xo/alloc/Object.hpp"
@ -12,10 +13,13 @@ namespace xo {
namespace scm {
/** @brief state that may be shared across VirtualSchematikaMachine instances **/
struct VirtualSchematikaMachineFlyweight {
explicit VirtualSchematikaMachineFlyweight(gc::IAlloc * mm);
explicit VirtualSchematikaMachineFlyweight(gc::IAlloc * mm,
gp<Env> env);
/** memory allocator for interpreter operation. **/
gc::IAlloc * object_mm_ = nullptr;
/** global environment **/
gp<Env> toplevel_env_;
/** convert TaggedPtr->Object **/
xo::obj::ObjectConverter object_converter_;
};
@ -29,17 +33,30 @@ namespace xo {
using IAlloc = xo::gc::IAlloc;
public:
VirtualSchematikaMachine(IAlloc * mm);
VirtualSchematikaMachine(IAlloc * mm, gp<Env> toplevel_env);
~VirtualSchematikaMachine();
/** evaluate expression @p expr.
* borrows calling thread until completion
* return [value, error]. error ignored unless value is nullptr.
* conversely when value is nullptr, error gives details of original
* error.
*
* Evaluate schematika expression @p expr in environment @p env
**/
std::pair<gp<Object>, SchematikaError> eval(bp<Expression> expr);
std::pair<gp<Object>, SchematikaError> eval(bp<Expression> expr, gp<Env> env);
/** evaluate expression @p expr in toplevel environment **/
std::pair<gp<Object>, SchematikaError> toplevel_eval(bp<Expression> expr);
private:
/** Not moveable or copyable.
* One constraint is member variables added to flyweight_.object_mm_
* as GC roots, with no provision for unwinding later.
**/
VirtualSchematikaMachine(const VirtualSchematikaMachine &) = delete;
VirtualSchematikaMachine(VirtualSchematikaMachine &&) = delete;
/** borrow calling thread to run schematika machine
* indefinitely, or until null continuation
**/
@ -67,6 +84,8 @@ namespace xo {
/** expression **/
rp<Expression> expr_;
/** holds bindings for all schematika variables **/
gp<Env> env_;
/** non-error result value from eval() / apply() **/
gp<Object> value_;

View file

@ -21,6 +21,40 @@ namespace xo {
symtab_{symtab}
{}
bool
GlobalEnv::local_contains_var(const std::string & vname) const
{
return symtab_->lookup_local(vname).get();
}
void
GlobalEnv::establish_var(bp<Variable> var)
{
// Warning: altering declared type for an already-existing variable
// invalidates any type checking that relied on that variable.
//
// Ignoring this problem for now.
//
// Actual solution might look like:
// - keep track of which functions/defs depend on each global variable.
// - invalidate any jit / types for such variables.
// - maybe use seqno's to track
// - re-check / re-complie
// - need to admit invalid states.
// suppose have mutually recursive functions f(), g()
// want ability to modify type signatures separately
//
// Alternatives:
// - forbid changing type of an already-established variable
// Actually: can't even change values if we intend supporting dependent types
// - quietly number variables so new definitions shadow old ones but don't
// affect previously-encountered expressions
this->symtab_->require_global(var->name(), var);
this->slot_map_[var->name()] = gp<Object>();
}
TaggedPtr
GlobalEnv::self_tp() const
{

View file

@ -44,6 +44,24 @@ namespace xo {
slot_v_{mm, n}
{}
bool
LocalEnv::local_contains_var(const std::string & vname) const
{
assert(symtab_.get());
return symtab_->lookup_local(vname);
}
void
LocalEnv::establish_var(bp<Variable> v)
{
assert(v);
throw std::runtime_error(tostr("LocalEnv::establish_var:"
" inserting new variables not supported for LocalEnv",
xtag("v.name", v->name())));
}
TaggedPtr
LocalEnv::self_tp() const
{

View file

@ -5,6 +5,7 @@
#include "Schematika.hpp"
#include "VirtualSchematikaMachine.hpp"
#include "GlobalEnv.hpp"
#include "xo/reader/reader.hpp"
#include <replxx.hxx>
#include <ostream>
@ -26,7 +27,8 @@ namespace xo {
* rather than VirtualSchematikaMachine to own allocator
* to preserve option to share it
**/
Impl(const Config & config, up<IAlloc> mm) : config_{config}, vsm_{mm.get()}, mm_{std::move(mm)} {}
Impl(const Config & config, up<IAlloc> mm, gp<Env> toplevel_env) :
config_{config}, vsm_{mm.get(), toplevel_env}, mm_{std::move(mm)} {}
/** create instance + allocator **/
static up<Impl> make(const Config & cfg);
@ -59,8 +61,10 @@ namespace xo {
Schematika::Impl::make(const Config & cfg)
{
up<IAlloc> mm = GC::make(cfg.gc_config_);
rp<GlobalSymtab> symtab = GlobalSymtab::make_empty();
gp<Env> env = GlobalEnv::make_empty(mm.get(), symtab);
return std::make_unique<Impl>(cfg, std::move(mm));
return std::make_unique<Impl>(cfg, std::move(mm), env);
}
void
@ -180,7 +184,7 @@ namespace xo {
//pps.prettyn(expr);
// TODO:
auto [ value, scm_error ] = this->vsm_.eval(expr);
auto [ value, scm_error ] = this->vsm_.toplevel_eval(expr);
if (scm_error.is_error()) {
/* print error */

View file

@ -3,6 +3,7 @@
#include "VirtualSchematikaMachine.hpp"
#include "VsmInstr.hpp"
#include "xo/expression/ConstantInterface.hpp"
#include "xo/alloc/GC.hpp"
/** continue after completing a VSM instruction;
* achieve by jumping to continuation.
@ -14,6 +15,8 @@
#define VSM_ERROR(msg) report_error(msg); return;
namespace xo {
using xo::gc::GC;
namespace scm {
struct VsmOps {
/** halt virtual scheme machine.
@ -36,21 +39,60 @@ namespace xo {
// ----- VirtualSchematikaMachineFlyweight -----
VirtualSchematikaMachineFlyweight::VirtualSchematikaMachineFlyweight(gc::IAlloc * mm) :
object_mm_{mm}
VirtualSchematikaMachineFlyweight::VirtualSchematikaMachineFlyweight(gc::IAlloc * mm,
gp<Env> env) :
object_mm_{mm},
toplevel_env_{env}
{}
// ----- VirtualSchematikaMachine -----
VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm) : flyweight_{mm}
{}
VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm, gp<Env> env) : flyweight_{mm, env}
{
// gc roots
gc::GC * gc = GC::from(mm);
if (gc) {
assert((gc->gc_in_progress() == false) && "cannot add roots while GC running");
gc->add_gc_root_dwim(&env_);
gc->add_gc_root_dwim(&value_);
} else {
// Want to support VSM with arena-allocator-only;
// if only for unit testing.
}
// TODO: install builtin primitives here
}
VirtualSchematikaMachine::~VirtualSchematikaMachine()
{
gc::GC * gc = GC::from(flyweight_.object_mm_);
if (gc) {
assert((gc->gc_in_progress() == false) && "cannot remove roots while GC running");
gc->remove_gc_root_dwim(&env_);
gc->remove_gc_root_dwim(&value_);
} else {
// nothing to do in arena-only mode
}
}
std::pair<gp<Object>,
SchematikaError>
VirtualSchematikaMachine::eval(bp<Expression> expr)
VirtualSchematikaMachine::toplevel_eval(bp<Expression> expr)
{
return this->eval(expr, this->env_);
}
std::pair<gp<Object>,
SchematikaError>
VirtualSchematikaMachine::eval(bp<Expression> expr, gp<Env> env)
{
this->pc_ = &VsmOps::eval_op;
this->expr_ = expr.promote();
this->env_ = env;
this->cont_ = &VsmOps::halt_op;
this->run();
@ -101,7 +143,7 @@ namespace xo {
case exprtype::n_expr:
this->pc_ = nullptr;
this->value_ = nullptr;
this->error_ = SchematikaError(tostr("execute_vsm: not implmented",
this->error_ = SchematikaError(tostr("execute_vsm: not implemented",
xtag("extype", expr_->extype())));
this->cont_ = nullptr;
break;
@ -129,10 +171,10 @@ namespace xo {
void
VirtualSchematikaMachine::constant_op()
{
scope log(XO_DEBUG(true));
using xo::scm::ConstantInterface;
scope log(XO_DEBUG(true));
bp<ConstantInterface> expr = ConstantInterface::from(expr_);
assert(expr);
@ -153,6 +195,30 @@ namespace xo {
}
}
// placeholder: primitive_op
#ifdef NOT_YET
void
VirtualSchematikaMachine::define_op()
{
using xo::scm::DefineExpr;
bp<DefineExpr> expr = DefineExpr::from(expr_);
assert(expr);
// note: establish lhs_var first, to allow for recursion, for example:
// def fact(n: i64) { if (n == 0) then 1; else n * fact(n-1); }
/** remembers promised variable type **/
env_->establish_var(expr->lhs_variable());
/* lhs_var
* rhs
*/
}
#endif
} /*namespace scm*/
} /*namespace xo*/