From 27096ddbf8a647d214fda7d992faecc3131ec808 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Nov 2025 20:10:23 -0500 Subject: [PATCH] xo-interpreter adds + explict mm arg to ctors (retiring Object::mm) --- CMakeLists.txt | 3 +- cmake/xo-bootstrap-macros.cmake | 35 ++++++ cmake/xo_interpreterConfig.cmake.in | 7 ++ docs/CMakeLists.txt | 9 ++ docs/conf.py | 39 ++++++ include/xo/interpreter/StackFrame.hpp | 52 +++++++- include/xo/interpreter/init_interpreter.hpp | 21 ++++ src/interpreter/CMakeLists.txt | 13 ++ src/interpreter/StackFrame.cpp | 120 ++++++++++++++++++ src/interpreter/init_interpreter.cpp | 27 ++++ utest/CMakeLists.txt | 12 ++ utest/StackFrame.test.cpp | 133 ++++++++++++++++++++ utest/interpreter_utest_main.cpp | 6 + 13 files changed, 472 insertions(+), 5 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_interpreterConfig.cmake.in create mode 100644 docs/CMakeLists.txt create mode 100644 docs/conf.py create mode 100644 include/xo/interpreter/init_interpreter.hpp create mode 100644 src/interpreter/CMakeLists.txt create mode 100644 src/interpreter/StackFrame.cpp create mode 100644 src/interpreter/init_interpreter.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/StackFrame.test.cpp create mode 100644 utest/interpreter_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 132e7b86..370a9650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,13 +20,12 @@ add_definitions(${PROJECT_CXX_FLAGS}) # note on ordering: must read .cmake defn of lib before configuring any examples add_subdirectory(src/interpreter) - xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- #add_subdirectory(example) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_interpreterConfig.cmake.in b/cmake/xo_interpreterConfig.cmake.in new file mode 100644 index 00000000..1cf48ccd --- /dev/null +++ b/cmake/xo_interpreterConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_alloc) +#find_dependency(xo_flatstring) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..b9df2dd6 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,9 @@ +# xo-alloc/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst install.rst) + +# see xo-reader/doc or xo-unit/doc for working examples +# example.rst install.rst implementation.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..e5855955 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo interpreter documentation' +copyright = '2025, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.mathjax", # inline math + "sphinx.ext.autodoc", # generate info from docstrings + "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.plantuml" # text -> uml diagrams + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' diff --git a/include/xo/interpreter/StackFrame.hpp b/include/xo/interpreter/StackFrame.hpp index c18f6bae..310229ca 100644 --- a/include/xo/interpreter/StackFrame.hpp +++ b/include/xo/interpreter/StackFrame.hpp @@ -1,6 +1,8 @@ /** @file StackFrame.hpp **/ +#include "xo/alloc/IAlloc.hpp" #include "xo/alloc/Object.hpp" +#include namespace xo { namespace scm { @@ -8,15 +10,59 @@ namespace xo { * @brief Represent a single runtime stack frame for a Schematika function * * StackFrame intended to be used for interpreted functions. - * Compiled functions will stil likely have stack frames, but need not use the + * Compiled functions will still likely have stack frames, but need not use the * @ref StackFrame class + * + * memory layout: + * + * +------------+ + * | vtable | + * +------------+ + * | .n_ | + * +------------+ + * | .v_ +------\ + * +------------+ <--/ + * | .v_[0] | + * +------------+ + * . .. . + * +------------+ + * | .v_[.n_-1] | + * +------------+ **/ class StackFrame : public Object { public: - StackFrame(std::size_t n_slot) + using TaggedPtr = xo::reflect::TaggedPtr; + + public: + StackFrame(gc::IAlloc * mm, std::size_t n_slot); + + /** create frame using allocator @p mm, + * with exactly @p n_slot object pointers + **/ + static gp make(gc::IAlloc * mm, std::size_t n_slot); + + /** reflect StackFrame object representation **/ + static void reflect_self(); + + std::size_t n_slot() const { return n_slot_; } + gp lookup(std::size_t i) const { return v_[i]; } + gp & lookup(std::size_t i) { return v_[i]; } + + gp operator[](std::size_t i) const { return lookup(i); } + gp & operator[](std::size_t i) { return lookup(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: - + /** number of elements in frame **/ + std::size_t n_slot_ = 0; + /** contiguous array of object pointers: v[0] .. v[n-1] **/ + gp * v_ = nullptr; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/interpreter/init_interpreter.hpp b/include/xo/interpreter/init_interpreter.hpp new file mode 100644 index 00000000..91e4828c --- /dev/null +++ b/include/xo/interpreter/init_interpreter.hpp @@ -0,0 +1,21 @@ +/** @file init_interpreter.hpp + * + * author: Roland Conybeare, Nov 2025 + **/ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the interpreter/ subsystem in ordered initialization */ + enum S_interpreter_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} + +/* end init_interpreter.hpp */ diff --git a/src/interpreter/CMakeLists.txt b/src/interpreter/CMakeLists.txt new file mode 100644 index 00000000..d0824e34 --- /dev/null +++ b/src/interpreter/CMakeLists.txt @@ -0,0 +1,13 @@ +# interpreter/CMakeLists.txt + +set(SELF_LIB xo_interpreter) +set(SELF_SRCS + init_interpreter.cpp + StackFrame.cpp + VirtualSchematikaMachine.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} xo_alloc) +#xo_dependency(${SELF_LIB} xo_reader) +xo_headeronly_dependency(${SELF_LIB} subsys) diff --git a/src/interpreter/StackFrame.cpp b/src/interpreter/StackFrame.cpp new file mode 100644 index 00000000..dcfec083 --- /dev/null +++ b/src/interpreter/StackFrame.cpp @@ -0,0 +1,120 @@ +/** @file StackFrame.cpp **/ + +#include "StackFrame.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::TaggedPtr; + using xo::print::quot; + + namespace scm { + namespace { + std::size_t + slot_array_size(std::size_t n) { + return n * sizeof(gp); + } + } + + StackFrame::StackFrame(gc::IAlloc * mm, std::size_t n_slot) + : n_slot_{n_slot}, v_{nullptr} + { + if (n_slot > 0) { + std::byte * mem = mm->alloc(slot_array_size(n_slot)); + + this->v_ = new (mem) gp[n_slot]; + } + } + + gp + StackFrame::make(gc::IAlloc * mm, std::size_t n_slot) + { + return new (MMPtr(mm)) StackFrame(mm, n_slot); + } + + TaggedPtr + StackFrame::self_tp() const + { + return Reflect::make_tp(const_cast(this)); + } + + void + StackFrame::display(std::ostream & os) const + { + os << ""; + } + + std::size_t + StackFrame::_shallow_size() const + { + std::size_t retval = sizeof(StackFrame); + + retval += gc::IAlloc::with_padding(slot_array_size(n_slot_)); + + return retval; + } + + Object * + StackFrame::_shallow_copy() const + { + Cpof cpof(Object::mm, this); + + StackFrame * copy = new (cpof) StackFrame(cpof.mm_, n_slot_); + + void * v_dest = copy->v_; + + if (v_) { + ::memcpy(v_dest, v_, slot_array_size(n_slot_)); + } + +#ifdef OBSOLETE + for (size_t i = 0, n = n_slot_; i < n; ++i) { + copy->v_[i] = v_[i]; + } +#endif + + return copy; + } + + std::size_t + StackFrame::_forward_children() + { + for (std::size_t i = 0, n = n_slot_; i < n; ++i) { + Object::_forward_inplace(lookup(i)); + } + + return _shallow_size(); + } + + void + StackFrame::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, n_slot); + + // non-trivial to reflect frame members, + // effectively need separate reflection for each cardinality; + // or: reflect .v_[] as nested element + } + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end StackFrame.cpp */ diff --git a/src/interpreter/init_interpreter.cpp b/src/interpreter/init_interpreter.cpp new file mode 100644 index 00000000..3f6565ff --- /dev/null +++ b/src/interpreter/init_interpreter.cpp @@ -0,0 +1,27 @@ +/** @file init_interpreter.cpp + * + * author: Roland Conybeare, Nov 2025 + */ + +#include "init_interpreter.hpp" +#include "StackFrame.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + using xo::scm::StackFrame; + + void + InitSubsys::init() + { + StackFrame::reflect_self(); + } + + InitEvidence + InitSubsys::require() + { + return Subsystem::provide("interpreter", &init); + } + +} /*namespace xo*/ + +/* end init_interpreter.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..5eebba74 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,12 @@ +# build unittest interpreter/utest + +set(UTEST_EXE utest.interpreter) +set(UTEST_SRCS + interpreter_utest_main.cpp + StackFrame.test.cpp +) + +xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) +xo_self_dependency(${UTEST_EXE} xo_interpreter) +xo_dependency(${UTEST_EXE} xo_object) +xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) diff --git a/utest/StackFrame.test.cpp b/utest/StackFrame.test.cpp new file mode 100644 index 00000000..5a64c248 --- /dev/null +++ b/utest/StackFrame.test.cpp @@ -0,0 +1,133 @@ +/** @file StackFrame.test.cpp **/ + +#include "xo/interpreter/init_interpreter.hpp" +#include "xo/interpreter/StackFrame.hpp" +#include "xo/object/Integer.hpp" +#include "xo/alloc/GC.hpp" +#include +#include +#include + +namespace xo { + using xo::scm::StackFrame; + 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_StackFrame { + Testcase_StackFrame(const std::vector & contents) : contents_{contents} {} + + /* build xo::obj::Integer for each contents_[i], store in F[i] for new StackFrame F */ + std::vector contents_; + }; + + std::vector + s_testcase_v = { + Testcase_StackFrame({}), + Testcase_StackFrame({}), + Testcase_StackFrame({111}), + }; + } + + TEST_CASE("StackFrame", "[StackFrame][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", "StackFrame2"), xtag("i_tc", i_tc)); + + const Testcase_StackFrame & 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 = StackFrame::make(alloc.get(), n); + + TaggedPtr tp = frame->self_tp(); + + REQUIRE(tp.is_struct()); + } + } + + TEST_CASE("StackFrame2", "[StackFrame][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", "StackFrame2"), xtag("i_tc", i_tc)); + + const Testcase_StackFrame & 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 = StackFrame::make(gc.get(), n); + StackFrame ** 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->lookup(i) = Integer::make(mm, tc.contents_.at(i)); + + std::size_t expected_alloc_z = frame->_shallow_size(); + REQUIRE(expected_alloc_z >= sizeof(StackFrame) + 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 StackFrame preserved across gc */ + REQUIRE(gc->tospace_generation_of(frame.ptr()) == generation_result::nursery); + REQUIRE(frame->n_slot() == 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 StackFrame.test.cpp */ diff --git a/utest/interpreter_utest_main.cpp b/utest/interpreter_utest_main.cpp new file mode 100644 index 00000000..e385f9b4 --- /dev/null +++ b/utest/interpreter_utest_main.cpp @@ -0,0 +1,6 @@ +/** @file interpreter_utest_main.cpp **/ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end interpreter_utest_main.cpp */