/** @file VirtualSchematikaMachine.cpp * * @author Roland Conybeare, Jan 2026 **/ #include "VirtualSchematikaMachine.hpp" #include "DPrimitive_gco_3_dict_string_gco.hpp" #include "DPrimitive_gco_2_gco_gco.hpp" #include "VsmDefContFrame.hpp" #include "VsmApplyFrame.hpp" #include "VsmEvalArgsFrame.hpp" #include "VsmApplyClosureFrame.hpp" #include "VsmIfElseContFrame.hpp" #include "VsmSeqContFrame.hpp" #include "VsmRcx.hpp" #include "Closure.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xo { using xo::scm::DDictionary; using xo::print::APrintable; using xo::print::ppconfig; using xo::print::ppstate_standalone; using xo::reflect::Reflect; using xo::mm::AGCObject; using xo::mm::MemorySizeInfo; using xo::mm::CollectorTypeRegistry; using xo::mm::AAllocator; using xo::mm::ACollector; using xo::mm::DX1Collector; using xo::mm::X1CollectorConfig; using xo::mm::DArena; using xo::facet::FacetRegistry; using xo::facet::TypeRegistry; using std::cout; namespace scm { bool VsmResult::is_error() const { return (*this->value() && obj::from(*(this->value()))); } namespace { /** helper function to create X1 collector instance **/ abox vsm_make_gc(obj aux_mm, const X1CollectorConfig & config) { auto retval = abox::make(aux_mm, config); // establish the set of types that mm_ will be able to collect CollectorTypeRegistry::instance().install_types(retval.to_op().to_facet()); return retval; } } // NOTE: using heap here for {DX1Collector, DArena, DVsmRcx} instances // (though DX1Collector allocations will be from explictly mmap'd memory) // DVirtualSchematikaMachine::DVirtualSchematikaMachine(const VsmConfig & config, obj aux_mm) : config_{config}, self_vroot_{obj(this)}, aux_mm_{aux_mm}, mm_(vsm_make_gc(aux_mm_, config.x1_config_)), rcx_(abox::make(aux_mm_, this)), reader_{config.rdr_config_, mm_.to_op(), aux_mm_} { { DArena * arena = new DArena(config_.error_config_); assert(arena); this->error_mm_.adopt(obj(arena)); } this->global_env_ = obj(reader_.global_env()); // TODO: // annoying to have to create self_vroot_ to appease // add_gc_root_poly() signature. // In practice gc won't modify the pointer // (instead traverses it to find +update children) // mm_.to_op().to_facet().add_gc_root_poly(&self_vroot_); } DVirtualSchematikaMachine * DVirtualSchematikaMachine::_make(obj mm, const VsmConfig & config, obj aux_mm) { void * mem = mm.alloc_for(); return new (mem) DVirtualSchematikaMachine(config, aux_mm); } obj DVirtualSchematikaMachine::make(obj mm, const VsmConfig & config, obj aux_mm) { return obj( _make(mm, config, aux_mm)); } obj DVirtualSchematikaMachine::allocator() const noexcept { return mm_.to_op(); } obj DVirtualSchematikaMachine::error_allocator() const noexcept { return error_mm_.to_op(); } StringTable * DVirtualSchematikaMachine::stringtable() noexcept { return reader_.stringtable(); } bool DVirtualSchematikaMachine::is_at_toplevel() const noexcept { return reader_.is_at_toplevel(); } void DVirtualSchematikaMachine::visit_pools(const MemorySizeVisitor & visitor) const { aux_mm_.visit_pools(visitor); mm_.visit_pools(visitor); error_mm_.visit_pools(visitor); reader_.visit_pools(visitor); } void DVirtualSchematikaMachine::begin_interactive_session() { reader_.begin_interactive_session(); } void DVirtualSchematikaMachine::begin_batch_session() { reader_.begin_batch_session(); } VsmResultExt DVirtualSchematikaMachine::read_eval_print(span_type input, bool eof) { if (input.empty()) { return VsmResultExt(); } reader_.reset_result(); auto [expr, remaining, tk_error] = reader_.read_expr(input, eof); if (!expr) { if (tk_error.is_error()) { // tokenizer error -> convert to runtime error DString * src = DString::from_view(mm_.to_op(), tk_error.src_function()); DString * msg = tk_error.report_to_string(mm_.to_op()); auto error = obj(DRuntimeError::_make(mm_.to_op(), src, msg)); this->value_ = VsmResult(error); { obj error_pr = FacetRegistry::instance().variant(error); ppconfig ppc; ppstate_standalone pps(&cout, 0, &ppc); pps.prettyn(error_pr); } return VsmResultExt(value_, remaining); } else { // incomplete input return VsmResultExt(VsmResult(), remaining); } } // here: have obtained complete input expression const VsmResult & evalresult = this->start_eval(expr); if (evalresult.is_error()) { // TODO: print error here return VsmResultExt(evalresult, remaining); } assert(evalresult.is_value()); obj value = evalresult.result_; assert(value); { obj value_pr = FacetRegistry::instance().variant(value); // pretty_toplevel(value_pr, &cout, ppconfig()); ppconfig ppc; ppstate_standalone pps(&cout, 0, &ppc); pps.prettyn(value_pr); } return VsmResultExt(evalresult, remaining); } const VsmResult & DVirtualSchematikaMachine::start_eval(obj expr) { this->pc_ = VsmInstr::c_eval; this->expr_ = expr; this->value_ = VsmResult(obj()); this->cont_ = VsmInstr::c_halt; this->run(); return value_; } void DVirtualSchematikaMachine::run() { while (this->execute_one()) ; } bool DVirtualSchematikaMachine::execute_one() { scope log(XO_DEBUG(config_.debug_flag_)); log && log(xtag("pc", pc_), xtag("cont", cont_)); auto expr_pr = expr_.to_facet(); if (expr_pr) log && log(xtag("expr", expr_pr)); if (value_.value()) { auto value_pr = const_cast *>(value_.value())->to_facet(); if (value_pr) log && log(xtag("value", value_pr)); } else { log && log("value not present or tk error"); } auto stack_pr = stack_.to_facet(); if (stack_pr) log && log(xtag("stack", stack_pr)); switch (pc_.opcode()) { case vsm_opcode::sentinel: case vsm_opcode::halt: case vsm_opcode::N: return false; case vsm_opcode::eval: _do_eval_op(); break; case vsm_opcode::apply: _do_apply_op(); break; case vsm_opcode::evalargs: _do_evalargs_op(); break; case vsm_opcode::def_cont: _do_def_cont_op(); break; case vsm_opcode::apply_cont: _do_apply_cont_op(); break; case vsm_opcode::ifelse_cont: _do_ifelse_cont_op(); break; case vsm_opcode::seq_cont: _do_seq_cont_op(); break; } return true; } void DVirtualSchematikaMachine::_do_eval_op() { switch(expr_.extype()) { case exprtype::invalid: case exprtype::N: break; case exprtype::constant: _do_eval_constant_op(); break; case exprtype::define: _do_eval_define_op(); break; case exprtype::lambda: _do_eval_lambda_op(); break; case exprtype::variable: _do_eval_variable_op(); break; case exprtype::varref: _do_eval_varref_op(); break; case exprtype::apply: _do_eval_apply_op(); break; case exprtype::ifexpr: _do_eval_if_else_op(); break; case exprtype::sequence: _do_eval_sequence_op(); break; } } void DVirtualSchematikaMachine::_do_eval_constant_op() { auto expr = obj::from(expr_); this->value_ = VsmResult(expr.data()->value()); this->pc_ = this->cont_; this->cont_ = VsmInstr::c_sentinel; } void DVirtualSchematikaMachine::_do_eval_define_op() { scope log(XO_DEBUG(true)); auto def_expr = obj::from(expr_); if (local_env_) { // nested defines implemented by rewriting, // so this branch should be unreachable assert(false); } else { // top-level define // .stack_ --+ // | // v // +------DVsmDefContFrame------+ // | .parent x | .cont | .def x | // +---------|-+-------+------|-+ // | | // ParserStack* <-----/ | // | // v // DDefineExpr /* stack frame for nested continuation * (to perform assignment) */ auto defcont_frame = obj (DVsmDefContFrame::make(mm_.to_op(), this->stack_ /*saved stack*/, this->cont_ /*saved cont*/, def_expr.data() /*saved expr*/)); this->stack_ = defcont_frame; // setup evaluation of rhs this->expr_ = def_expr->rhs(); this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_def_cont; } } void DVirtualSchematikaMachine::_do_def_cont_op() { // see DVsmDefContFrame auto frame = obj::from(stack_); assert(frame); assert(value_.is_value()); // TODO: verify that value satisfies expected type ? DVariable * lhs = frame->def_expr()->lhs(); obj rhs = *value_.value(); assert(lhs->path().is_global()); global_env_->assign_value(mm_.to_op(), lhs->path(), rhs); // TODO: unfortunate const_cast here, because obj<> doesn't support const DRepr yet this->value_ = VsmResult(obj(const_cast(lhs->name()))); this->stack_ = frame->parent(); this->pc_ = frame->cont(); this->cont_ = VsmInstr::c_sentinel; } void DVirtualSchematikaMachine::_do_eval_lambda_op() { // assuming bump allocator // // +----------- DArray---------+ +-------------DLocalEnv-----------+ +-----DClosure-------+ // | .cap |.size | .elts_[]... |h| .parent x | .symtab x | .args x |h| .lambda x | .env x | // +------+------+-------------+ +---------|-+---------|-+-------|-+ +---------|-+------|-+ // ^ ^ | | | | | // \-----------------------------|---------|-----------|---------/ | | // | | | | | // \---------|-----------|-----------------------|--------/ // | | | // <--------------------------------------/ | | // | | // v v // DLocalSymtab DLambdaExpr // // DClosure runtime procedure (created below) // DArray bound non-local variables (established by VSM) // DLocalEnv local environment (copy ref from VSM state) // h alloc header // DLocalSymtab local symbol table (created by parser) // DLambdaExpr lambda expression (created by parser) // // will create DClosure with local_env_ // local_env_ // global_env_ auto lambda = obj::from(expr_); DClosure * closure = DClosure::make(mm_.to_op(), lambda.data(), local_env_.data()); this->value_ = VsmResult(obj(obj(closure))); this->pc_ = this->cont_; this->cont_ = VsmInstr::c_sentinel; } void DVirtualSchematikaMachine::_do_eval_variable_op() { // not implemented assert(false); } void DVirtualSchematikaMachine::_do_eval_varref_op() { auto var = obj::from(expr_); Binding b = var->path(); obj value; if (b.is_local() && local_env_) { value = local_env_->lookup_value(b); } else if (b.is_global()) { value = global_env_->lookup_value(b); } if (value) { this->value_ = VsmResult(value); this->pc_ = this->cont_; this->cont_ = VsmInstr::c_sentinel; return; } // no local or global binding auto error = DRuntimeError::make(mm_.to_op(), "_do_eval_varref_op", "no binding for variable"); this->value_ = VsmResult(error); // for now: halt VSM execution // TODO: some combination of // 1. emit stack trace // 2. go to debugger // 3. have every vsm instruction check inputs for errors this->pc_ = VsmInstr::c_halt; this->cont_ = VsmInstr::c_sentinel; } void DVirtualSchematikaMachine::_do_eval_apply_op() { // ApplyExpr in expr_ register // assuming bump allocator: // // DArray VsmApplyFrame VsmEvalArgsFrame // v v v // +----------------------+-------+-------+----+--------+-------+-------+-------+ // | argument expressions | par x | cont1 | fn | args x | par x | cont2 | i_arg | // +----------------------+-----|-+-------+----+------|-+-----|-+-------+-------+ // ^ ^ | | | // | \-----------------------------------/ // \ | / // \------------------------------------------------/ // / // <---------------------------/ // // - VsmEvalArgsFrame: owned by VSM, state for evalargs loop // - VsmApplyFrame: owned by VSM, state for transferring control to called function // - DArray: contains evaluated args; owned by called primitive // - cont2: always c_apply // auto apply = obj::from(expr_); // accumulate evaluated arguments here DArray * args = DArray::_empty(mm_.to_op(), apply->n_args()); // TODO: check function signature DVsmApplyFrame * apply_frame = DVsmApplyFrame::make(mm_.to_op(), stack_, cont_, args); auto evalargs_frame = obj (DVsmEvalArgsFrame::make(mm_.to_op(), apply_frame, VsmInstr::c_apply, apply.data())); this->stack_ = evalargs_frame; // Setup evaluation of first argument. No new stack for this. this->expr_ = apply->fn(); this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_evalargs; } void DVirtualSchematikaMachine::_do_eval_if_else_op() { // control: // self -> eval(test) -> ifelse_cont -> eval(when_true) // -> eval(when_false) auto ifelse_expr = obj::from(expr_); obj ifelse_frame (DVsmIfElseContFrame::make(mm_.to_op(), stack_, cont_, ifelse_expr.data())); this->stack_ = ifelse_frame; this->expr_ = ifelse_expr->test(); this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_ifelse_cont; } void DVirtualSchematikaMachine::_do_eval_sequence_op() { // stack: // // VsmEvalSequence // v // +-------+------+-------+-------+ // | par x | cont | seq | i_elt | // +-----|-+------+-------+-------+ // | // <-----/ // auto seq_expr = obj::from(expr_); if (seq_expr->size() == 0) { /* empty sequence expression does not produce a value */ this->value_ = VsmResult(obj()); this->pc_ = this->cont_; return; } auto seqexpr_frame = obj (DVsmSeqContFrame::make(mm_.to_op(), this->stack_ /*saved stack*/, this->cont_ /*saved cont*/, seq_expr.data() /*saved expr*/, 0 /*index of seq element*/)); this->stack_ = seqexpr_frame; // Setup evaluation of first sequence element this->expr_ = (*seq_expr.data())[0]; this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_seq_cont; } void DVirtualSchematikaMachine::_do_apply_op() { // rcx_ : runtime context // fn_ : function to call // args_ : array of arguments // TODO: check argument types auto closure = obj::from(fn_); if (closure) { _do_call_closure_op(); return; } else { _do_call_primitive_op(); return; } } void DVirtualSchematikaMachine::_do_call_closure_op() { // We need to preserve registers while evaluating // lambda body auto closure = obj::from(fn_); assert(closure); // 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_apply_cont) { // 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 frame( DVsmApplyClosureFrame::make(mm_.to_op(), stack_, cont_, local_env_.data())); // push frame w/ saved vsm registers this->stack_ = frame; this->cont_ = VsmInstr::c_apply_cont; } auto lambda = closure->lambda(); auto local_env = DLocalEnv::_make(mm_.to_op(), local_env_.data(), lambda->local_symtab(), args_.data()); this->local_env_ = obj(local_env); this->expr_ = lambda->body_expr(); this->pc_ = VsmInstr::c_eval; // cont_ already established } void DVirtualSchematikaMachine::_do_call_primitive_op() { auto fn = fn_.to_facet(); this->value_ = VsmResult(fn.apply_nocheck(rcx_.to_op(), args_.data())); this->pc_ = cont_; this->cont_ = VsmInstr::c_sentinel; } void DVirtualSchematikaMachine::_do_evalargs_op() { scope log(XO_DEBUG(false)); if (!value_.is_value()) { // error while evaluating function arg log.retroactively_enable(); log && log("error in apply -> terminating app"); this->pc_ = VsmInstr::c_halt; this->cont_ = VsmInstr::c_sentinel; return; } // here: nested evaluation succeeded // value of one of {fn, arg(i), ..} in fn(arg0 .. arg(n-1)) // obj value = *(value_.value()); // value_ in [i_arg] value_ // . (if i_arg >= 0) . (if i_arg = -1) // . . // DArray . VsmApplyFrame . VsmEvalArgsFrame // v v v v v // +----------------------+-------+-------+----+--------+-------+-------+--------+-------+ // | argument expressions | par o | cont1 | fn | args x | par o | cont2 | applyx | i_arg | // +----------------------+-----|-+-------+----+------|-+-----|-+-------+--------+-------+ // ^ ^ | | | // | \-----------------------------------/ // \ | / // \------------------------------------------------/ // / // <---------------------------/ // // - VsmEvalArgsFrame: owned by VSM, state for evalargs loop // - VsmApplyFrame: owned by VSM, state for transferring control to called function // - DArray: contains evaluated args; owned by called primitive // - i_arg // if -1: value_ register holds function // if >=0: value_ register holds i'th function argument // auto evalargs_frame = obj::from(stack_); assert(evalargs_frame); int32_t i_arg = evalargs_frame->i_arg(); DVsmApplyFrame * apply_frame = evalargs_frame->parent(); const DApplyExpr * apply_expr = evalargs_frame->apply_expr(); if (i_arg == -1) { bool is_closure = obj::from(value); bool is_native_fn = value.try_to_facet(); if (is_native_fn || is_closure) { apply_frame->assign_fn(value); i_arg = evalargs_frame->increment_arg(); // now i_arg is 0 -> evaluate that argument if (i_arg >= static_cast(apply_expr->n_args())) { // corner case: function with 0 arguments this->fn_ = apply_frame->fn(); // = value; this->args_ = obj(apply_frame->args()); // empty this->stack_ = apply_frame->parent(); this->pc_ = VsmInstr::c_apply; this->cont_ = apply_frame->cont(); return; } else { this->expr_ = apply_expr->arg(i_arg); this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_evalargs; return; } } else { // error - function position must deliver something with AProcedure? // or DClosure, but we'll get to that. log.retroactively_enable(); log("expected procedure in function position -> terminate"); assert(false); } } else { DArray * args = apply_frame->args(); log && log(xtag("i_arg", i_arg), xtag("n_arg", args->size()), xtag("cap", args->capacity())); //auto gc = mm_.to_op().to_facet(); args->push_back(mm_.to_op(), value); i_arg = evalargs_frame->increment_arg(); if (i_arg == static_cast(apply_expr->n_args())) { // all apply-arguments have been evaluated // -> done with VsmEvalArgsFrame // this->fn_ = apply_frame->fn(); this->args_ = obj(apply_frame->args()); this->stack_ = apply_frame->parent(); this->pc_ = VsmInstr::c_apply; this->cont_ = apply_frame->cont(); return; } else { this->expr_ = apply_expr->arg(i_arg); this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_evalargs; return; } } // not implemented assert(false); } void DVirtualSchematikaMachine::_do_apply_cont_op() { // see DVsmApplyClosureFrame auto frame = obj::from(stack_); assert(frame); this->stack_ = frame->parent(); this->local_env_ = obj(frame->local_env()); this->pc_ = frame->cont(); this->cont_ = VsmInstr::c_sentinel; } void DVirtualSchematikaMachine::_do_ifelse_cont_op() { // pre: result of evaluating test condition in value_ register auto frame = obj::from(stack_); assert(frame); assert(value_.is_value()); auto flag = obj::from(*value_.value()); if (flag.data()) { obj next_expr; { if (flag->value()) { // proceed with if-branch next_expr = frame->ifelse_expr()->when_true(); } else { // proceed with else-branch next_expr = frame->ifelse_expr()->when_false(); } } this->stack_ = frame->parent(); this->expr_ = next_expr; this->pc_ = VsmInstr::c_eval; this->cont_ = frame->cont(); } else { auto error = DRuntimeError::make(mm_.to_op(), "_do_ifelse_cont_op", "expected boolean for test condition"); this->value_ = VsmResult(error); // for now: halt VSM execution // TODO: some combination of // 1. emit stack trace // 2. go to debugger // 3. have every vsm instruction check inputs for errors this->pc_ = VsmInstr::c_halt; this->cont_ = VsmInstr::c_sentinel; } } void DVirtualSchematikaMachine::_do_seq_cont_op() { auto frame = obj::from(stack_); assert(frame); uint32_t i_seq = 1 + frame->i_seq(); auto seq_expr = frame->seq_expr(); assert(seq_expr); if (i_seq == seq_expr->size()) { /* done with sequence * value of sequence-expr is the value of the last expression in that sequence, * which is already in the value_ register */ this->stack_ = frame->parent(); this->pc_ = frame->cont(); this->cont_ = VsmInstr::c_sentinel; return; } else { frame->incr_i_seq(); this->expr_ = (*seq_expr)[i_seq]; this->pc_ = VsmInstr::c_eval; this->cont_ = VsmInstr::c_seq_cont; return; } } std::size_t DVirtualSchematikaMachine::shallow_size() const noexcept { return sizeof(DVirtualSchematikaMachine); } DVirtualSchematikaMachine * DVirtualSchematikaMachine::gco_shallow_move(obj gc) noexcept { // need move-ctor on abox<..> //return gc.std_move_for(this); (void)gc; /** TODO: should be able to use gc.std_move_for(this) now * that shallow_move uses move construction. * DVirtualSchematikaMachine is (or can be made) moveable. **/ assert(false); return nullptr; } void DVirtualSchematikaMachine::visit_gco_children(VisitReason reason, obj gc) noexcept { reader_.visit_gco_children(reason, gc); gc.visit_child(reason, &stack_); gc.visit_poly_child(reason, &expr_); gc.visit_child(reason, &global_env_); gc.visit_child(reason, &local_env_); gc.visit_child(reason, &fn_); gc.visit_child(reason, &args_); if (value_.is_value()) { gc.visit_child(reason, const_cast *>(&value_.value_ref())); } } } /*namespace scm*/ } /*namespace xo*/ /* end DVirtualSchematikaMachine.cpp */