diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index ce7b0f85..8e12990e 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/include/xo/alloc/GC.hpp @@ -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 @@ -154,6 +155,9 @@ namespace xo { **/ static up 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 + void add_gc_root_dwim(gp * p) { this->add_gc_root(reinterpret_cast(p->ptr_address())); } + template + void remove_gc_root_dwim(gp * p) { this->remove_gc_root(reinterpret_cast(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. diff --git a/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index 22654700..c78768fb 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/src/alloc/GC.cpp @@ -132,11 +132,15 @@ namespace xo { up GC::make(const Config & config) { - //GC * gc = new GC(config); - return std::make_unique(config); } + GC * + GC::from(IAlloc * mm) + { + return dynamic_cast(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 fn) -> CallbackId { diff --git a/xo-expression/include/xo/expression/GlobalSymtab.hpp b/xo-expression/include/xo/expression/GlobalSymtab.hpp index 6bdd88e0..1669df3b 100644 --- a/xo-expression/include/xo/expression/GlobalSymtab.hpp +++ b/xo-expression/include/xo/expression/GlobalSymtab.hpp @@ -39,7 +39,7 @@ namespace xo { if (ix == global_map_.end()) { /* not found */ - return bp::from_native(nullptr); + return bp(); } return ix->second; diff --git a/xo-interpreter/include/xo/interpreter/Env.hpp b/xo-interpreter/include/xo/interpreter/Env.hpp index a1c43132..d4eaa513 100644 --- a/xo-interpreter/include/xo/interpreter/Env.hpp +++ b/xo-interpreter/include/xo/interpreter/Env.hpp @@ -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 v) = 0; + //gp lookup_symbol(const std::string & name) const; }; } /*namespace scm*/ diff --git a/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp b/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp index f6e660b9..d45f9cb4 100644 --- a/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp +++ b/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp @@ -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 var) final override; + // inherited from Object.. virtual TaggedPtr self_tp() const final override; virtual void display(std::ostream & os) const final override; diff --git a/xo-interpreter/include/xo/interpreter/LocalEnv.hpp b/xo-interpreter/include/xo/interpreter/LocalEnv.hpp index ddb31fbe..f7515fa2 100644 --- a/xo-interpreter/include/xo/interpreter/LocalEnv.hpp +++ b/xo-interpreter/include/xo/interpreter/LocalEnv.hpp @@ -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 operator[](std::size_t i) const { return slot_v_[i]; } gp & 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 v) final override; + // inherited from Object.. virtual TaggedPtr self_tp() const final override; virtual void display(std::ostream & os) const final override; diff --git a/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp b/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp index ca4ede2a..94712f33 100644 --- a/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp +++ b/xo-interpreter/include/xo/interpreter/VirtualSchematikaMachine.hpp @@ -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); /** memory allocator for interpreter operation. **/ gc::IAlloc * object_mm_ = nullptr; + /** global environment **/ + gp 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 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, SchematikaError> eval(bp expr); + std::pair, SchematikaError> eval(bp expr, gp env); + + /** evaluate expression @p expr in toplevel environment **/ + std::pair, SchematikaError> toplevel_eval(bp 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 expr_; + /** holds bindings for all schematika variables **/ + gp env_; /** non-error result value from eval() / apply() **/ gp value_; diff --git a/xo-interpreter/src/interpreter/GlobalEnv.cpp b/xo-interpreter/src/interpreter/GlobalEnv.cpp index 7eee0f86..5ecb598d 100644 --- a/xo-interpreter/src/interpreter/GlobalEnv.cpp +++ b/xo-interpreter/src/interpreter/GlobalEnv.cpp @@ -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 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(); + } + TaggedPtr GlobalEnv::self_tp() const { diff --git a/xo-interpreter/src/interpreter/LocalEnv.cpp b/xo-interpreter/src/interpreter/LocalEnv.cpp index 1959c903..38cea06d 100644 --- a/xo-interpreter/src/interpreter/LocalEnv.cpp +++ b/xo-interpreter/src/interpreter/LocalEnv.cpp @@ -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 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 { diff --git a/xo-interpreter/src/interpreter/Schematika.cpp b/xo-interpreter/src/interpreter/Schematika.cpp index 7e764b25..073064da 100644 --- a/xo-interpreter/src/interpreter/Schematika.cpp +++ b/xo-interpreter/src/interpreter/Schematika.cpp @@ -5,6 +5,7 @@ #include "Schematika.hpp" #include "VirtualSchematikaMachine.hpp" +#include "GlobalEnv.hpp" #include "xo/reader/reader.hpp" #include #include @@ -26,7 +27,8 @@ namespace xo { * rather than VirtualSchematikaMachine to own allocator * to preserve option to share it **/ - Impl(const Config & config, up mm) : config_{config}, vsm_{mm.get()}, mm_{std::move(mm)} {} + Impl(const Config & config, up mm, gp toplevel_env) : + config_{config}, vsm_{mm.get(), toplevel_env}, mm_{std::move(mm)} {} /** create instance + allocator **/ static up make(const Config & cfg); @@ -59,8 +61,10 @@ namespace xo { Schematika::Impl::make(const Config & cfg) { up mm = GC::make(cfg.gc_config_); + rp symtab = GlobalSymtab::make_empty(); + gp env = GlobalEnv::make_empty(mm.get(), symtab); - return std::make_unique(cfg, std::move(mm)); + return std::make_unique(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 */ diff --git a/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp b/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp index 915f138c..9353c3be 100644 --- a/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp +++ b/xo-interpreter/src/interpreter/VirtualSchematikaMachine.cpp @@ -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) : + object_mm_{mm}, + toplevel_env_{env} {} // ----- VirtualSchematikaMachine ----- - VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm) : flyweight_{mm} - {} + VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm, gp 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, SchematikaError> - VirtualSchematikaMachine::eval(bp expr) + VirtualSchematikaMachine::toplevel_eval(bp expr) + { + return this->eval(expr, this->env_); + } + + std::pair, + SchematikaError> + VirtualSchematikaMachine::eval(bp expr, gp 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 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 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*/