diff --git a/CMakeLists.txt b/CMakeLists.txt index e000ceb1..91826ebf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(ZenKit VERSION 1.3.0) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) option(ZK_BUILD_EXAMPLES "ZenKit: Build the examples." OFF) option(ZK_BUILD_TESTS "ZenKit: Build the test suite." ON) diff --git a/include/zenkit/DaedalusScript.hh b/include/zenkit/DaedalusScript.hh index a5ce2cf8..f62f24bd 100644 --- a/include/zenkit/DaedalusScript.hh +++ b/include/zenkit/DaedalusScript.hh @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,7 @@ namespace zenkit { static constexpr auto EXTERNAL = 1U << 3U; ///< The symbol refers to an external function. static constexpr auto MERGED = 1U << 4U; ///< Unused. static constexpr auto TRAP_ACCESS = 1U << 6U; ///< VM should call trap callback, when symbol accessed. + static constexpr auto FUNC_LOCALS = 1U << 7U; ///< VM should call trap callback, when symbol accessed. // Deprecated entries. ZKREM("renamed to DaedalusSymbolFlag::CONST") static constexpr auto const_ = CONST; @@ -556,6 +558,10 @@ namespace zenkit { /// \param enable true to enable and false to disable ZKAPI void set_access_trap_enable(bool enable) noexcept; + /// \brief Allows function associated with this symbol to stash local variables on a stack during recursion. + /// \param enable true to enable and false to disable + ZKAPI void set_local_variables_enable(bool enable) noexcept; + /// \brief Tests whether the symbol is a constant. /// \return `true` if the symbol is a constant, `false` if not. [[nodiscard]] ZKAPI bool is_const() const noexcept { @@ -598,6 +604,12 @@ namespace zenkit { return (_m_flags & DaedalusSymbolFlag::RETURN) != 0; } + /// \brief brief Tests whether the symbol has local-variables enabled. + /// \return return `true` if enabled, `false` if not. + [[nodiscard]] ZKAPI bool has_local_variables_enabled() const noexcept { + return (_m_flags & DaedalusSymbolFlag::FUNC_LOCALS) != 0; + } + /// \return The name of the symbol. [[nodiscard]] ZKAPI std::string const& name() const noexcept { return _m_name; @@ -799,7 +811,7 @@ namespace zenkit { /// \brief Looks for parameters of the given function symbol. Only works for external functions. /// \param parent The function symbol to get the parameter symbols for. /// \return A list of function parameter symbols. - [[nodiscard]] ZKAPI std::vector + [[nodiscard]] ZKAPI std::span find_parameters_for_function(DaedalusSymbol const* parent) const; /// \brief Retrieves the symbol with the given \p address set @@ -825,7 +837,12 @@ namespace zenkit { /// \brief Looks for parameters of the given function symbol. Only works for external functions. /// \param parent The function symbol to get the parameter symbols for. /// \return A list of function parameter symbols. - [[nodiscard]] ZKAPI std::vector find_parameters_for_function(DaedalusSymbol const* parent); + [[nodiscard]] ZKAPI std::span find_parameters_for_function(DaedalusSymbol const* parent); + + /// \brief Looks for local-variables of the given function symbol. + /// \param parent The function symbol to get the parameter symbols for. + /// \return A list of function local-variable symbols. + [[nodiscard]] ZKAPI std::span find_locals_for_function(DaedalusSymbol const* parent); /// \brief Retrieves the symbol with the given \p name. /// \param name The name of the symbol to get. diff --git a/include/zenkit/DaedalusVm.hh b/include/zenkit/DaedalusVm.hh index 96f0d4b7..87894c23 100644 --- a/include/zenkit/DaedalusVm.hh +++ b/include/zenkit/DaedalusVm.hh @@ -132,7 +132,7 @@ namespace zenkit { throw DaedalusVmException {"Cannot call " + sym->name() + ": not a function"}; } - std::vector params = find_parameters_for_function(sym); + std::span params = find_parameters_for_function(sym); if (params.size() < sizeof...(P)) { throw DaedalusVmException {"too many arguments provided for " + sym->name() + ": given " + std::to_string(sizeof...(P)) + " expected " + std::to_string(params.size())}; @@ -439,7 +439,7 @@ namespace zenkit { if (sym->has_return()) throw DaedalusIllegalExternalReturnType(sym, "void"); } - std::vector params = find_parameters_for_function(sym); + std::span params = find_parameters_for_function(sym); if (params.size() < sizeof...(P)) throw DaedalusIllegalExternalDefinition {sym, "too many arguments declared for external " + sym->name() + @@ -526,7 +526,7 @@ namespace zenkit { if (sym->has_return()) throw DaedalusIllegalExternalReturnType(sym, "void"); } - std::vector params = find_parameters_for_function(sym); + std::span params = find_parameters_for_function(sym); if (params.size() < sizeof...(P)) throw DaedalusIllegalExternalDefinition { sym, @@ -714,12 +714,23 @@ namespace zenkit { /// \param sym The symbol referring to the function called. ZKINT void push_call(DaedalusSymbol const* sym); - /// \brief Pops a call stack from from the call stack. + /// \brief Pops a call stack frame from the call stack. /// /// This method restores the interpreter's state to before the function which the /// call stack entry refers to was called. ZKINT void pop_call(); + /// \brief Pushes a function local variables onto the call stack. + /// + /// Part of #push_call implementation + /// \param sym The symbol referring to the function called. + ZKINT void push_local_variables(DaedalusSymbol const* sym); + + /// \brief Pops a function-local variables from the call stack. + /// + /// Part of #pop_call implementation + ZKINT void pop_local_variables(DaedalusSymbol const* sym); + /// \brief Checks that the type of each symbol in the given set of defined symbols matches the given type /// parameters. /// @@ -734,20 +745,20 @@ namespace zenkit { /// \throws DaedalusIllegalExternalParameter If the types don't match. /// \note Requires that sizeof...(Px) + 1 == defined.size(). template - void check_external_params(std::vector const& defined) { + void check_external_params(std::span const& defined) { if constexpr (is_instance_ptr_v

|| std::is_same_v || is_raw_instance_ptr_v

) { - if (defined[i]->type() != DaedalusDataType::INSTANCE) - throw DaedalusIllegalExternalParameter(defined[i], "instance", i + 1); + if (defined[i].type() != DaedalusDataType::INSTANCE) + throw DaedalusIllegalExternalParameter(&defined[i], "instance", i + 1); } else if constexpr (std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::FLOAT) - throw DaedalusIllegalExternalParameter(defined[i], "float", i + 1); + if (defined[i].type() != DaedalusDataType::FLOAT) + throw DaedalusIllegalExternalParameter(&defined[i], "float", i + 1); } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::INT && defined[i]->type() != DaedalusDataType::FUNCTION) - throw DaedalusIllegalExternalParameter(defined[i], "int/func", i + 1); + if (defined[i].type() != DaedalusDataType::INT && defined[i].type() != DaedalusDataType::FUNCTION) + throw DaedalusIllegalExternalParameter(&defined[i], "int/func", i + 1); } else if constexpr (std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::STRING) - throw DaedalusIllegalExternalParameter(defined[i], "string", i + 1); + if (defined[i].type() != DaedalusDataType::STRING) + throw DaedalusIllegalExternalParameter(&defined[i], "string", i + 1); } if constexpr (sizeof...(Px) > 0) { @@ -916,25 +927,25 @@ namespace zenkit { std::is_same_v, std::string_view> || std::is_same_v, DaedalusSymbol*>, void> - push_call_parameters(std::vector const& defined, P value, Px... more) { // clang-format on + push_call_parameters(std::span const& defined, P value, Px... more) { // clang-format on if constexpr (is_instance_ptr_v

|| std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::INSTANCE) - throw DaedalusIllegalExternalParameter(defined[i], "instance", i + 1); + if (defined[i].type() != DaedalusDataType::INSTANCE) + throw DaedalusIllegalExternalParameter(&defined[i], "instance", i + 1); push_instance(value); } else if constexpr (std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::FLOAT) - throw DaedalusIllegalExternalParameter(defined[i], "float", i + 1); + if (defined[i].type() != DaedalusDataType::FLOAT) + throw DaedalusIllegalExternalParameter(&defined[i], "float", i + 1); push_float(value); } else if constexpr (std::is_same_v || std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::INT && defined[i]->type() != DaedalusDataType::FUNCTION) - throw DaedalusIllegalExternalParameter(defined[i], "int", i + 1); + if (defined[i].type() != DaedalusDataType::INT && defined[i].type() != DaedalusDataType::FUNCTION) + throw DaedalusIllegalExternalParameter(&defined[i], "int", i + 1); push_int(value); } else if constexpr (std::is_same_v) { - if (defined[i]->type() != DaedalusDataType::STRING) - throw DaedalusIllegalExternalParameter(defined[i], "string", i + 1); + if (defined[i].type() != DaedalusDataType::STRING) + throw DaedalusIllegalExternalParameter(&defined[i], "string", i + 1); push_string(value); } @@ -983,7 +994,7 @@ namespace zenkit { std::array _m_stack; uint16_t _m_stack_ptr {0}; - std::stack _m_call_stack; + std::vector _m_call_stack; std::unordered_map> _m_externals; std::unordered_map> _m_function_overrides; std::optional> _m_default_external {std::nullopt}; diff --git a/src/DaedalusScript.cc b/src/DaedalusScript.cc index aa3bfda2..f40ae33d 100644 --- a/src/DaedalusScript.cc +++ b/src/DaedalusScript.cc @@ -192,25 +192,27 @@ namespace zenkit { } } - std::vector DaedalusScript::find_parameters_for_function(DaedalusSymbol const* parent) { - std::vector syms {}; - - for (uint32_t i = 0; i < parent->count(); ++i) { - syms.push_back(find_symbol_by_index(parent->index() + i + 1)); + std::span DaedalusScript::find_parameters_for_function(DaedalusSymbol const* parent) { + return std::span(_m_symbols.begin() + parent->index() + 1, + _m_symbols.begin() + parent->index() + parent->count() + 1); + } + + std::span DaedalusScript::find_locals_for_function(DaedalusSymbol const* parent) { + std::uint32_t const first = parent->index() + 1 + parent->count(); + for (size_t i = first; i < _m_symbols.size(); ++i) { + auto& name = _m_symbols[i].name(); + if (name.find(parent->name()) == 0 && name.size() >= parent->name().size() && + name[parent->name().size()] == '.') { + continue; + } + return std::span(_m_symbols.begin() + first, _m_symbols.begin() + i); } - - return syms; + return {}; } - std::vector - DaedalusScript::find_parameters_for_function(DaedalusSymbol const* parent) const { - std::vector syms {}; - - for (uint32_t i = 0; i < parent->count(); ++i) { - syms.push_back(find_symbol_by_index(parent->index() + i + 1)); - } - - return syms; + std::span DaedalusScript::find_parameters_for_function(DaedalusSymbol const* parent) const { + return std::span(_m_symbols.begin() + parent->index() + 1, + _m_symbols.begin() + parent->index() + parent->count() + 1); } std::vector DaedalusScript::find_class_members(DaedalusSymbol const& cls) { @@ -610,6 +612,13 @@ namespace zenkit { _m_flags &= ~DaedalusSymbolFlag::TRAP_ACCESS; } + void zenkit::DaedalusSymbol::set_local_variables_enable(bool enable) noexcept { + if (enable) + _m_flags |= DaedalusSymbolFlag::FUNC_LOCALS; + else + _m_flags &= ~DaedalusSymbolFlag::FUNC_LOCALS; + } + DaedalusOpaqueInstance::DaedalusOpaqueInstance(DaedalusSymbol const& sym, std::vector const& members) { size_t str_count = 0; diff --git a/src/DaedalusVm.cc b/src/DaedalusVm.cc index 263dfeaa..0b4420ee 100644 --- a/src/DaedalusVm.cc +++ b/src/DaedalusVm.cc @@ -455,12 +455,15 @@ namespace zenkit { } void DaedalusVm::push_call(DaedalusSymbol const* sym) { + if (sym->has_local_variables_enabled()) { + push_local_variables(sym); + } auto var_count = this->find_parameters_for_function(sym).size(); - _m_call_stack.push({sym, _m_pc, _m_stack_ptr - static_cast(var_count), _m_instance}); + _m_call_stack.push_back({sym, _m_pc, _m_stack_ptr - static_cast(var_count), _m_instance}); } void DaedalusVm::pop_call() { - auto const& call = _m_call_stack.top(); + auto const& call = _m_call_stack.back(); // First, try to fix up the stack. if (!call.function->has_return()) { @@ -487,10 +490,157 @@ namespace zenkit { // } } + if (call.function->has_local_variables_enabled()) { + pop_local_variables(call.function); + } + // Second, reset PC and context, then remove the call stack frame _m_pc = call.program_counter; _m_instance = call.context; - _m_call_stack.pop(); + _m_call_stack.pop_back(); + } + + void DaedalusVm::push_local_variables(DaedalusSymbol const* sym) { + bool has_recursion = false; + for (auto& i : _m_call_stack) { + if (i.function == sym) { + has_recursion = true; + break; + } + } + + if (!has_recursion) { + return; + } + + auto params = this->find_parameters_for_function(sym); + auto locals = this->find_locals_for_function(sym); + + // estimate stack storage for local copy of variables + std::uint32_t locals_size = 0; + for (auto& l : locals) { + switch (l.type()) { + case DaedalusDataType::VOID: + break; + case DaedalusDataType::FLOAT: + case DaedalusDataType::FUNCTION: + case DaedalusDataType::INT: + case DaedalusDataType::STRING: + locals_size += l.count(); + break; + case DaedalusDataType::INSTANCE: + locals_size += 1; + break; + case DaedalusDataType::CLASS: + case DaedalusDataType::PROTOTYPE: + break; + } + } + + if (_m_stack_ptr + locals_size > stack_size) { + throw DaedalusVmException {"stack overflow"}; + } + + if (_m_stack_ptr < params.size()) { + throw DaedalusVmException {"stack underoverflow"}; + } + + // Move function arguments further back, since we're currently at the call instruction and the + // arguments for the next call have already been pushed. + _m_stack_ptr -= params.size(); + for (size_t i = 0; i < params.size(); ++i) { + _m_stack[_m_stack_ptr + locals_size + i] = std::move(_m_stack[_m_stack_ptr + i]); + } + + for (auto& l : locals) { + switch (l.type()) { + case DaedalusDataType::VOID: + break; + case DaedalusDataType::FLOAT: + for (std::uint32_t i = 0; i < l.count(); ++i) { + push_float(l.get_float(i)); + } + break; + case DaedalusDataType::FUNCTION: + case DaedalusDataType::INT: + for (std::uint32_t i = 0; i < l.count(); ++i) { + push_int(l.get_int(i)); + } + break; + case DaedalusDataType::STRING: + for (std::uint32_t i = 0; i < l.count(); ++i) { + push_string(l.get_string(i)); + } + break; + case DaedalusDataType::INSTANCE: + push_instance(l.get_instance()); + break; + case DaedalusDataType::CLASS: + case DaedalusDataType::PROTOTYPE: + ZKLOGW("DaedalusVm", "Attempted to save class or prototype in stack frame"); + break; + } + } + + _m_stack_ptr += params.size(); + } + + void DaedalusVm::pop_local_variables(DaedalusSymbol const* sym) { + int has_recursion = 0; + for (auto& i : _m_call_stack) { + if (i.function == sym) { + ++has_recursion; + } + } + + if (has_recursion <= 1) { + return; + } + + DaedalusStackFrame ret; + if (sym->has_return()) { + ret = std::move(_m_stack[--_m_stack_ptr]); + } + + auto locals = this->find_locals_for_function(sym); + for (size_t i = locals.size(); i > 0;) { + --i; + auto& l = locals[i]; + switch (l.type()) { + case DaedalusDataType::VOID: + break; + case DaedalusDataType::FLOAT: + for (std::uint32_t r = l.count(); r > 0;) { + --r; + l.set_float(pop_float(), r); + } + break; + case DaedalusDataType::FUNCTION: + case DaedalusDataType::INT: + for (std::uint32_t r = l.count(); r > 0;) { + --r; + l.set_int(pop_int(), r); + } + break; + case DaedalusDataType::STRING: + for (std::uint32_t r = l.count(); r > 0;) { + --r; + l.set_string(pop_string(), r); + } + break; + case DaedalusDataType::INSTANCE: + l.set_instance(pop_instance()); + break; + case DaedalusDataType::CLASS: + case DaedalusDataType::PROTOTYPE: + ZKLOGW("DaedalusVm", "Attempted to restore class or prototype in stack frame"); + break; + } + } + + if (sym->has_return()) { + _m_stack[_m_stack_ptr++] = std::move(ret); + } } void DaedalusVm::push_int(std::int32_t value) { @@ -661,12 +811,12 @@ namespace zenkit { // pop all parameters from the stack auto params = find_parameters_for_function(&sym); for (int i = static_cast(params.size()) - 1; i >= 0; --i) { - auto par = params[static_cast(i)]; - if (par->type() == DaedalusDataType::INT) + auto& par = params[static_cast(i)]; + if (par.type() == DaedalusDataType::INT) (void) v.pop_int(); - else if (par->type() == DaedalusDataType::FLOAT) + else if (par.type() == DaedalusDataType::FLOAT) (void) v.pop_float(); - else if (par->type() == DaedalusDataType::INSTANCE || par->type() == DaedalusDataType::STRING) + else if (par.type() == DaedalusDataType::INSTANCE || par.type() == DaedalusDataType::STRING) (void) v.pop_reference(); }