From 85bfd34c0d7e42d6cfe309e27bf846b8524f17aa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Nov 2025 12:46:06 -0500 Subject: [PATCH] xo-interpreter: + Env + LocalEnv + GlobalEnv scaffold --- xo-interpreter/include/xo/interpreter/Env.hpp | 2 +- .../include/xo/interpreter/GlobalEnv.hpp | 57 +++++++ .../include/xo/interpreter/LocalEnv.hpp | 113 +++++++++++++ xo-interpreter/src/interpreter/CMakeLists.txt | 3 +- xo-interpreter/src/interpreter/GlobalEnv.cpp | 62 ++++++++ xo-interpreter/src/interpreter/LocalEnv.cpp | 149 ++++++++++++++++++ xo-interpreter/utest/LocalEnv.test.cpp | 134 ++++++++++++++++ 7 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 xo-interpreter/include/xo/interpreter/GlobalEnv.hpp create mode 100644 xo-interpreter/include/xo/interpreter/LocalEnv.hpp create mode 100644 xo-interpreter/src/interpreter/GlobalEnv.cpp create mode 100644 xo-interpreter/src/interpreter/LocalEnv.cpp create mode 100644 xo-interpreter/utest/LocalEnv.test.cpp diff --git a/xo-interpreter/include/xo/interpreter/Env.hpp b/xo-interpreter/include/xo/interpreter/Env.hpp index 947f19e9..a1c43132 100644 --- a/xo-interpreter/include/xo/interpreter/Env.hpp +++ b/xo-interpreter/include/xo/interpreter/Env.hpp @@ -18,7 +18,7 @@ namespace xo { **/ class Env : public Object { public: - //gp lookup_symbol(xxx); + //gp lookup_symbol(const std::string & name) const; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp b/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp new file mode 100644 index 00000000..0dcc392c --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/GlobalEnv.hpp @@ -0,0 +1,57 @@ +/** @file GlobalEnv.hpp **/ + +#pragma once + +#include "Env.hpp" +#include "xo/alloc/IAlloc.hpp" +#include "xo/expression/GlobalSymtab.hpp" + +namespace xo { + namespace scm { + /** @class GlobalEnv + * @brief Top-level global environment + **/ + class GlobalEnv : public Env { + public: + /** Create top-level global environment, allocating via @p mm. + * Expect one of these per interpreter session. + **/ + static gp make_empty(gc::IAlloc * mm, const rp & symtab); + + // inherited from Object.. + virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; + virtual std::size_t _shallow_size() const final override; + virtual Object * _shallow_copy() const final override; + virtual std::size_t _forward_children() final override; + + private: + GlobalEnv(gc::IAlloc * mm, const rp & symtab); + + private: + /** memory manager to use **/ + gc::IAlloc * mm_; + + /** global symbol table. + * variables known to @c symtab_ are represented by + * corresponding values in @p slot_map_ + **/ + rp symtab_; + + /** environment contents. + * expression @c symtab_->lookup_binding(vname) + * has associated value @c slot_map_.at(vname) + * + * TODO: replace with something subject to GC ? + * every member of @ref slot_map_ will have to be a + * GC root + * + * TODO: probably want to hash here instead. + * May also want lhs names to be separately hashed symbols + **/ + std::map> slot_map_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end GlobalEnv.hpp */ diff --git a/xo-interpreter/include/xo/interpreter/LocalEnv.hpp b/xo-interpreter/include/xo/interpreter/LocalEnv.hpp new file mode 100644 index 00000000..ddb31fbe --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/LocalEnv.hpp @@ -0,0 +1,113 @@ +/** @file LocalEnv.hpp **/ + +#include "Env.hpp" +#include "xo/alloc/IAlloc.hpp" +#include "xo/expression/LocalSymtab.hpp" +#include +#include + +namespace xo { + namespace scm { + /** gc-only vector + **/ + template + class CVector { + public: + using value_type = ElementType; + + public: + CVector(gc::IAlloc * mm, std::size_t n) + : n_{n}, v_{nullptr} + { + if (n_ > 0) { + std::byte * mem = mm->alloc(n_ * sizeof(ElementType)); + this->v_ = new (mem) ElementType[n]; + } + } + + std::size_t size() const { return n_; } + + ElementType operator[](std::size_t i) const { return v_[i]; } + ElementType & operator[](std::size_t i) { return v_[i]; } + + friend class LocalEnv; + private: + /** number of elements in @ref v_ **/ + std::size_t n_ = 0; + /** contiguous array of pointers **/ + ElementType * v_ = nullptr; + }; + + /** @class LocalEnv + * @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 + * + * memory layout: + * ^ + * +-----------------------+ | + * | vtable | | + * +-----------------------+ | + * | .parent +------/ + * +------------+----------+ + * | .slot_v_ | .n_ | + * | +----------+ + * | | .v_ +------\ + * +------------+----------+ <--/ + * | .v_[0] +---------> Object(1) + * +-----------------------+ + * . .. . + * +-----------------------+ + * | .v_[.n_-1] +---------> Object(n) + * +-----------------------+ + **/ + class LocalEnv : public Env { + public: + using TaggedPtr = xo::reflect::TaggedPtr; + + public: + LocalEnv(gc::IAlloc * mm, gp p, const rp & s, std::size_t n); + + /** create frame using allocator @p mm, + * with parent @p p and exactly @p n_slot object pointers. + * variable types are taken from symbol table @p s. + **/ + static gp make(gc::IAlloc * mm, + gp p, + const rp & s, + std::size_t n_slot); + + /** reflect LocalEnv object representation **/ + static void reflect_self(); + + gp parent() const { return parent_; } + std::size_t size() const { return slot_v_.size(); } + + gp operator[](std::size_t i) const { return slot_v_[i]; } + gp & operator[](std::size_t i) { return slot_v_[i]; } + + // inherited from Object.. + virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; + virtual std::size_t _shallow_size() const final override; + virtual Object * _shallow_copy() const final override; + virtual std::size_t _forward_children() final override; + + private: + /** parent stack frame **/ + gp parent_; + /** origin symbol table. records variable names and bindings. + * for a binding path p with leaf slot index j = p.j_slot_: + * @c slot_v_[j] holds value associated with variable @c symtab_->argv_[j] + **/ + rp symtab_; + /** environment contents **/ + CVector> slot_v_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end LocalEnv.hpp */ diff --git a/xo-interpreter/src/interpreter/CMakeLists.txt b/xo-interpreter/src/interpreter/CMakeLists.txt index e9fb9317..1cc1a277 100644 --- a/xo-interpreter/src/interpreter/CMakeLists.txt +++ b/xo-interpreter/src/interpreter/CMakeLists.txt @@ -3,8 +3,9 @@ set(SELF_LIB xo_interpreter) set(SELF_SRCS init_interpreter.cpp - StackFrame.cpp Schematika.cpp + LocalEnv.cpp + GlobalEnv.cpp VirtualSchematikaMachine.cpp ) diff --git a/xo-interpreter/src/interpreter/GlobalEnv.cpp b/xo-interpreter/src/interpreter/GlobalEnv.cpp new file mode 100644 index 00000000..dd94b6b5 --- /dev/null +++ b/xo-interpreter/src/interpreter/GlobalEnv.cpp @@ -0,0 +1,62 @@ +/** @file GlobalEnv.cpp **/ + +#include "GlobalEnv.hpp" +#include "xo/reflect/Reflect.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + + namespace scm { + gp + GlobalEnv::make_empty(gc::IAlloc * mm, const rp & symtab) + { + /* by design: GlobalEnv and GlobalEnv.slot_map_ are heap-allocated */ + + return new GlobalEnv(mm, symtab); + } + + GlobalEnv::GlobalEnv(gc::IAlloc * mm, + const rp & symtab) : mm_{mm}, symtab_{symtab} + {} + + TaggedPtr + GlobalEnv::self_tp() const + { + return Reflect::make_tp(const_cast(this)); + } + + void + GlobalEnv::display(std::ostream & os) const + { + os << ""; + } + + std::size_t + GlobalEnv::_shallow_size() const + { + /** 0: since not allocated in gc-space */ + return 0; + } + + Object * + GlobalEnv::_shallow_copy() const + { + /* by design, don't copy; not subject to GC */ + return const_cast(this); + } + + std::size_t + GlobalEnv::_forward_children() + { + /* All global slots are treated as GC roots; this means we + * don't have to forward them + * + * This works only as long as global env is immortal. + */ + return _shallow_size(); + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end GlobalEnv.cpp */ diff --git a/xo-interpreter/src/interpreter/LocalEnv.cpp b/xo-interpreter/src/interpreter/LocalEnv.cpp new file mode 100644 index 00000000..c68ce2bc --- /dev/null +++ b/xo-interpreter/src/interpreter/LocalEnv.cpp @@ -0,0 +1,149 @@ +s/** @file LocalEnv.cpp **/ + +#include "LocalEnv.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::StructReflector; + using xo::reflect::TypeDescrW; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescrExtra; + using xo::reflect::EstablishTypeDescr; + using xo::reflect::StlVectorTdx; + using xo::print::quot; + + namespace scm { + namespace { + std::size_t + slot_array_size(std::size_t n) { + return n * sizeof(gp); + } + } + + gp + LocalEnv::make(gc::IAlloc * mm, + gp p, + const rp & s, + std::size_t n) + { + if (s) { + assert(static_cast(n) == s->n_arg()); + } + + return new (MMPtr(mm)) LocalEnv(mm, p, s, n); + } + + LocalEnv::LocalEnv(gc::IAlloc * mm, + gp p, + const rp & s, + std::size_t n) : parent_{p}, + symtab_{s}, + slot_v_{mm, n} + {} + + TaggedPtr + LocalEnv::self_tp() const + { + return Reflect::make_tp(const_cast(this)); + } + + void + LocalEnv::display(std::ostream & os) const + { + os << ""; + } + + std::size_t + LocalEnv::_shallow_size() const + { + std::size_t retval = sizeof(LocalEnv); + + retval += gc::IAlloc::with_padding(slot_array_size(slot_v_.size())); + + return retval; + } + + Object * + LocalEnv::_shallow_copy() const + { + Cpof cpof(Object::mm, this); + + size_t z = size(); + + LocalEnv * copy = new (cpof) LocalEnv(cpof.mm_, parent_, symtab_, z); + + void * v_dest = copy->slot_v_.v_; + + if (slot_v_.v_) { + ::memcpy(v_dest, slot_v_.v_, slot_array_size(z)); + } + +#ifdef OBSOLETE + for (size_t i = 0, n = n_slot_; i < n; ++i) { + copy->v_[i] = v_[i]; + } +#endif + + return copy; + } + + std::size_t + LocalEnv::_forward_children() + { + static_assert(decltype(symtab_)::is_gc_ptr == false); + + Object::_forward_inplace(parent_); + // Object::_forward_inplace(symtab_); // not a gp yet + for (std::size_t i = 0, n = slot_v_.size(); i < n; ++i) { + Object::_forward_inplace((*this)[i]); + } + + return _shallow_size(); + } + + void + LocalEnv::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + /* reflect CVector> + * + * note: placement here works b/c CVector not used anywhere else + */ + using VectorType = CVector>; + + /* custom reflection for array of Object pointers. + * Can use StlVectorTdx here, treating CVector as a vector + * via .size() and .operator[] members + */ + std::unique_ptr tdx1 + = std::make_unique>(); + TypeDescrW td1 + = EstablishTypeDescr::establish(); + td1->assign_tdextra(Reflect::get_final_invoker(), + std::move(tdx1)); + + REFLECT_MEMBER(sr, parent); + REFLECT_MEMBER(sr, slot_v); + } + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end LocalEnv.cpp */ diff --git a/xo-interpreter/utest/LocalEnv.test.cpp b/xo-interpreter/utest/LocalEnv.test.cpp new file mode 100644 index 00000000..657f1293 --- /dev/null +++ b/xo-interpreter/utest/LocalEnv.test.cpp @@ -0,0 +1,134 @@ +/** @file LocalEnv.test.cpp **/ + +#include "xo/interpreter/init_interpreter.hpp" +#include "xo/interpreter/LocalEnv.hpp" +#include "xo/object/Integer.hpp" +#include "xo/alloc/GC.hpp" +#include +#include +#include + +namespace xo { + using xo::scm::LocalEnv; + using xo::obj::Integer; + using xo::gc::GC; + using xo::gc::ArenaAlloc; + using xo::gc::generation; + using xo::gc::generation_result; + using xo::reflect::TaggedPtr; + + namespace ut { + static InitEvidence s_init = (InitSubsys::require()); + + namespace { + struct Testcase_LocalEnv { + Testcase_LocalEnv(const std::vector & contents) : contents_{contents} {} + + /* build xo::obj::Integer for each contents_[i], store in F[i] for new LocalEnv F */ + std::vector contents_; + }; + + std::vector + s_testcase_v = { + Testcase_LocalEnv({}), + Testcase_LocalEnv({}), + Testcase_LocalEnv({111}), + Testcase_LocalEnv({111, 222}), + }; + } + + TEST_CASE("LocalEnv", "[LocalEnv][interpreter]") + { + Subsystem::initialize_all(); + + constexpr bool c_debug_flag = false; + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + scope log(XO_DEBUG(c_debug_flag), xtag("test", "LocalEnv2"), xtag("i_tc", i_tc)); + + const Testcase_LocalEnv & tc = s_testcase_v[i_tc]; + + up alloc = ArenaAlloc::make("utest", 16384, c_debug_flag); + REQUIRE(alloc.get()); + Object::mm = alloc.get(); + + std::size_t n = tc.contents_.size(); + gp frame = LocalEnv::make(alloc.get(), nullptr /*parent*/, nullptr /*symtab*/, n); + + TaggedPtr tp = frame->self_tp(); + + REQUIRE(tp.is_struct()); + } + } + + TEST_CASE("LocalEnv2", "[LocalEnv][gc][interpreter]") + { + Subsystem::initialize_all(); + + constexpr bool c_debug_flag = false; + + try { + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + scope log(XO_DEBUG(c_debug_flag), xtag("test", "LocalEnv2"), xtag("i_tc", i_tc)); + + const Testcase_LocalEnv & tc = s_testcase_v[i_tc]; + + up gc = GC::make( + {.initial_nursery_z_ = 16384, + .initial_tenured_z_ = 32768, + .incr_gc_threshold_ = 4096, + .full_gc_threshold_ = 4096, + .object_stats_flag_ = true, + .debug_flag_ = c_debug_flag, + }); + + REQUIRE(gc.get()); + + /* use gc for all Object allocs */ + GC * mm = gc.get(); + Object::mm = mm; + + std::size_t n = tc.contents_.size(); + + gp x = Integer::make(gc.get(), 42); + gc->add_gc_root(reinterpret_cast(&x)); + REQUIRE(gc->tospace_generation_of(x.ptr()) == generation_result::nursery); + + gp frame = LocalEnv::make(gc.get(), nullptr /*parent*/, nullptr /*symtab*/, n); + LocalEnv ** frame_pp = frame.ptr_address(); + gc->add_gc_root(reinterpret_cast(frame_pp)); + + /* verifying allocated in N1 */ + REQUIRE(gc->tospace_generation_of(frame.ptr()) == generation_result::nursery); + + for (std::size_t i = 0; i < n; ++i) + (*frame)[i] = Integer::make(mm, tc.contents_.at(i)); + + std::size_t expected_alloc_z = frame->_shallow_size(); + REQUIRE(expected_alloc_z >= sizeof(LocalEnv) + n * sizeof(gp)); + + gc->request_gc(generation::nursery); // <<<<<<<<< GC here <<<<<<<<< + + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); + + /* verify Integer x preserved across gc */ + REQUIRE(gc->tospace_generation_of(x.ptr()) == generation_result::nursery); + + /* verify LocalEnv preserved across gc */ + REQUIRE(gc->tospace_generation_of(frame.ptr()) == generation_result::nursery); + REQUIRE(frame->size() == n); + for (std::size_t i = 0; i < n; ++i) { + //REQUIRE(Integer::from(frame->lookup(i)).ptr()); + //REQUIRE(Integer::from(frame->lookup(i))->value() == tc.contents_.at(i)); + } + } + } catch (std::exception & ex) { + std::cerr << "exception: " << ex.what() << std::endl; + REQUIRE(false); + } + } + } +} + +/* end LocalEnv.test.cpp */