diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d2a5db9..a0c56a13e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - new `ptr` command for the debugger, printing the VM pointers (ip, pp, sp) - compile time arity check when performing a tail call - `string:utf8len` to compute the number of codepoints in a string +- new `trace` command for the debugger, printing the last executed instructions ### Changed - all paths inside `if` should return a value, when used as an expression. If an `else` branch is missing, `nil` will be returned diff --git a/include/Ark/Compiler/BytecodeReader.hpp b/include/Ark/Compiler/BytecodeReader.hpp index ccfe50af0..a10a61cef 100644 --- a/include/Ark/Compiler/BytecodeReader.hpp +++ b/include/Ark/Compiler/BytecodeReader.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -76,6 +77,46 @@ namespace Ark std::size_t start {}; ///< Point to the CODE_SEGMENT_START byte in the bytecode }; + namespace internal + { + enum class ArgKind + { + Symbol, + Constant, + Builtin, + Raw, ///< eg: Stack index, jump address, number + RawHex, + ConstConst, + ConstSym, + SymConst, + SymSym, + BuiltinRaw, ///< Builtin, number + ConstRaw, ///< Constant, number + SymRaw, ///< Symbol, number + RawSym, ///< Symbol index, symbol + RawConst, ///< Symbol index, constant + RawRaw, ///< Symbol index, symbol index + RawRawRaw + }; + + struct Arg + { + ArgKind kind; + uint8_t padding; + uint16_t arg; + + [[nodiscard]] uint16_t primary() const + { + return arg & 0x0fff; + } + + [[nodiscard]] uint16_t secondary() const + { + return static_cast((padding << 4) | (arg & 0xf000) >> 12); + } + }; + } + /** * @brief This class is just a helper to * - check if a bytecode is valid @@ -89,7 +130,7 @@ namespace Ark * @brief Construct a new Bytecode Reader object * */ - BytecodeReader() = default; + BytecodeReader(); /** * @brief Construct needed data before displaying information about a given file @@ -182,10 +223,13 @@ namespace Ark std::optional sEnd = std::nullopt, std::optional cPage = std::nullopt) const; + void printInstruction(std::ostream& os, uint8_t inst, uint8_t padding, uint16_t imm_arg, const Symbols& syms, const Values& vals, bool colorize = true) const; + friend class Ark::State; private: bytecode_t m_bytecode; + std::unordered_map m_arg_kinds; /** * @brief Read a number from the bytecode, under the instruction pointer i diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp index 6f1b2189c..edb2462e4 100644 --- a/include/Ark/VM/Debugger.hpp +++ b/include/Ark/VM/Debugger.hpp @@ -94,6 +94,8 @@ namespace Ark::internal */ void run(VM& vm, ExecutionContext& context, bool from_breakpoint); + void registerInstruction(uint32_t word) noexcept; + [[nodiscard]] inline bool isRunning() const noexcept { return m_running; @@ -105,23 +107,28 @@ namespace Ark::internal } private: + struct Command; struct CommandArgs { VM* vm_ptr; ExecutionContext* ctx_ptr; std::size_t ip, pp; + const Command& me; }; - struct StartsWith_t + struct StartsWith { - } StartsWith; + std::string prefix; + }; struct Command { using Action_t = std::function; + using Args_t = std::vector>; bool is_exact; std::vector names; + Args_t args; std::string description; Action_t action; @@ -133,9 +140,13 @@ namespace Ark::internal is_exact(true), names(list_of_names), description(std::move(desc)), action(do_this) {} - Command(StartsWith_t, std::string start, std::string desc, Action_t&& do_this) : - is_exact(false), names({ std::move(start) }), description(std::move(desc)), action(std::move(do_this)) + Command(StartsWith start, std::vector> arguments, std::string desc, Action_t&& do_this) : + is_exact(false), names({ std::move(start.prefix) }), args(std::move(arguments)), description(std::move(desc)), action(std::move(do_this)) {} + + [[nodiscard]] std::optional getArgs(const std::string& line, std::ostream& os) const; + + [[nodiscard]] std::optional argAsCount(const std::string& line, std::size_t idx, std::ostream& os) const; }; std::vector m_commands; @@ -147,6 +158,8 @@ namespace Ark::internal bool m_running { false }; bool m_quit_vm { false }; + std::vector m_previous_insts; + std::ostream& m_os; bool m_colorize; std::unique_ptr m_prompt_stream; @@ -154,15 +167,12 @@ namespace Ark::internal std::size_t m_line_count { 0 }; void initCommands(); - std::optional matchCommand(const std::string& line) const; + [[nodiscard]] std::optional matchCommand(const std::string& line) const; void showContext(const VM& vm, const ExecutionContext& context) const; void showStack(VM& vm, const ExecutionContext& context, std::size_t count) const; void showLocals(VM& vm, ExecutionContext& context, std::size_t count) const; - - static std::optional getCommandArg(const std::string& command, const std::string& line); - static std::optional parseStringAsInt(const std::string& str); - [[nodiscard]] std::optional getArgAndParseOrError(const std::string& command, const std::string& line, std::size_t default_value) const; + void showPreviousInstructions(const VM& vm, std::size_t count) const; std::optional prompt(std::size_t ip, std::size_t pp, VM& vm, ExecutionContext& context); diff --git a/include/Ark/VM/VM.hpp b/include/Ark/VM/VM.hpp index a17fb0705..aa0b70566 100644 --- a/include/Ark/VM/VM.hpp +++ b/include/Ark/VM/VM.hpp @@ -105,7 +105,7 @@ namespace Ark * @brief Return a pointer to the first execution context, for the main thread of the app * @return internal::ExecutionContext* */ - inline internal::ExecutionContext* getDefaultContext() + inline internal::ExecutionContext* getDefaultContext() const { return m_execution_contexts.front().get(); } @@ -201,6 +201,9 @@ namespace Ark */ int safeRun(internal::ExecutionContext& context, std::size_t untilFrameCount = 0, bool fail_with_exception = false); + template + void unsafeRun(internal::ExecutionContext& context, std::size_t untilFrameCount = 0); + /** * @brief Initialize the VM according to the parameters * diff --git a/src/arkreactor/Compiler/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index 9f6fe7338..167977e29 100644 --- a/src/arkreactor/Compiler/BytecodeReader.cpp +++ b/src/arkreactor/Compiler/BytecodeReader.cpp @@ -9,11 +9,99 @@ #include #include #include +#include namespace Ark { using namespace Ark::internal; + BytecodeReader::BytecodeReader() + { + m_arg_kinds = { + { LOAD_FAST, ArgKind::Symbol }, + { LOAD_FAST_BY_INDEX, ArgKind::Raw }, + { LOAD_SYMBOL, ArgKind::Symbol }, + { LOAD_CONST, ArgKind::Constant }, + { POP_JUMP_IF_TRUE, ArgKind::Raw }, + { STORE, ArgKind::Symbol }, + { STORE_REF, ArgKind::Symbol }, + { SET_VAL, ArgKind::Symbol }, + { POP_JUMP_IF_FALSE, ArgKind::Raw }, + { JUMP, ArgKind::Raw }, + { PUSH_RETURN_ADDRESS, ArgKind::RawHex }, + { CALL, ArgKind::Raw }, + { CAPTURE, ArgKind::Symbol }, + { RENAME_NEXT_CAPTURE, ArgKind::Symbol }, + { BUILTIN, ArgKind::Builtin }, + { DEL, ArgKind::Symbol }, + { MAKE_CLOSURE, ArgKind::Constant }, + { GET_FIELD, ArgKind::Symbol }, + { PLUGIN, ArgKind::Constant }, + { LIST, ArgKind::Raw }, + { APPEND, ArgKind::Raw }, + { CONCAT, ArgKind::Raw }, + { APPEND_IN_PLACE, ArgKind::Raw }, + { CONCAT_IN_PLACE, ArgKind::Raw }, + { POP_LIST, ArgKind::Raw }, + { POP_LIST_IN_PLACE, ArgKind::Raw }, + { SET_AT_INDEX, ArgKind::Raw }, + { SET_AT_2_INDEX, ArgKind::Raw }, + { RESET_SCOPE_JUMP, ArgKind::Raw }, + { LOAD_CONST_LOAD_CONST, ArgKind::ConstConst }, + { LOAD_CONST_STORE, ArgKind::ConstSym }, + { LOAD_CONST_SET_VAL, ArgKind::ConstSym }, + { STORE_FROM, ArgKind::SymSym }, + { STORE_FROM_INDEX, ArgKind::RawSym }, + { SET_VAL_FROM, ArgKind::SymSym }, + { SET_VAL_FROM_INDEX, ArgKind::RawSym }, + { INCREMENT, ArgKind::SymRaw }, + { INCREMENT_BY_INDEX, ArgKind::RawRaw }, + { INCREMENT_STORE, ArgKind::RawRaw }, + { DECREMENT, ArgKind::SymRaw }, + { DECREMENT_BY_INDEX, ArgKind::RawRaw }, + { DECREMENT_STORE, ArgKind::SymRaw }, + { STORE_TAIL, ArgKind::SymSym }, + { STORE_TAIL_BY_INDEX, ArgKind::RawSym }, + { STORE_HEAD, ArgKind::SymSym }, + { STORE_HEAD_BY_INDEX, ArgKind::RawSym }, + { STORE_LIST, ArgKind::RawSym }, + { SET_VAL_TAIL, ArgKind::SymSym }, + { SET_VAL_TAIL_BY_INDEX, ArgKind::RawSym }, + { SET_VAL_HEAD, ArgKind::SymSym }, + { SET_VAL_HEAD_BY_INDEX, ArgKind::RawSym }, + { CALL_BUILTIN, ArgKind::BuiltinRaw }, + { CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, ArgKind::BuiltinRaw }, + { LT_CONST_JUMP_IF_FALSE, ArgKind::ConstRaw }, + { LT_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, + { LT_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, + { GT_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, + { GT_CONST_JUMP_IF_FALSE, ArgKind::ConstRaw }, + { GT_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, + { EQ_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, + { EQ_SYM_INDEX_JUMP_IF_TRUE, ArgKind::SymRaw }, + { NEQ_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, + { NEQ_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, + { CALL_SYMBOL, ArgKind::SymRaw }, + { CALL_SYMBOL_BY_INDEX, ArgKind::RawRaw }, + { CALL_CURRENT_PAGE, ArgKind::SymRaw }, + { GET_FIELD_FROM_SYMBOL, ArgKind::SymSym }, + { GET_FIELD_FROM_SYMBOL_INDEX, ArgKind::RawSym }, + { AT_SYM_SYM, ArgKind::SymSym }, + { AT_SYM_INDEX_SYM_INDEX, ArgKind::RawRaw }, + { AT_SYM_INDEX_CONST, ArgKind::RawConst }, + { CHECK_TYPE_OF, ArgKind::SymConst }, + { CHECK_TYPE_OF_BY_INDEX, ArgKind::RawConst }, + { APPEND_IN_PLACE_SYM, ArgKind::SymRaw }, + { APPEND_IN_PLACE_SYM_INDEX, ArgKind::RawRaw }, + { STORE_LEN, ArgKind::RawSym }, + { LT_LEN_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, + { MUL_BY, ArgKind::RawRaw }, + { MUL_BY_INDEX, ArgKind::RawRaw }, + { MUL_SET_VAL, ArgKind::RawRaw }, + { FUSED_MATH, ArgKind::RawRawRaw } + }; + } + void BytecodeReader::feed(const bytecode_t& bytecode) { m_bytecode = bytecode; @@ -439,215 +527,6 @@ namespace Ark fmt::print("\n"); } - const auto stringify_value = [](const Value& val) -> std::string { - switch (val.valueType()) - { - case ValueType::Number: - return fmt::format("{} (Number)", val.number()); - case ValueType::String: - return fmt::format("{} (String)", val.string()); - case ValueType::PageAddr: - return fmt::format("{} (PageAddr)", val.pageAddr()); - default: - return ""; - } - }; - - enum class ArgKind - { - Symbol, - Constant, - Builtin, - Raw, ///< eg: Stack index, jump address, number - RawHex, - ConstConst, - ConstSym, - SymConst, - SymSym, - BuiltinRaw, ///< Builtin, number - ConstRaw, ///< Constant, number - SymRaw, ///< Symbol, number - RawSym, ///< Symbol index, symbol - RawConst, ///< Symbol index, constant - RawRaw, ///< Symbol index, symbol index - RawRawRaw - }; - - struct Arg - { - ArgKind kind; - uint8_t padding; - uint16_t arg; - - [[nodiscard]] uint16_t primary() const - { - return arg & 0x0fff; - } - - [[nodiscard]] uint16_t secondary() const - { - return static_cast((padding << 4) | (arg & 0xf000) >> 12); - } - }; - - const std::unordered_map arg_kinds = { - { LOAD_FAST, ArgKind::Symbol }, - { LOAD_FAST_BY_INDEX, ArgKind::Raw }, - { LOAD_SYMBOL, ArgKind::Symbol }, - { LOAD_CONST, ArgKind::Constant }, - { POP_JUMP_IF_TRUE, ArgKind::Raw }, - { STORE, ArgKind::Symbol }, - { STORE_REF, ArgKind::Symbol }, - { SET_VAL, ArgKind::Symbol }, - { POP_JUMP_IF_FALSE, ArgKind::Raw }, - { JUMP, ArgKind::Raw }, - { PUSH_RETURN_ADDRESS, ArgKind::RawHex }, - { CALL, ArgKind::Raw }, - { CAPTURE, ArgKind::Symbol }, - { RENAME_NEXT_CAPTURE, ArgKind::Symbol }, - { BUILTIN, ArgKind::Builtin }, - { DEL, ArgKind::Symbol }, - { MAKE_CLOSURE, ArgKind::Constant }, - { GET_FIELD, ArgKind::Symbol }, - { PLUGIN, ArgKind::Constant }, - { LIST, ArgKind::Raw }, - { APPEND, ArgKind::Raw }, - { CONCAT, ArgKind::Raw }, - { APPEND_IN_PLACE, ArgKind::Raw }, - { CONCAT_IN_PLACE, ArgKind::Raw }, - { POP_LIST, ArgKind::Raw }, - { POP_LIST_IN_PLACE, ArgKind::Raw }, - { SET_AT_INDEX, ArgKind::Raw }, - { SET_AT_2_INDEX, ArgKind::Raw }, - { RESET_SCOPE_JUMP, ArgKind::Raw }, - { LOAD_CONST_LOAD_CONST, ArgKind::ConstConst }, - { LOAD_CONST_STORE, ArgKind::ConstSym }, - { LOAD_CONST_SET_VAL, ArgKind::ConstSym }, - { STORE_FROM, ArgKind::SymSym }, - { STORE_FROM_INDEX, ArgKind::RawSym }, - { SET_VAL_FROM, ArgKind::SymSym }, - { SET_VAL_FROM_INDEX, ArgKind::RawSym }, - { INCREMENT, ArgKind::SymRaw }, - { INCREMENT_BY_INDEX, ArgKind::RawRaw }, - { INCREMENT_STORE, ArgKind::RawRaw }, - { DECREMENT, ArgKind::SymRaw }, - { DECREMENT_BY_INDEX, ArgKind::RawRaw }, - { DECREMENT_STORE, ArgKind::SymRaw }, - { STORE_TAIL, ArgKind::SymSym }, - { STORE_TAIL_BY_INDEX, ArgKind::RawSym }, - { STORE_HEAD, ArgKind::SymSym }, - { STORE_HEAD_BY_INDEX, ArgKind::RawSym }, - { STORE_LIST, ArgKind::RawSym }, - { SET_VAL_TAIL, ArgKind::SymSym }, - { SET_VAL_TAIL_BY_INDEX, ArgKind::RawSym }, - { SET_VAL_HEAD, ArgKind::SymSym }, - { SET_VAL_HEAD_BY_INDEX, ArgKind::RawSym }, - { CALL_BUILTIN, ArgKind::BuiltinRaw }, - { CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, ArgKind::BuiltinRaw }, - { LT_CONST_JUMP_IF_FALSE, ArgKind::ConstRaw }, - { LT_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, - { LT_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, - { GT_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, - { GT_CONST_JUMP_IF_FALSE, ArgKind::ConstRaw }, - { GT_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, - { EQ_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, - { EQ_SYM_INDEX_JUMP_IF_TRUE, ArgKind::SymRaw }, - { NEQ_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw }, - { NEQ_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, - { CALL_SYMBOL, ArgKind::SymRaw }, - { CALL_SYMBOL_BY_INDEX, ArgKind::RawRaw }, - { CALL_CURRENT_PAGE, ArgKind::SymRaw }, - { GET_FIELD_FROM_SYMBOL, ArgKind::SymSym }, - { GET_FIELD_FROM_SYMBOL_INDEX, ArgKind::RawSym }, - { AT_SYM_SYM, ArgKind::SymSym }, - { AT_SYM_INDEX_SYM_INDEX, ArgKind::RawRaw }, - { AT_SYM_INDEX_CONST, ArgKind::RawConst }, - { CHECK_TYPE_OF, ArgKind::SymConst }, - { CHECK_TYPE_OF_BY_INDEX, ArgKind::RawConst }, - { APPEND_IN_PLACE_SYM, ArgKind::SymRaw }, - { APPEND_IN_PLACE_SYM_INDEX, ArgKind::RawRaw }, - { STORE_LEN, ArgKind::RawSym }, - { LT_LEN_SYM_JUMP_IF_FALSE, ArgKind::SymRaw }, - { MUL_BY, ArgKind::RawRaw }, - { MUL_BY_INDEX, ArgKind::RawRaw }, - { MUL_SET_VAL, ArgKind::RawRaw }, - { FUSED_MATH, ArgKind::RawRawRaw } - }; - - const auto builtin_name = [](const uint16_t idx) { - return Builtins::builtins[idx].first; - }; - const auto value_str = [&stringify_value, &vals](const uint16_t idx) { - return stringify_value(vals.values[idx]); - }; - const auto symbol_name = [&syms](const uint16_t idx) { - return syms.symbols[idx]; - }; - - const auto color_print_inst = [=](const std::string& name, std::optional arg = std::nullopt) { - fmt::print("{}", fmt::styled(name, fmt::fg(fmt::color::gold))); - if (arg.has_value()) - { - constexpr auto sym_color = fmt::fg(fmt::color::green); - constexpr auto const_color = fmt::fg(fmt::color::magenta); - constexpr auto raw_color = fmt::fg(fmt::color::red); - - switch (auto [kind, _, idx] = arg.value(); kind) - { - case ArgKind::Symbol: - fmt::print(sym_color, " {}\n", symbol_name(idx)); - break; - case ArgKind::Constant: - fmt::print(const_color, " {}\n", value_str(idx)); - break; - case ArgKind::Builtin: - fmt::print(" {}\n", builtin_name(idx)); - break; - case ArgKind::Raw: - fmt::print(raw_color, " ({})\n", idx); - break; - case ArgKind::RawHex: - fmt::print(raw_color, " ({:#x})\n", idx); - break; - case ArgKind::ConstConst: - fmt::print(" {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(value_str(arg->secondary()), const_color)); - break; - case ArgKind::ConstSym: - fmt::print(" {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(symbol_name(arg->secondary()), sym_color)); - break; - case ArgKind::SymConst: - fmt::print(" {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(value_str(arg->secondary()), const_color)); - break; - case ArgKind::SymSym: - fmt::print(" {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(symbol_name(arg->secondary()), sym_color)); - break; - case ArgKind::BuiltinRaw: - fmt::print(" {}, {}\n", builtin_name(arg->primary()), fmt::styled(arg->secondary(), raw_color)); - break; - case ArgKind::ConstRaw: - fmt::print(" {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(arg->secondary(), raw_color)); - break; - case ArgKind::SymRaw: - fmt::print(" {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(arg->secondary(), raw_color)); - break; - case ArgKind::RawSym: - fmt::print(" {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(symbol_name(arg->secondary()), sym_color)); - break; - case ArgKind::RawConst: - fmt::print(" {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(value_str(arg->secondary()), const_color)); - break; - case ArgKind::RawRaw: - fmt::print(" {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(arg->secondary(), raw_color)); - break; - case ArgKind::RawRawRaw: - fmt::print(" {}, {}, {}\n", fmt::styled(arg->padding, raw_color), fmt::styled((arg->arg & 0xff00) >> 8, raw_color), fmt::styled(arg->arg & 0x00ff, raw_color)); - break; - } - } - else - fmt::print("\n"); - }; - if (segment == BytecodeSegment::All || segment == BytecodeSegment::Code || segment == BytecodeSegment::HeadersOnly) { uint16_t pp = 0; @@ -705,16 +584,7 @@ namespace Ark // padding inst arg arg fmt::print(" {:02x} {:02x} {:02x} {:02x} ", inst, padding, page[j + 2], page[j + 3]); - if (const auto idx = static_cast(inst); idx < InstructionNames.size()) - { - const auto inst_name = InstructionNames[idx]; - if (const auto iinst = static_cast(inst); arg_kinds.contains(iinst)) - color_print_inst(inst_name, Arg { arg_kinds.at(iinst), padding, arg }); - else - color_print_inst(inst_name); - } - else - fmt::println("Unknown instruction"); + printInstruction(std::cout, inst, padding, arg, syms, vals); } } if (displayCode && segment != BytecodeSegment::HeadersOnly) @@ -731,4 +601,114 @@ namespace Ark const uint16_t y = m_bytecode[++i]; return x + y; } + + fmt::text_style withForeColor(const fmt::color color, const bool colorize) + { + if (colorize) + return fmt::fg(color); + return {}; + } + + void BytecodeReader::printInstruction(std::ostream& os, const uint8_t inst, const uint8_t padding, const uint16_t imm_arg, const Symbols& syms, const Values& vals, const bool colorize) const + { + const auto stringify_value = [](const Value& val) -> std::string { + switch (val.valueType()) + { + case ValueType::Number: + return fmt::format("{} (Number)", val.number()); + case ValueType::String: + return fmt::format("{} (String)", val.string()); + case ValueType::PageAddr: + return fmt::format("{} (PageAddr)", val.pageAddr()); + default: + return ""; + } + }; + + const auto builtin_name = [](const uint16_t idx) -> std::string { + return Builtins::builtins[idx].first; + }; + const auto value_str = [&stringify_value, &vals](const uint16_t idx) -> std::string { + if (idx < vals.values.size()) + return stringify_value(vals.values[idx]); + return "?"; + }; + const auto symbol_name = [&syms](const uint16_t idx) -> std::string { + if (idx < syms.symbols.size()) + return syms.symbols[idx]; + return "?"; + }; + + if (const auto inst_idx = static_cast(inst); inst_idx < InstructionNames.size()) + { + const std::string name = InstructionNames[inst_idx]; + std::optional arg = std::nullopt; + if (const auto iinst = static_cast(inst); m_arg_kinds.contains(iinst)) + arg = Arg { m_arg_kinds.at(iinst), padding, imm_arg }; + + fmt::print(os, "{}", fmt::styled(name, withForeColor(fmt::color::gold, colorize))); + if (arg.has_value()) + { + const auto sym_color = withForeColor(fmt::color::green, colorize); + const auto const_color = withForeColor(fmt::color::magenta, colorize); + const auto raw_color = withForeColor(fmt::color::red, colorize); + + switch (auto [kind, _, idx] = arg.value(); kind) + { + case ArgKind::Symbol: + fmt::print(os, " {}\n", fmt::styled(symbol_name(idx), sym_color)); + break; + case ArgKind::Constant: + fmt::print(os, " {}\n", fmt::styled(value_str(idx), const_color)); + break; + case ArgKind::Builtin: + fmt::print(os, " {}\n", builtin_name(idx)); + break; + case ArgKind::Raw: + fmt::print(os, " ({})\n", fmt::styled(idx, raw_color)); + break; + case ArgKind::RawHex: + fmt::print(os, " ({:#x})\n", fmt::styled(idx, raw_color)); + break; + case ArgKind::ConstConst: + fmt::print(os, " {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(value_str(arg->secondary()), const_color)); + break; + case ArgKind::ConstSym: + fmt::print(os, " {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(symbol_name(arg->secondary()), sym_color)); + break; + case ArgKind::SymConst: + fmt::print(os, " {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(value_str(arg->secondary()), const_color)); + break; + case ArgKind::SymSym: + fmt::print(os, " {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(symbol_name(arg->secondary()), sym_color)); + break; + case ArgKind::BuiltinRaw: + fmt::print(os, " {}, {}\n", builtin_name(arg->primary()), fmt::styled(arg->secondary(), raw_color)); + break; + case ArgKind::ConstRaw: + fmt::print(os, " {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(arg->secondary(), raw_color)); + break; + case ArgKind::SymRaw: + fmt::print(os, " {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(arg->secondary(), raw_color)); + break; + case ArgKind::RawSym: + fmt::print(os, " {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(symbol_name(arg->secondary()), sym_color)); + break; + case ArgKind::RawConst: + fmt::print(os, " {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(value_str(arg->secondary()), const_color)); + break; + case ArgKind::RawRaw: + fmt::print(os, " {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(arg->secondary(), raw_color)); + break; + case ArgKind::RawRawRaw: + fmt::print(os, " {}, {}, {}\n", fmt::styled(arg->padding, raw_color), fmt::styled((arg->arg & 0xff00) >> 8, raw_color), fmt::styled(arg->arg & 0x00ff, raw_color)); + break; + } + } + else + fmt::print(os, "\n"); + } + else + fmt::println(os, "Unknown instruction"); + } } diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index d97eb41ec..1afc5b55c 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -131,6 +131,50 @@ namespace Ark::internal m_running = false; } + void Debugger::registerInstruction(const uint32_t word) noexcept + { + m_previous_insts.push_back(word); + } + + std::optional Debugger::Command::getArgs(const std::string& line, std::ostream& os) const + { + std::vector split = Utils::splitString(line, ' '); + Args_t args_values = args; + std::size_t i = 0; + for (const auto& arg : std::ranges::views::drop(split, 1)) + { + if (i < args.size()) + args_values[i].second = arg; + else + { + fmt::println(os, "Too many arguments provided to {}, expected {}, got {}", split.front(), args.size(), split.size() - 1); + return std::nullopt; + } + + ++i; + } + + return args_values; + } + + std::optional Debugger::Command::argAsCount(const std::string& line, const std::size_t idx, std::ostream& os) const + { + const std::optional maybe_parsed = getArgs(line, os); + if (maybe_parsed && idx < maybe_parsed->size()) + { + const std::string str = maybe_parsed.value()[idx].second; + std::size_t result = 0; + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + + if (ec == std::errc()) + return result; + + fmt::println(os, "Couldn't parse argument as an unsigned integer"); + return std::nullopt; + } + return std::nullopt; + } + void Debugger::initCommands() { m_commands = { @@ -144,8 +188,12 @@ namespace Ark::internal if (cmd.is_exact) fmt::println(m_os, " {} -- {}", fmt::join(cmd.names, ", "), cmd.description); else - // todo: make arguments description configurable - fmt::println(m_os, " {} -- {}", fmt::join(cmd.names, ", "), cmd.description); + { + const auto v = std::views::transform(cmd.args, [](const auto& p) { + return fmt::format("{}={}", p.first, p.second); + }); + fmt::println(m_os, " {} <{}> -- {}", fmt::join(cmd.names, ", "), fmt::join(v, ", "), cmd.description); + } } return false; }), @@ -165,20 +213,20 @@ namespace Ark::internal return true; }), Command( - StartsWith, - "stack", + StartsWith("stack"), + { { "n", "5" } }, "show the last n values on the stack", [this](const std::string& line, const CommandArgs& args) { - if (const auto arg = getArgAndParseOrError("stack", line, /* default_value= */ 5)) + if (const auto arg = args.me.argAsCount(line, 0, m_os)) showStack(*args.vm_ptr, *args.ctx_ptr, arg.value()); return false; }), Command( - StartsWith, - "locals", + StartsWith("locals"), + { { "n", "5" } }, "show the last n values on the locals' stack", [this](const std::string& line, const CommandArgs& args) { - if (const auto arg = getArgAndParseOrError("locals", line, /* default_value= */ 5)) + if (const auto arg = args.me.argAsCount(line, 0, m_os)) showLocals(*args.vm_ptr, *args.ctx_ptr, arg.value()); return false; }), @@ -194,6 +242,15 @@ namespace Ark::internal fmt::styled(args.ctx_ptr->sp, m_colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style())); return false; }), + Command( + StartsWith("trace"), + { { "n", "10" } }, + "show the last n executed instructions", + [this](const std::string& line, const CommandArgs& args) { + if (const auto arg = args.me.argAsCount(line, 0, m_os)) + showPreviousInstructions(*args.vm_ptr, arg.value()); + return false; + }), }; } @@ -300,42 +357,24 @@ namespace Ark::internal fmt::println(m_os, ""); } - std::optional Debugger::getCommandArg(const std::string& command, const std::string& line) - { - std::string arg = line.substr(command.size()); - Utils::trimWhitespace(arg); - - if (arg.empty()) - return std::nullopt; - return arg; - } - - std::optional Debugger::parseStringAsInt(const std::string& str) + void Debugger::showPreviousInstructions(const VM& vm, const std::size_t count) const { - std::size_t result = 0; - auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + BytecodeReader bcr; + bcr.feed(vm.bytecode()); - if (ec == std::errc()) - return result; - return std::nullopt; - } + const auto syms = bcr.symbols(); + const auto vals = bcr.values(syms); - std::optional Debugger::getArgAndParseOrError(const std::string& command, const std::string& line, const std::size_t default_value) const - { - const auto maybe_arg = getCommandArg(command, line); - std::size_t count = default_value; - if (maybe_arg) + for (std::size_t i = 0; i < count; ++i) { - if (const auto maybe_int = parseStringAsInt(maybe_arg.value())) - count = maybe_int.value(); - else - { - fmt::println(m_os, "Couldn't parse argument as an integer"); - return std::nullopt; - } - } + if (i >= m_previous_insts.size()) + break; - return count; + const uint8_t inst = (m_previous_insts[m_previous_insts.size() - 1 - i] >> 24) & 0xff; + const uint8_t padding = (m_previous_insts[m_previous_insts.size() - 1 - i] >> 16) & 0xff; + const uint16_t arg = m_previous_insts[m_previous_insts.size() - 1 - i] & 0xffff; + bcr.printInstruction(m_os, inst, padding, arg, syms, vals, m_colorize); + } } std::optional Debugger::prompt(const std::size_t ip, const std::size_t pp, VM& vm, ExecutionContext& context) @@ -374,7 +413,8 @@ namespace Ark::internal if (const auto& maybe_cmd = matchCommand(line)) { - if (maybe_cmd->action(line, CommandArgs { .vm_ptr = &vm, .ctx_ptr = &context, .ip = ip, .pp = pp })) + const Command cmd = maybe_cmd.value(); + if (cmd.action(line, CommandArgs { .vm_ptr = &vm, .ctx_ptr = &context, .ip = ip, .pp = pp, .me = cmd })) return std::nullopt; } else diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index aa03026cf..988823256 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -404,7 +404,57 @@ namespace Ark return m_exit_code; } - int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception) + int VM::safeRun(ExecutionContext& context, const std::size_t untilFrameCount, const bool fail_with_exception) + { + m_running = true; + + try + { + if (m_state.m_features & FeatureVMDebugger) + unsafeRun(context, untilFrameCount); + else + unsafeRun(context, untilFrameCount); + } + catch (const Error& e) + { + if (fail_with_exception) + { + std::stringstream stream; + backtrace(context, stream, /* colorize= */ false); + // It's important we have an Ark::Error here, as the constructor for NestedError + // does more than just aggregate error messages, hence the code duplication. + throw NestedError(e, stream.str(), *this); + } + showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context); + } + catch (const std::exception& e) + { + if (fail_with_exception) + { + std::stringstream stream; + backtrace(context, stream, /* colorize= */ false); + throw NestedError(e, stream.str()); + } + showBacktraceWithException(e, context); + } + catch (...) + { + if (fail_with_exception) + throw; + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + throw; +#endif + fmt::println("Unknown error"); + backtrace(context); + m_exit_code = 1; + } + + return m_exit_code; + } + + template + void VM::unsafeRun(ExecutionContext& context, const std::size_t untilFrameCount) { #if ARK_USE_COMPUTED_GOTOS # define TARGET(op) TARGET_##op: @@ -419,7 +469,7 @@ namespace Ark # define GOTO_HALT() break #endif -#define NEXTOPARG() \ +#define FETCH_NEXT_INSTRUCTION() \ do \ { \ inst = m_state.inst(context.pp, context.ip); \ @@ -428,6 +478,11 @@ namespace Ark m_state.inst(context.pp, context.ip + 3)); \ context.ip += 4; \ context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize; \ + if constexpr (WithDebugger) \ + { \ + if (!m_debugger) initDebugger(context); \ + m_debugger->registerInstruction(static_cast((inst << 24) | (padding << 16) | arg)); \ + } \ if (context.inst_exec_counter < 2 && context.sp >= VMStackSize) \ { \ if (context.pp != 0) \ @@ -436,8 +491,8 @@ namespace Ark throw Error("Stack overflow. Are you trying to call a function with too many arguments?"); \ } \ } while (false) -#define DISPATCH() \ - NEXTOPARG(); \ +#define DISPATCH() \ + FETCH_NEXT_INSTRUCTION(); \ DISPATCH_GOTO(); #define UNPACK_ARGS() \ do \ @@ -573,318 +628,286 @@ namespace Ark # pragma GCC diagnostic pop #endif - try - { - uint8_t inst = 0; - uint8_t padding = 0; - uint16_t arg = 0; - uint16_t primary_arg = 0; - uint16_t secondary_arg = 0; + uint8_t inst = 0; + uint8_t padding = 0; + uint16_t arg = 0; + uint16_t primary_arg = 0; + uint16_t secondary_arg = 0; - m_running = true; - - DISPATCH(); - // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works! - { + DISPATCH(); + // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works! + { #if !ARK_USE_COMPUTED_GOTOS - dispatch_opcode: - switch (inst) + dispatch_opcode: + switch (inst) #endif - { + { #pragma region "Instructions" - TARGET(NOP) - { - DISPATCH(); - } - - TARGET(LOAD_FAST) - { - push(loadSymbol(arg, context), context); - DISPATCH(); - } + TARGET(NOP) + { + DISPATCH(); + } - TARGET(LOAD_FAST_BY_INDEX) - { - push(loadSymbolFromIndex(arg, context), context); - DISPATCH(); - } + TARGET(LOAD_FAST) + { + push(loadSymbol(arg, context), context); + DISPATCH(); + } - TARGET(LOAD_SYMBOL) - { - // force resolving the reference - push(*loadSymbol(arg, context), context); - DISPATCH(); - } + TARGET(LOAD_FAST_BY_INDEX) + { + push(loadSymbolFromIndex(arg, context), context); + DISPATCH(); + } - TARGET(LOAD_CONST) - { - push(loadConstAsPtr(arg), context); - DISPATCH(); - } + TARGET(LOAD_SYMBOL) + { + // force resolving the reference + push(*loadSymbol(arg, context), context); + DISPATCH(); + } - TARGET(POP_JUMP_IF_TRUE) - { - if (Value boolean = *popAndResolveAsPtr(context); !!boolean) - jump(arg, context); - DISPATCH(); - } + TARGET(LOAD_CONST) + { + push(loadConstAsPtr(arg), context); + DISPATCH(); + } - TARGET(STORE) - { - store(arg, popAndResolveAsPtr(context), context); - DISPATCH(); - } + TARGET(POP_JUMP_IF_TRUE) + { + if (Value boolean = *popAndResolveAsPtr(context); !!boolean) + jump(arg, context); + DISPATCH(); + } - TARGET(STORE_REF) - { - // Not resolving a potential ref is on purpose! - // This instruction is only used by functions when storing arguments - const Value* tmp = pop(context); - store(arg, tmp, context); - DISPATCH(); - } + TARGET(STORE) + { + store(arg, popAndResolveAsPtr(context), context); + DISPATCH(); + } - TARGET(SET_VAL) - { - setVal(arg, popAndResolveAsPtr(context), context); - DISPATCH(); - } + TARGET(STORE_REF) + { + // Not resolving a potential ref is on purpose! + // This instruction is only used by functions when storing arguments + const Value* tmp = pop(context); + store(arg, tmp, context); + DISPATCH(); + } - TARGET(POP_JUMP_IF_FALSE) - { - if (Value boolean = *popAndResolveAsPtr(context); !boolean) - jump(arg, context); - DISPATCH(); - } + TARGET(SET_VAL) + { + setVal(arg, popAndResolveAsPtr(context), context); + DISPATCH(); + } - TARGET(JUMP) - { + TARGET(POP_JUMP_IF_FALSE) + { + if (Value boolean = *popAndResolveAsPtr(context); !boolean) jump(arg, context); - DISPATCH(); - } + DISPATCH(); + } + + TARGET(JUMP) + { + jump(arg, context); + DISPATCH(); + } - TARGET(RET) + TARGET(RET) + { { + Value ts = *popAndResolveAsPtr(context); + Value ts1 = *popAndResolveAsPtr(context); + + if (ts1.valueType() == ValueType::InstPtr) { - Value ts = *popAndResolveAsPtr(context); - Value ts1 = *popAndResolveAsPtr(context); - - if (ts1.valueType() == ValueType::InstPtr) - { - context.ip = ts1.pageAddr(); - // we always push PP then IP, thus the next value - // MUST be the page pointer - context.pp = pop(context)->pageAddr(); - - returnFromFuncCall(context); - if (ts.valueType() == ValueType::Garbage) - push(Builtins::nil, context); - else - push(std::move(ts), context); - } - else if (ts1.valueType() == ValueType::Garbage) - { - const Value* ip = pop(context); - assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)"); - context.ip = ip->pageAddr(); - context.pp = pop(context)->pageAddr(); - - returnFromFuncCall(context); - push(std::move(ts), context); - } - else if (ts.valueType() == ValueType::InstPtr) - { - context.ip = ts.pageAddr(); - context.pp = ts1.pageAddr(); - returnFromFuncCall(context); + context.ip = ts1.pageAddr(); + // we always push PP then IP, thus the next value + // MUST be the page pointer + context.pp = pop(context)->pageAddr(); + + returnFromFuncCall(context); + if (ts.valueType() == ValueType::Garbage) push(Builtins::nil, context); - } else - throw Error( - fmt::format( - "Unhandled case when returning from function call. TS=({}){}, TS1=({}){}", - std::to_string(ts.valueType()), - ts.toString(*this), - std::to_string(ts1.valueType()), - ts1.toString(*this))); - - if (context.fc <= untilFrameCount) - GOTO_HALT(); + push(std::move(ts), context); } + else if (ts1.valueType() == ValueType::Garbage) + { + const Value* ip = pop(context); + assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)"); + context.ip = ip->pageAddr(); + context.pp = pop(context)->pageAddr(); - DISPATCH(); - } + returnFromFuncCall(context); + push(std::move(ts), context); + } + else if (ts.valueType() == ValueType::InstPtr) + { + context.ip = ts.pageAddr(); + context.pp = ts1.pageAddr(); + returnFromFuncCall(context); + push(Builtins::nil, context); + } + else + throw Error( + fmt::format( + "Unhandled case when returning from function call. TS=({}){}, TS1=({}){}", + std::to_string(ts.valueType()), + ts.toString(*this), + std::to_string(ts1.valueType()), + ts1.toString(*this))); - TARGET(HALT) - { - m_running = false; - GOTO_HALT(); + if (context.fc <= untilFrameCount) + GOTO_HALT(); } - TARGET(PUSH_RETURN_ADDRESS) - { - push(Value(static_cast(context.pp)), context); - // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call - push(Value(ValueType::InstPtr, static_cast(arg * 4)), context); - context.inst_exec_counter++; - DISPATCH(); - } + DISPATCH(); + } - TARGET(CALL) - { - call(context, arg); - if (!m_running) - GOTO_HALT(); - DISPATCH(); - } + TARGET(HALT) + { + m_running = false; + GOTO_HALT(); + } - TARGET(TAIL_CALL_SELF) - { - jump(0, context); - context.locals.back().reset(); - DISPATCH(); - } + TARGET(PUSH_RETURN_ADDRESS) + { + push(Value(static_cast(context.pp)), context); + // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call + push(Value(ValueType::InstPtr, static_cast(arg * 4)), context); + context.inst_exec_counter++; + DISPATCH(); + } - TARGET(CAPTURE) - { - if (!context.saved_scope) - context.saved_scope = ClosureScope(); + TARGET(CALL) + { + call(context, arg); + if (!m_running) + GOTO_HALT(); + DISPATCH(); + } - const Value* ptr = findNearestVariable(arg, context); - if (!ptr) - throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg])); - else - { - ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr; - uint16_t id = context.capture_rename_id.value_or(arg); - context.saved_scope.value().push_back(id, *ptr); - context.capture_rename_id.reset(); - } + TARGET(TAIL_CALL_SELF) + { + jump(0, context); + context.locals.back().reset(); + DISPATCH(); + } - DISPATCH(); - } + TARGET(CAPTURE) + { + if (!context.saved_scope) + context.saved_scope = ClosureScope(); - TARGET(RENAME_NEXT_CAPTURE) + const Value* ptr = findNearestVariable(arg, context); + if (!ptr) + throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg])); + else { - context.capture_rename_id = arg; - DISPATCH(); + ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr; + uint16_t id = context.capture_rename_id.value_or(arg); + context.saved_scope.value().push_back(id, *ptr); + context.capture_rename_id.reset(); } - TARGET(BUILTIN) - { - push(Builtins::builtins[arg].second, context); - DISPATCH(); - } + DISPATCH(); + } - TARGET(DEL) - { - if (Value* var = findNearestVariable(arg, context); var != nullptr) - { - if (var->valueType() == ValueType::User) - var->usertypeRef().del(); - *var = Value(); - DISPATCH(); - } + TARGET(RENAME_NEXT_CAPTURE) + { + context.capture_rename_id = arg; + DISPATCH(); + } - throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg])); - } + TARGET(BUILTIN) + { + push(Builtins::builtins[arg].second, context); + DISPATCH(); + } - TARGET(MAKE_CLOSURE) + TARGET(DEL) + { + if (Value* var = findNearestVariable(arg, context); var != nullptr) { - push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context); - context.saved_scope.reset(); + if (var->valueType() == ValueType::User) + var->usertypeRef().del(); + *var = Value(); DISPATCH(); } - TARGET(GET_FIELD) - { - Value* var = popAndResolveAsPtr(context); - push(getField(var, arg, context), context); - DISPATCH(); - } + throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg])); + } - TARGET(GET_FIELD_AS_CLOSURE) - { - Value* var = popAndResolveAsPtr(context); - push(getField(var, arg, context, /* push_with_env= */ true), context); - DISPATCH(); - } + TARGET(MAKE_CLOSURE) + { + push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context); + context.saved_scope.reset(); + DISPATCH(); + } - TARGET(PLUGIN) - { - loadPlugin(arg, context); - DISPATCH(); - } + TARGET(GET_FIELD) + { + Value* var = popAndResolveAsPtr(context); + push(getField(var, arg, context), context); + DISPATCH(); + } - TARGET(LIST) + TARGET(GET_FIELD_AS_CLOSURE) + { + Value* var = popAndResolveAsPtr(context); + push(getField(var, arg, context, /* push_with_env= */ true), context); + DISPATCH(); + } + + TARGET(PLUGIN) + { + loadPlugin(arg, context); + DISPATCH(); + } + + TARGET(LIST) + { { - { - Value l = createList(arg, context); - push(std::move(l), context); - } - DISPATCH(); + Value l = createList(arg, context); + push(std::move(l), context); } + DISPATCH(); + } - TARGET(APPEND) + TARGET(APPEND) + { { + Value* list = popAndResolveAsPtr(context); + if (list->valueType() != ValueType::List) { - Value* list = popAndResolveAsPtr(context); - if (list->valueType() != ValueType::List) - { - std::vector args = { *list }; - for (uint16_t i = 0; i < arg; ++i) - args.push_back(*popAndResolveAsPtr(context)); - throw types::TypeCheckingError( - "append", - { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } }, - args); - } - - const auto size = static_cast(list->constList().size()); - - Value obj { *list }; - obj.list().reserve(size + arg); - + std::vector args = { *list }; for (uint16_t i = 0; i < arg; ++i) - obj.push_back(*popAndResolveAsPtr(context)); - push(std::move(obj), context); + args.push_back(*popAndResolveAsPtr(context)); + throw types::TypeCheckingError( + "append", + { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } }, + args); } - DISPATCH(); - } - TARGET(CONCAT) - { - { - Value* list = popAndResolveAsPtr(context); - Value obj { *list }; + const auto size = static_cast(list->constList().size()); - for (uint16_t i = 0; i < arg; ++i) - { - Value* next = popAndResolveAsPtr(context); - - if (list->valueType() != ValueType::List || next->valueType() != ValueType::List) - throw types::TypeCheckingError( - "concat", - { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } }, - { *list, *next }); - - std::ranges::copy(next->list(), std::back_inserter(obj.list())); - } - push(std::move(obj), context); - } - DISPATCH(); - } + Value obj { *list }; + obj.list().reserve(size + arg); - TARGET(APPEND_IN_PLACE) - { - Value* list = popAndResolveAsPtr(context); - listAppendInPlace(list, arg, context); - DISPATCH(); + for (uint16_t i = 0; i < arg; ++i) + obj.push_back(*popAndResolveAsPtr(context)); + push(std::move(obj), context); } + DISPATCH(); + } - TARGET(CONCAT_IN_PLACE) + TARGET(CONCAT) + { { Value* list = popAndResolveAsPtr(context); + Value obj { *list }; for (uint16_t i = 0; i < arg; ++i) { @@ -892,1267 +915,1256 @@ namespace Ark if (list->valueType() != ValueType::List || next->valueType() != ValueType::List) throw types::TypeCheckingError( - "concat!", + "concat", { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } }, { *list, *next }); - std::ranges::copy(next->list(), std::back_inserter(list->list())); + std::ranges::copy(next->list(), std::back_inserter(obj.list())); } - DISPATCH(); + push(std::move(obj), context); } + DISPATCH(); + } - TARGET(POP_LIST) - { - { - Value list = *popAndResolveAsPtr(context); - Value number = *popAndResolveAsPtr(context); + TARGET(APPEND_IN_PLACE) + { + Value* list = popAndResolveAsPtr(context); + listAppendInPlace(list, arg, context); + DISPATCH(); + } - if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number) - throw types::TypeCheckingError( - "pop", - { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } }, - { list, number }); - - long idx = static_cast(number.number()); - idx = idx < 0 ? static_cast(list.list().size()) + idx : idx; - if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0) - throwVMError( - ErrorKind::Index, - fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size())); - - list.list().erase(list.list().begin() + idx); - push(list, context); - } - DISPATCH(); - } + TARGET(CONCAT_IN_PLACE) + { + Value* list = popAndResolveAsPtr(context); - TARGET(POP_LIST_IN_PLACE) + for (uint16_t i = 0; i < arg; ++i) { - { - Value* list = popAndResolveAsPtr(context); - Value number = *popAndResolveAsPtr(context); + Value* next = popAndResolveAsPtr(context); - if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number) - throw types::TypeCheckingError( - "pop!", - { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } }, - { *list, number }); - - long idx = static_cast(number.number()); - idx = idx < 0 ? static_cast(list->list().size()) + idx : idx; - if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0) - throwVMError( - ErrorKind::Index, - fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size())); - - // Save the value we're removing to push it later. - // We need to save the value and push later because we're using a pointer to 'list', and pushing before erasing - // would overwrite values from the stack. - if (arg) - number = list->list()[static_cast(idx)]; - list->list().erase(list->list().begin() + idx); - if (arg) - push(number, context); - } - DISPATCH(); + if (list->valueType() != ValueType::List || next->valueType() != ValueType::List) + throw types::TypeCheckingError( + "concat!", + { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } }, + { *list, *next }); + + std::ranges::copy(next->list(), std::back_inserter(list->list())); } + DISPATCH(); + } - TARGET(SET_AT_INDEX) + TARGET(POP_LIST) + { { - { - Value* list = popAndResolveAsPtr(context); - Value number = *popAndResolveAsPtr(context); - Value new_value = *popAndResolveAsPtr(context); + Value list = *popAndResolveAsPtr(context); + Value number = *popAndResolveAsPtr(context); - if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String)) - throw types::TypeCheckingError( - "@=", - { { types::Contract { - { types::Typedef("list", ValueType::List), - types::Typedef("index", ValueType::Number), - types::Typedef("new_value", ValueType::Any) } } }, - { types::Contract { - { types::Typedef("string", ValueType::String), - types::Typedef("index", ValueType::Number), - types::Typedef("char", ValueType::String) } } } }, - { *list, number, new_value }); - - const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size(); - long idx = static_cast(number.number()); - idx = idx < 0 ? static_cast(size) + idx : idx; - if (std::cmp_greater_equal(idx, size) || idx < 0) - throwVMError( - ErrorKind::Index, - fmt::format("@= index ({}) out of range (indexable size: {})", idx, size)); - - if (list->valueType() == ValueType::List) - { - list->list()[static_cast(idx)] = new_value; - if (arg) - push(new_value, context); - } - else - { - list->stringRef()[static_cast(idx)] = new_value.string()[0]; - if (arg) - push(Value(std::string(1, new_value.string()[0])), context); - } - } - DISPATCH(); - } + if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number) + throw types::TypeCheckingError( + "pop", + { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } }, + { list, number }); - TARGET(SET_AT_2_INDEX) - { - { - Value* list = popAndResolveAsPtr(context); - Value x = *popAndResolveAsPtr(context); - Value y = *popAndResolveAsPtr(context); - Value new_value = *popAndResolveAsPtr(context); + long idx = static_cast(number.number()); + idx = idx < 0 ? static_cast(list.list().size()) + idx : idx; + if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0) + throwVMError( + ErrorKind::Index, + fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size())); - if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number) - throw types::TypeCheckingError( - "@@=", - { { types::Contract { - { types::Typedef("list", ValueType::List), - types::Typedef("x", ValueType::Number), - types::Typedef("y", ValueType::Number), - types::Typedef("new_value", ValueType::Any) } } } }, - { *list, x, y, new_value }); - - long idx_y = static_cast(x.number()); - idx_y = idx_y < 0 ? static_cast(list->list().size()) + idx_y : idx_y; - if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0) - throwVMError( - ErrorKind::Index, - fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size())); - - if (!list->list()[static_cast(idx_y)].isIndexable() || - (list->list()[static_cast(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String)) - throw types::TypeCheckingError( - "@@=", - { { types::Contract { - { types::Typedef("list", ValueType::List), - types::Typedef("x", ValueType::Number), - types::Typedef("y", ValueType::Number), - types::Typedef("new_value", ValueType::Any) } } }, - { types::Contract { - { types::Typedef("string", ValueType::String), - types::Typedef("x", ValueType::Number), - types::Typedef("y", ValueType::Number), - types::Typedef("char", ValueType::String) } } } }, - { *list, x, y, new_value }); - - const bool is_list = list->list()[static_cast(idx_y)].valueType() == ValueType::List; - const std::size_t size = - is_list - ? list->list()[static_cast(idx_y)].list().size() - : list->list()[static_cast(idx_y)].stringRef().size(); - - long idx_x = static_cast(y.number()); - idx_x = idx_x < 0 ? static_cast(size) + idx_x : idx_x; - if (std::cmp_greater_equal(idx_x, size) || idx_x < 0) - throwVMError( - ErrorKind::Index, - fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size)); - - if (is_list) - { - list->list()[static_cast(idx_y)].list()[static_cast(idx_x)] = new_value; - if (arg) - push(new_value, context); - } - else - { - list->list()[static_cast(idx_y)].stringRef()[static_cast(idx_x)] = new_value.string()[0]; - if (arg) - push(Value(std::string(1, new_value.string()[0])), context); - } - } - DISPATCH(); + list.list().erase(list.list().begin() + idx); + push(list, context); } + DISPATCH(); + } - TARGET(POP) + TARGET(POP_LIST_IN_PLACE) + { { - pop(context); - DISPATCH(); - } + Value* list = popAndResolveAsPtr(context); + Value number = *popAndResolveAsPtr(context); + + if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number) + throw types::TypeCheckingError( + "pop!", + { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } }, + { *list, number }); + + long idx = static_cast(number.number()); + idx = idx < 0 ? static_cast(list->list().size()) + idx : idx; + if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0) + throwVMError( + ErrorKind::Index, + fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size())); + + // Save the value we're removing to push it later. + // We need to save the value and push later because we're using a pointer to 'list', and pushing before erasing + // would overwrite values from the stack. + if (arg) + number = list->list()[static_cast(idx)]; + list->list().erase(list->list().begin() + idx); + if (arg) + push(number, context); + } + DISPATCH(); + } - TARGET(SHORTCIRCUIT_AND) + TARGET(SET_AT_INDEX) + { { - if (!*peekAndResolveAsPtr(context)) - jump(arg, context); + Value* list = popAndResolveAsPtr(context); + Value number = *popAndResolveAsPtr(context); + Value new_value = *popAndResolveAsPtr(context); + + if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String)) + throw types::TypeCheckingError( + "@=", + { { types::Contract { + { types::Typedef("list", ValueType::List), + types::Typedef("index", ValueType::Number), + types::Typedef("new_value", ValueType::Any) } } }, + { types::Contract { + { types::Typedef("string", ValueType::String), + types::Typedef("index", ValueType::Number), + types::Typedef("char", ValueType::String) } } } }, + { *list, number, new_value }); + + const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size(); + long idx = static_cast(number.number()); + idx = idx < 0 ? static_cast(size) + idx : idx; + if (std::cmp_greater_equal(idx, size) || idx < 0) + throwVMError( + ErrorKind::Index, + fmt::format("@= index ({}) out of range (indexable size: {})", idx, size)); + + if (list->valueType() == ValueType::List) + { + list->list()[static_cast(idx)] = new_value; + if (arg) + push(new_value, context); + } else - pop(context); - DISPATCH(); + { + list->stringRef()[static_cast(idx)] = new_value.string()[0]; + if (arg) + push(Value(std::string(1, new_value.string()[0])), context); + } } + DISPATCH(); + } - TARGET(SHORTCIRCUIT_OR) + TARGET(SET_AT_2_INDEX) + { { - if (!!*peekAndResolveAsPtr(context)) - jump(arg, context); + Value* list = popAndResolveAsPtr(context); + Value x = *popAndResolveAsPtr(context); + Value y = *popAndResolveAsPtr(context); + Value new_value = *popAndResolveAsPtr(context); + + if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number) + throw types::TypeCheckingError( + "@@=", + { { types::Contract { + { types::Typedef("list", ValueType::List), + types::Typedef("x", ValueType::Number), + types::Typedef("y", ValueType::Number), + types::Typedef("new_value", ValueType::Any) } } } }, + { *list, x, y, new_value }); + + long idx_y = static_cast(x.number()); + idx_y = idx_y < 0 ? static_cast(list->list().size()) + idx_y : idx_y; + if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0) + throwVMError( + ErrorKind::Index, + fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size())); + + if (!list->list()[static_cast(idx_y)].isIndexable() || + (list->list()[static_cast(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String)) + throw types::TypeCheckingError( + "@@=", + { { types::Contract { + { types::Typedef("list", ValueType::List), + types::Typedef("x", ValueType::Number), + types::Typedef("y", ValueType::Number), + types::Typedef("new_value", ValueType::Any) } } }, + { types::Contract { + { types::Typedef("string", ValueType::String), + types::Typedef("x", ValueType::Number), + types::Typedef("y", ValueType::Number), + types::Typedef("char", ValueType::String) } } } }, + { *list, x, y, new_value }); + + const bool is_list = list->list()[static_cast(idx_y)].valueType() == ValueType::List; + const std::size_t size = + is_list + ? list->list()[static_cast(idx_y)].list().size() + : list->list()[static_cast(idx_y)].stringRef().size(); + + long idx_x = static_cast(y.number()); + idx_x = idx_x < 0 ? static_cast(size) + idx_x : idx_x; + if (std::cmp_greater_equal(idx_x, size) || idx_x < 0) + throwVMError( + ErrorKind::Index, + fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size)); + + if (is_list) + { + list->list()[static_cast(idx_y)].list()[static_cast(idx_x)] = new_value; + if (arg) + push(new_value, context); + } else - pop(context); - DISPATCH(); + { + list->list()[static_cast(idx_y)].stringRef()[static_cast(idx_x)] = new_value.string()[0]; + if (arg) + push(Value(std::string(1, new_value.string()[0])), context); + } } + DISPATCH(); + } - TARGET(CREATE_SCOPE) - { - context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); - DISPATCH(); - } + TARGET(POP) + { + pop(context); + DISPATCH(); + } - TARGET(RESET_SCOPE_JUMP) - { - context.locals.back().reset(); + TARGET(SHORTCIRCUIT_AND) + { + if (!*peekAndResolveAsPtr(context)) jump(arg, context); - DISPATCH(); - } + else + pop(context); + DISPATCH(); + } - TARGET(POP_SCOPE) - { - context.locals.pop_back(); - DISPATCH(); - } + TARGET(SHORTCIRCUIT_OR) + { + if (!!*peekAndResolveAsPtr(context)) + jump(arg, context); + else + pop(context); + DISPATCH(); + } + + TARGET(CREATE_SCOPE) + { + context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd()); + DISPATCH(); + } + + TARGET(RESET_SCOPE_JUMP) + { + context.locals.back().reset(); + jump(arg, context); + DISPATCH(); + } + + TARGET(POP_SCOPE) + { + context.locals.pop_back(); + DISPATCH(); + } - TARGET(APPLY) + TARGET(APPLY) + { { + const Value args_list = *popAndResolveAsPtr(context), + func = *popAndResolveAsPtr(context); + if (args_list.valueType() != ValueType::List || !func.isFunction()) { - const Value args_list = *popAndResolveAsPtr(context), - func = *popAndResolveAsPtr(context); - if (args_list.valueType() != ValueType::List || !func.isFunction()) - { - throw types::TypeCheckingError( - "apply", - { { - types::Contract { - { types::Typedef("func", ValueType::PageAddr), - types::Typedef("args", ValueType::List) } }, - types::Contract { - { types::Typedef("func", ValueType::Closure), - types::Typedef("args", ValueType::List) } }, - types::Contract { - { types::Typedef("func", ValueType::CProc), - types::Typedef("args", ValueType::List) } }, - } }, - { func, args_list }); - } - - push(func, context); - for (const Value& a : args_list.constList()) - push(a, context); - - call(context, static_cast(args_list.constList().size())); + throw types::TypeCheckingError( + "apply", + { { + types::Contract { + { types::Typedef("func", ValueType::PageAddr), + types::Typedef("args", ValueType::List) } }, + types::Contract { + { types::Typedef("func", ValueType::Closure), + types::Typedef("args", ValueType::List) } }, + types::Contract { + { types::Typedef("func", ValueType::CProc), + types::Typedef("args", ValueType::List) } }, + } }, + { func, args_list }); } - DISPATCH(); + + push(func, context); + for (const Value& a : args_list.constList()) + push(a, context); + + call(context, static_cast(args_list.constList().size())); } + DISPATCH(); + } #pragma endregion #pragma region "Operators" - TARGET(BREAKPOINT) + TARGET(BREAKPOINT) + { { + bool breakpoint_active = true; + if (arg == 1) + breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym; + + if (m_state.m_features & FeatureVMDebugger && breakpoint_active) { - bool breakpoint_active = true; - if (arg == 1) - breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym; - - if (m_state.m_features & FeatureVMDebugger && breakpoint_active) - { - initDebugger(context); - m_debugger->run(*this, context, /* from_breakpoint= */ true); - m_debugger->resetContextToSavedState(context); - - if (m_debugger->shouldQuitVM()) - GOTO_HALT(); - } + initDebugger(context); + m_debugger->run(*this, context, /* from_breakpoint= */ true); + m_debugger->resetContextToSavedState(context); + + if (m_debugger->shouldQuitVM()) + GOTO_HALT(); } - DISPATCH(); } + DISPATCH(); + } + + TARGET(ADD) + { + Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + + if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number) + push(Value(a->number() + b->number()), context); + else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String) + push(Value(a->string() + b->string()), context); + else + throw types::TypeCheckingError( + "+", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } }, + types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } }, + { *a, *b }); + DISPATCH(); + } + + TARGET(SUB) + { + Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + + if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + throw types::TypeCheckingError( + "-", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *a, *b }); + push(Value(a->number() - b->number()), context); + DISPATCH(); + } + + TARGET(MUL) + { + Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + + if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + throw types::TypeCheckingError( + "*", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *a, *b }); + push(Value(a->number() * b->number()), context); + DISPATCH(); + } + + TARGET(DIV) + { + Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + + if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + throw types::TypeCheckingError( + "/", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *a, *b }); + auto d = b->number(); + if (d == 0) + throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this))); + + push(Value(a->number() / d), context); + DISPATCH(); + } + + TARGET(GT) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + + TARGET(LT) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + + TARGET(LE) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + + TARGET(GE) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + + TARGET(NEQ) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } - TARGET(ADD) + TARGET(EQ) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + + TARGET(LEN) + { + const Value* a = popAndResolveAsPtr(context); + + if (a->valueType() == ValueType::List) + push(Value(static_cast(a->constList().size())), context); + else if (a->valueType() == ValueType::String) + push(Value(static_cast(a->string().size())), context); + else if (a->valueType() == ValueType::Dict) + push(Value(static_cast(a->dict().size())), context); + else + throw types::TypeCheckingError( + "len", + { { types::Contract { { types::Typedef("value", ValueType::List) } }, + types::Contract { { types::Typedef("value", ValueType::String) } }, + types::Contract { { types::Typedef("value", ValueType::Dict) } } } }, + { *a }); + DISPATCH(); + } + + TARGET(IS_EMPTY) + { + const Value* a = popAndResolveAsPtr(context); + + if (a->valueType() == ValueType::List) + push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context); + else if (a->valueType() == ValueType::String) + push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context); + else if (a->valueType() == ValueType::Dict) + push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context); + else if (a->valueType() == ValueType::Nil) + push(Builtins::trueSym, context); + else + throw types::TypeCheckingError( + "empty?", + { { types::Contract { { types::Typedef("value", ValueType::List) } }, + types::Contract { { types::Typedef("value", ValueType::Nil) } }, + types::Contract { { types::Typedef("value", ValueType::String) } }, + types::Contract { { types::Typedef("value", ValueType::Dict) } } } }, + { *a }); + DISPATCH(); + } + + TARGET(TAIL) + { + Value* const a = popAndResolveAsPtr(context); + push(helper::tail(a), context); + DISPATCH(); + } + + TARGET(HEAD) + { + Value* const a = popAndResolveAsPtr(context); + push(helper::head(a), context); + DISPATCH(); + } + + TARGET(IS_NIL) + { + const Value* a = popAndResolveAsPtr(context); + push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + + TARGET(TO_NUM) + { + const Value* a = popAndResolveAsPtr(context); + + if (a->valueType() != ValueType::String) + throw types::TypeCheckingError( + "toNumber", + { { types::Contract { { types::Typedef("value", ValueType::String) } } } }, + { *a }); + + double val; + if (Utils::isDouble(a->string(), &val)) + push(Value(val), context); + else + push(Builtins::nil, context); + DISPATCH(); + } + + TARGET(TO_STR) + { + const Value* a = popAndResolveAsPtr(context); + push(Value(a->toString(*this)), context); + DISPATCH(); + } + + TARGET(AT) + { + Value& b = *popAndResolveAsPtr(context); + Value& a = *popAndResolveAsPtr(context); + push(helper::at(a, b, *this), context); + DISPATCH(); + } + + TARGET(AT_AT) + { { - Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + const Value* x = popAndResolveAsPtr(context); + const Value* y = popAndResolveAsPtr(context); + Value& list = *popAndResolveAsPtr(context); - if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number) - push(Value(a->number() + b->number()), context); - else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String) - push(Value(a->string() + b->string()), context); - else - throw types::TypeCheckingError( - "+", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } }, - types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } }, - { *a, *b }); - DISPATCH(); + push(helper::atAt(x, y, list), context); } + DISPATCH(); + } - TARGET(SUB) - { - Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + TARGET(MOD) + { + const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + throw types::TypeCheckingError( + "mod", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *a, *b }); + push(Value(std::fmod(a->number(), b->number())), context); + DISPATCH(); + } - if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + TARGET(TYPE) + { + const Value* a = popAndResolveAsPtr(context); + push(Value(std::to_string(a->valueType())), context); + DISPATCH(); + } + + TARGET(HAS_FIELD) + { + { + Value* const field = popAndResolveAsPtr(context); + Value* const closure = popAndResolveAsPtr(context); + if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String) throw types::TypeCheckingError( - "-", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *a, *b }); - push(Value(a->number() - b->number()), context); - DISPATCH(); + "hasField", + { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } }, + { *closure, *field }); + + auto it = std::ranges::find(m_state.m_symbols, field->stringRef()); + if (it == m_state.m_symbols.end()) + { + push(Builtins::falseSym, context); + DISPATCH(); + } + + auto id = static_cast(std::distance(m_state.m_symbols.begin(), it)); + push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context); } + DISPATCH(); + } + + TARGET(NOT) + { + const Value* a = popAndResolveAsPtr(context); + push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context); + DISPATCH(); + } + +#pragma endregion + +#pragma region "Super Instructions" + TARGET(LOAD_CONST_LOAD_CONST) + { + UNPACK_ARGS(); + push(loadConstAsPtr(primary_arg), context); + push(loadConstAsPtr(secondary_arg), context); + context.inst_exec_counter++; + DISPATCH(); + } + + TARGET(LOAD_CONST_STORE) + { + UNPACK_ARGS(); + store(secondary_arg, loadConstAsPtr(primary_arg), context); + DISPATCH(); + } + + TARGET(LOAD_CONST_SET_VAL) + { + UNPACK_ARGS(); + setVal(secondary_arg, loadConstAsPtr(primary_arg), context); + DISPATCH(); + } + + TARGET(STORE_FROM) + { + UNPACK_ARGS(); + store(secondary_arg, loadSymbol(primary_arg, context), context); + DISPATCH(); + } + + TARGET(STORE_FROM_INDEX) + { + UNPACK_ARGS(); + store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context); + DISPATCH(); + } + + TARGET(SET_VAL_FROM) + { + UNPACK_ARGS(); + setVal(secondary_arg, loadSymbol(primary_arg, context), context); + DISPATCH(); + } + + TARGET(SET_VAL_FROM_INDEX) + { + UNPACK_ARGS(); + setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context); + DISPATCH(); + } - TARGET(MUL) + TARGET(INCREMENT) + { + UNPACK_ARGS(); { - Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + Value* var = loadSymbol(primary_arg, context); - if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); + + if (var->valueType() == ValueType::Number) + push(Value(var->number() + secondary_arg), context); + else throw types::TypeCheckingError( - "*", + "+", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *a, *b }); - push(Value(a->number() * b->number()), context); - DISPATCH(); + { *var, Value(secondary_arg) }); } + DISPATCH(); + } - TARGET(DIV) + TARGET(INCREMENT_BY_INDEX) + { + UNPACK_ARGS(); { - Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); + Value* var = loadSymbolFromIndex(primary_arg, context); + + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); - if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) + if (var->valueType() == ValueType::Number) + push(Value(var->number() + secondary_arg), context); + else throw types::TypeCheckingError( - "/", + "+", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *a, *b }); - auto d = b->number(); - if (d == 0) - throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this))); - - push(Value(a->number() / d), context); - DISPATCH(); + { *var, Value(secondary_arg) }); } + DISPATCH(); + } - TARGET(GT) + TARGET(INCREMENT_STORE) + { + UNPACK_ARGS(); { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); - } + Value* var = loadSymbol(primary_arg, context); - TARGET(LT) - { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); - } + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); - TARGET(LE) - { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); + if (var->valueType() == ValueType::Number) + { + auto val = Value(var->number() + secondary_arg); + setVal(primary_arg, &val, context); + } + else + throw types::TypeCheckingError( + "+", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *var, Value(secondary_arg) }); } + DISPATCH(); + } - TARGET(GE) + TARGET(DECREMENT) + { + UNPACK_ARGS(); { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); - } + Value* var = loadSymbol(primary_arg, context); - TARGET(NEQ) - { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); - } + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); - TARGET(EQ) - { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); + if (var->valueType() == ValueType::Number) + push(Value(var->number() - secondary_arg), context); + else + throw types::TypeCheckingError( + "-", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *var, Value(secondary_arg) }); } + DISPATCH(); + } - TARGET(LEN) + TARGET(DECREMENT_BY_INDEX) + { + UNPACK_ARGS(); { - const Value* a = popAndResolveAsPtr(context); + Value* var = loadSymbolFromIndex(primary_arg, context); - if (a->valueType() == ValueType::List) - push(Value(static_cast(a->constList().size())), context); - else if (a->valueType() == ValueType::String) - push(Value(static_cast(a->string().size())), context); - else if (a->valueType() == ValueType::Dict) - push(Value(static_cast(a->dict().size())), context); + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); + + if (var->valueType() == ValueType::Number) + push(Value(var->number() - secondary_arg), context); else throw types::TypeCheckingError( - "len", - { { types::Contract { { types::Typedef("value", ValueType::List) } }, - types::Contract { { types::Typedef("value", ValueType::String) } }, - types::Contract { { types::Typedef("value", ValueType::Dict) } } } }, - { *a }); - DISPATCH(); + "-", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *var, Value(secondary_arg) }); } + DISPATCH(); + } - TARGET(IS_EMPTY) + TARGET(DECREMENT_STORE) + { + UNPACK_ARGS(); { - const Value* a = popAndResolveAsPtr(context); + Value* var = loadSymbol(primary_arg, context); - if (a->valueType() == ValueType::List) - push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context); - else if (a->valueType() == ValueType::String) - push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context); - else if (a->valueType() == ValueType::Dict) - push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context); - else if (a->valueType() == ValueType::Nil) - push(Builtins::trueSym, context); + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); + + if (var->valueType() == ValueType::Number) + { + auto val = Value(var->number() - secondary_arg); + setVal(primary_arg, &val, context); + } else throw types::TypeCheckingError( - "empty?", - { { types::Contract { { types::Typedef("value", ValueType::List) } }, - types::Contract { { types::Typedef("value", ValueType::Nil) } }, - types::Contract { { types::Typedef("value", ValueType::String) } }, - types::Contract { { types::Typedef("value", ValueType::Dict) } } } }, - { *a }); - DISPATCH(); + "-", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *var, Value(secondary_arg) }); } + DISPATCH(); + } - TARGET(TAIL) + TARGET(STORE_TAIL) + { + UNPACK_ARGS(); { - Value* const a = popAndResolveAsPtr(context); - push(helper::tail(a), context); - DISPATCH(); + Value* list = loadSymbol(primary_arg, context); + Value tail = helper::tail(list); + store(secondary_arg, &tail, context); } + DISPATCH(); + } - TARGET(HEAD) + TARGET(STORE_TAIL_BY_INDEX) + { + UNPACK_ARGS(); { - Value* const a = popAndResolveAsPtr(context); - push(helper::head(a), context); - DISPATCH(); + Value* list = loadSymbolFromIndex(primary_arg, context); + Value tail = helper::tail(list); + store(secondary_arg, &tail, context); } + DISPATCH(); + } - TARGET(IS_NIL) + TARGET(STORE_HEAD) + { + UNPACK_ARGS(); { - const Value* a = popAndResolveAsPtr(context); - push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); + Value* list = loadSymbol(primary_arg, context); + Value head = helper::head(list); + store(secondary_arg, &head, context); } + DISPATCH(); + } - TARGET(TO_NUM) + TARGET(STORE_HEAD_BY_INDEX) + { + UNPACK_ARGS(); { - const Value* a = popAndResolveAsPtr(context); + Value* list = loadSymbolFromIndex(primary_arg, context); + Value head = helper::head(list); + store(secondary_arg, &head, context); + } + DISPATCH(); + } - if (a->valueType() != ValueType::String) - throw types::TypeCheckingError( - "toNumber", - { { types::Contract { { types::Typedef("value", ValueType::String) } } } }, - { *a }); - - double val; - if (Utils::isDouble(a->string(), &val)) - push(Value(val), context); - else - push(Builtins::nil, context); - DISPATCH(); - } - - TARGET(TO_STR) - { - const Value* a = popAndResolveAsPtr(context); - push(Value(a->toString(*this)), context); - DISPATCH(); - } - - TARGET(AT) - { - Value& b = *popAndResolveAsPtr(context); - Value& a = *popAndResolveAsPtr(context); - push(helper::at(a, b, *this), context); - DISPATCH(); - } - - TARGET(AT_AT) - { - { - const Value* x = popAndResolveAsPtr(context); - const Value* y = popAndResolveAsPtr(context); - Value& list = *popAndResolveAsPtr(context); - - push(helper::atAt(x, y, list), context); - } - DISPATCH(); - } - - TARGET(MOD) - { - const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context); - if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number) - throw types::TypeCheckingError( - "mod", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *a, *b }); - push(Value(std::fmod(a->number(), b->number())), context); - DISPATCH(); - } - - TARGET(TYPE) - { - const Value* a = popAndResolveAsPtr(context); - push(Value(std::to_string(a->valueType())), context); - DISPATCH(); - } - - TARGET(HAS_FIELD) - { - { - Value* const field = popAndResolveAsPtr(context); - Value* const closure = popAndResolveAsPtr(context); - if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String) - throw types::TypeCheckingError( - "hasField", - { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } }, - { *closure, *field }); - - auto it = std::ranges::find(m_state.m_symbols, field->stringRef()); - if (it == m_state.m_symbols.end()) - { - push(Builtins::falseSym, context); - DISPATCH(); - } - - auto id = static_cast(std::distance(m_state.m_symbols.begin(), it)); - push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context); - } - DISPATCH(); - } - - TARGET(NOT) - { - const Value* a = popAndResolveAsPtr(context); - push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context); - DISPATCH(); - } - -#pragma endregion - -#pragma region "Super Instructions" - TARGET(LOAD_CONST_LOAD_CONST) - { - UNPACK_ARGS(); - push(loadConstAsPtr(primary_arg), context); - push(loadConstAsPtr(secondary_arg), context); - context.inst_exec_counter++; - DISPATCH(); - } - - TARGET(LOAD_CONST_STORE) - { - UNPACK_ARGS(); - store(secondary_arg, loadConstAsPtr(primary_arg), context); - DISPATCH(); - } - - TARGET(LOAD_CONST_SET_VAL) - { - UNPACK_ARGS(); - setVal(secondary_arg, loadConstAsPtr(primary_arg), context); - DISPATCH(); - } - - TARGET(STORE_FROM) - { - UNPACK_ARGS(); - store(secondary_arg, loadSymbol(primary_arg, context), context); - DISPATCH(); - } - - TARGET(STORE_FROM_INDEX) - { - UNPACK_ARGS(); - store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context); - DISPATCH(); - } - - TARGET(SET_VAL_FROM) - { - UNPACK_ARGS(); - setVal(secondary_arg, loadSymbol(primary_arg, context), context); - DISPATCH(); - } - - TARGET(SET_VAL_FROM_INDEX) - { - UNPACK_ARGS(); - setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context); - DISPATCH(); - } - - TARGET(INCREMENT) - { - UNPACK_ARGS(); - { - Value* var = loadSymbol(primary_arg, context); - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - push(Value(var->number() + secondary_arg), context); - else - throw types::TypeCheckingError( - "+", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(secondary_arg) }); - } - DISPATCH(); - } - - TARGET(INCREMENT_BY_INDEX) - { - UNPACK_ARGS(); - { - Value* var = loadSymbolFromIndex(primary_arg, context); - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - push(Value(var->number() + secondary_arg), context); - else - throw types::TypeCheckingError( - "+", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(secondary_arg) }); - } - DISPATCH(); - } - - TARGET(INCREMENT_STORE) - { - UNPACK_ARGS(); - { - Value* var = loadSymbol(primary_arg, context); - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - { - auto val = Value(var->number() + secondary_arg); - setVal(primary_arg, &val, context); - } - else - throw types::TypeCheckingError( - "+", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(secondary_arg) }); - } - DISPATCH(); - } - - TARGET(DECREMENT) - { - UNPACK_ARGS(); - { - Value* var = loadSymbol(primary_arg, context); - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - push(Value(var->number() - secondary_arg), context); - else - throw types::TypeCheckingError( - "-", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(secondary_arg) }); - } - DISPATCH(); - } - - TARGET(DECREMENT_BY_INDEX) + TARGET(STORE_LIST) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* var = loadSymbolFromIndex(primary_arg, context); - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - push(Value(var->number() - secondary_arg), context); - else - throw types::TypeCheckingError( - "-", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(secondary_arg) }); - } - DISPATCH(); + Value l = createList(primary_arg, context); + store(secondary_arg, &l, context); } + DISPATCH(); + } - TARGET(DECREMENT_STORE) + TARGET(SET_VAL_TAIL) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* var = loadSymbol(primary_arg, context); - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - { - auto val = Value(var->number() - secondary_arg); - setVal(primary_arg, &val, context); - } - else - throw types::TypeCheckingError( - "-", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(secondary_arg) }); - } - DISPATCH(); - } - - TARGET(STORE_TAIL) - { - UNPACK_ARGS(); - { - Value* list = loadSymbol(primary_arg, context); - Value tail = helper::tail(list); - store(secondary_arg, &tail, context); - } - DISPATCH(); + Value* list = loadSymbol(primary_arg, context); + Value tail = helper::tail(list); + setVal(secondary_arg, &tail, context); } + DISPATCH(); + } - TARGET(STORE_TAIL_BY_INDEX) + TARGET(SET_VAL_TAIL_BY_INDEX) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* list = loadSymbolFromIndex(primary_arg, context); - Value tail = helper::tail(list); - store(secondary_arg, &tail, context); - } - DISPATCH(); + Value* list = loadSymbolFromIndex(primary_arg, context); + Value tail = helper::tail(list); + setVal(secondary_arg, &tail, context); } + DISPATCH(); + } - TARGET(STORE_HEAD) + TARGET(SET_VAL_HEAD) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* list = loadSymbol(primary_arg, context); - Value head = helper::head(list); - store(secondary_arg, &head, context); - } - DISPATCH(); + Value* list = loadSymbol(primary_arg, context); + Value head = helper::head(list); + setVal(secondary_arg, &head, context); } + DISPATCH(); + } - TARGET(STORE_HEAD_BY_INDEX) + TARGET(SET_VAL_HEAD_BY_INDEX) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* list = loadSymbolFromIndex(primary_arg, context); - Value head = helper::head(list); - store(secondary_arg, &head, context); - } - DISPATCH(); + Value* list = loadSymbolFromIndex(primary_arg, context); + Value head = helper::head(list); + setVal(secondary_arg, &head, context); } + DISPATCH(); + } - TARGET(STORE_LIST) - { - UNPACK_ARGS(); - { - Value l = createList(primary_arg, context); - store(secondary_arg, &l, context); - } - DISPATCH(); - } + TARGET(CALL_BUILTIN) + { + UNPACK_ARGS(); + // no stack size check because we do not push IP/PP since we are just calling a builtin + callBuiltin( + context, + Builtins::builtins[primary_arg].second, + secondary_arg, + /* remove_return_address= */ true, + /* remove_builtin= */ false); + if (!m_running) + GOTO_HALT(); + DISPATCH(); + } - TARGET(SET_VAL_TAIL) - { - UNPACK_ARGS(); - { - Value* list = loadSymbol(primary_arg, context); - Value tail = helper::tail(list); - setVal(secondary_arg, &tail, context); - } - DISPATCH(); - } + TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS) + { + UNPACK_ARGS(); + // no stack size check because we do not push IP/PP since we are just calling a builtin + callBuiltin( + context, + Builtins::builtins[primary_arg].second, + secondary_arg, + // we didn't have a PUSH_RETURN_ADDRESS instruction before, + // so do not attempt to remove (pp,ip) from the stack: they're not there! + /* remove_return_address= */ false, + /* remove_builtin= */ false); + if (!m_running) + GOTO_HALT(); + DISPATCH(); + } - TARGET(SET_VAL_TAIL_BY_INDEX) - { - UNPACK_ARGS(); - { - Value* list = loadSymbolFromIndex(primary_arg, context); - Value tail = helper::tail(list); - setVal(secondary_arg, &tail, context); - } - DISPATCH(); - } + TARGET(LT_CONST_JUMP_IF_FALSE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (!(*sym < *loadConstAsPtr(primary_arg))) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(SET_VAL_HEAD) - { - UNPACK_ARGS(); - { - Value* list = loadSymbol(primary_arg, context); - Value head = helper::head(list); - setVal(secondary_arg, &head, context); - } - DISPATCH(); - } + TARGET(LT_CONST_JUMP_IF_TRUE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (*sym < *loadConstAsPtr(primary_arg)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(SET_VAL_HEAD_BY_INDEX) - { - UNPACK_ARGS(); - { - Value* list = loadSymbolFromIndex(primary_arg, context); - Value head = helper::head(list); - setVal(secondary_arg, &head, context); - } - DISPATCH(); - } + TARGET(LT_SYM_JUMP_IF_FALSE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (!(*sym < *loadSymbol(primary_arg, context))) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(CALL_BUILTIN) - { - UNPACK_ARGS(); - // no stack size check because we do not push IP/PP since we are just calling a builtin - callBuiltin( - context, - Builtins::builtins[primary_arg].second, - secondary_arg, - /* remove_return_address= */ true, - /* remove_builtin= */ false); - if (!m_running) - GOTO_HALT(); - DISPATCH(); - } + TARGET(GT_CONST_JUMP_IF_TRUE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + const Value* cst = loadConstAsPtr(primary_arg); + if (*cst < *sym) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS) - { - UNPACK_ARGS(); - // no stack size check because we do not push IP/PP since we are just calling a builtin - callBuiltin( - context, - Builtins::builtins[primary_arg].second, - secondary_arg, - // we didn't have a PUSH_RETURN_ADDRESS instruction before, - // so do not attempt to remove (pp,ip) from the stack: they're not there! - /* remove_return_address= */ false, - /* remove_builtin= */ false); - if (!m_running) - GOTO_HALT(); - DISPATCH(); - } + TARGET(GT_CONST_JUMP_IF_FALSE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + const Value* cst = loadConstAsPtr(primary_arg); + if (!(*cst < *sym)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(LT_CONST_JUMP_IF_FALSE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (!(*sym < *loadConstAsPtr(primary_arg))) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(GT_SYM_JUMP_IF_FALSE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + const Value* rhs = loadSymbol(primary_arg, context); + if (!(*rhs < *sym)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(LT_CONST_JUMP_IF_TRUE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (*sym < *loadConstAsPtr(primary_arg)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(EQ_CONST_JUMP_IF_TRUE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (*sym == *loadConstAsPtr(primary_arg)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(LT_SYM_JUMP_IF_FALSE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (!(*sym < *loadSymbol(primary_arg, context))) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (*sym == *loadSymbolFromIndex(primary_arg, context)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(GT_CONST_JUMP_IF_TRUE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - const Value* cst = loadConstAsPtr(primary_arg); - if (*cst < *sym) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(NEQ_CONST_JUMP_IF_TRUE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (*sym != *loadConstAsPtr(primary_arg)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(GT_CONST_JUMP_IF_FALSE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - const Value* cst = loadConstAsPtr(primary_arg); - if (!(*cst < *sym)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(NEQ_SYM_JUMP_IF_FALSE) + { + UNPACK_ARGS(); + const Value* sym = popAndResolveAsPtr(context); + if (*sym == *loadSymbol(primary_arg, context)) + jump(secondary_arg, context); + DISPATCH(); + } - TARGET(GT_SYM_JUMP_IF_FALSE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - const Value* rhs = loadSymbol(primary_arg, context); - if (!(*rhs < *sym)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(CALL_SYMBOL) + { + UNPACK_ARGS(); + call(context, secondary_arg, /* function_ptr= */ loadSymbol(primary_arg, context)); + if (!m_running) + GOTO_HALT(); + DISPATCH(); + } - TARGET(EQ_CONST_JUMP_IF_TRUE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (*sym == *loadConstAsPtr(primary_arg)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(CALL_SYMBOL_BY_INDEX) + { + UNPACK_ARGS(); + call(context, secondary_arg, /* function_ptr= */ loadSymbolFromIndex(primary_arg, context)); + if (!m_running) + GOTO_HALT(); + DISPATCH(); + } - TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (*sym == *loadSymbolFromIndex(primary_arg, context)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(CALL_CURRENT_PAGE) + { + UNPACK_ARGS(); + context.last_symbol = primary_arg; + call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast(context.pp)); + if (!m_running) + GOTO_HALT(); + DISPATCH(); + } - TARGET(NEQ_CONST_JUMP_IF_TRUE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (*sym != *loadConstAsPtr(primary_arg)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(GET_FIELD_FROM_SYMBOL) + { + UNPACK_ARGS(); + push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context); + DISPATCH(); + } - TARGET(NEQ_SYM_JUMP_IF_FALSE) - { - UNPACK_ARGS(); - const Value* sym = popAndResolveAsPtr(context); - if (*sym == *loadSymbol(primary_arg, context)) - jump(secondary_arg, context); - DISPATCH(); - } + TARGET(GET_FIELD_FROM_SYMBOL_INDEX) + { + UNPACK_ARGS(); + push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context); + DISPATCH(); + } - TARGET(CALL_SYMBOL) - { - UNPACK_ARGS(); - call(context, secondary_arg, /* function_ptr= */ loadSymbol(primary_arg, context)); - if (!m_running) - GOTO_HALT(); - DISPATCH(); - } + TARGET(AT_SYM_SYM) + { + UNPACK_ARGS(); + push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context); + DISPATCH(); + } - TARGET(CALL_SYMBOL_BY_INDEX) - { - UNPACK_ARGS(); - call(context, secondary_arg, /* function_ptr= */ loadSymbolFromIndex(primary_arg, context)); - if (!m_running) - GOTO_HALT(); - DISPATCH(); - } + TARGET(AT_SYM_INDEX_SYM_INDEX) + { + UNPACK_ARGS(); + push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context); + DISPATCH(); + } - TARGET(CALL_CURRENT_PAGE) - { - UNPACK_ARGS(); - context.last_symbol = primary_arg; - call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast(context.pp)); - if (!m_running) - GOTO_HALT(); - DISPATCH(); - } + TARGET(AT_SYM_INDEX_CONST) + { + UNPACK_ARGS(); + push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context); + DISPATCH(); + } - TARGET(GET_FIELD_FROM_SYMBOL) - { - UNPACK_ARGS(); - push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context); - DISPATCH(); - } + TARGET(CHECK_TYPE_OF) + { + UNPACK_ARGS(); + const Value* sym = loadSymbol(primary_arg, context); + const Value* cst = loadConstAsPtr(secondary_arg); + push( + cst->valueType() == ValueType::String && + std::to_string(sym->valueType()) == cst->string() + ? Builtins::trueSym + : Builtins::falseSym, + context); + DISPATCH(); + } - TARGET(GET_FIELD_FROM_SYMBOL_INDEX) - { - UNPACK_ARGS(); - push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context); - DISPATCH(); - } + TARGET(CHECK_TYPE_OF_BY_INDEX) + { + UNPACK_ARGS(); + const Value* sym = loadSymbolFromIndex(primary_arg, context); + const Value* cst = loadConstAsPtr(secondary_arg); + push( + cst->valueType() == ValueType::String && + std::to_string(sym->valueType()) == cst->string() + ? Builtins::trueSym + : Builtins::falseSym, + context); + DISPATCH(); + } - TARGET(AT_SYM_SYM) - { - UNPACK_ARGS(); - push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context); - DISPATCH(); - } + TARGET(APPEND_IN_PLACE_SYM) + { + UNPACK_ARGS(); + listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context); + DISPATCH(); + } - TARGET(AT_SYM_INDEX_SYM_INDEX) - { - UNPACK_ARGS(); - push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context); - DISPATCH(); - } + TARGET(APPEND_IN_PLACE_SYM_INDEX) + { + UNPACK_ARGS(); + listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context); + DISPATCH(); + } - TARGET(AT_SYM_INDEX_CONST) + TARGET(STORE_LEN) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context); - DISPATCH(); + Value* a = loadSymbolFromIndex(primary_arg, context); + Value len; + if (a->valueType() == ValueType::List) + len = Value(static_cast(a->constList().size())); + else if (a->valueType() == ValueType::String) + len = Value(static_cast(a->string().size())); + else + throw types::TypeCheckingError( + "len", + { { types::Contract { { types::Typedef("value", ValueType::List) } }, + types::Contract { { types::Typedef("value", ValueType::String) } } } }, + { *a }); + store(secondary_arg, &len, context); } + DISPATCH(); + } - TARGET(CHECK_TYPE_OF) + TARGET(LT_LEN_SYM_JUMP_IF_FALSE) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); const Value* sym = loadSymbol(primary_arg, context); - const Value* cst = loadConstAsPtr(secondary_arg); - push( - cst->valueType() == ValueType::String && - std::to_string(sym->valueType()) == cst->string() - ? Builtins::trueSym - : Builtins::falseSym, - context); - DISPATCH(); - } + Value size; - TARGET(CHECK_TYPE_OF_BY_INDEX) - { - UNPACK_ARGS(); - const Value* sym = loadSymbolFromIndex(primary_arg, context); - const Value* cst = loadConstAsPtr(secondary_arg); - push( - cst->valueType() == ValueType::String && - std::to_string(sym->valueType()) == cst->string() - ? Builtins::trueSym - : Builtins::falseSym, - context); - DISPATCH(); - } - - TARGET(APPEND_IN_PLACE_SYM) - { - UNPACK_ARGS(); - listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context); - DISPATCH(); - } - - TARGET(APPEND_IN_PLACE_SYM_INDEX) - { - UNPACK_ARGS(); - listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context); - DISPATCH(); - } + if (sym->valueType() == ValueType::List) + size = Value(static_cast(sym->constList().size())); + else if (sym->valueType() == ValueType::String) + size = Value(static_cast(sym->string().size())); + else + throw types::TypeCheckingError( + "len", + { { types::Contract { { types::Typedef("value", ValueType::List) } }, + types::Contract { { types::Typedef("value", ValueType::String) } } } }, + { *sym }); - TARGET(STORE_LEN) - { - UNPACK_ARGS(); - { - Value* a = loadSymbolFromIndex(primary_arg, context); - Value len; - if (a->valueType() == ValueType::List) - len = Value(static_cast(a->constList().size())); - else if (a->valueType() == ValueType::String) - len = Value(static_cast(a->string().size())); - else - throw types::TypeCheckingError( - "len", - { { types::Contract { { types::Typedef("value", ValueType::List) } }, - types::Contract { { types::Typedef("value", ValueType::String) } } } }, - { *a }); - store(secondary_arg, &len, context); - } - DISPATCH(); + if (!(*popAndResolveAsPtr(context) < size)) + jump(secondary_arg, context); } + DISPATCH(); + } - TARGET(LT_LEN_SYM_JUMP_IF_FALSE) + TARGET(MUL_BY) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - const Value* sym = loadSymbol(primary_arg, context); - Value size; + Value* var = loadSymbol(primary_arg, context); + const int other = static_cast(secondary_arg) - 2048; - if (sym->valueType() == ValueType::List) - size = Value(static_cast(sym->constList().size())); - else if (sym->valueType() == ValueType::String) - size = Value(static_cast(sym->string().size())); - else - throw types::TypeCheckingError( - "len", - { { types::Contract { { types::Typedef("value", ValueType::List) } }, - types::Contract { { types::Typedef("value", ValueType::String) } } } }, - { *sym }); + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); - if (!(*popAndResolveAsPtr(context) < size)) - jump(secondary_arg, context); - } - DISPATCH(); + if (var->valueType() == ValueType::Number) + push(Value(var->number() * other), context); + else + throw types::TypeCheckingError( + "*", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *var, Value(other) }); } + DISPATCH(); + } - TARGET(MUL_BY) + TARGET(MUL_BY_INDEX) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* var = loadSymbol(primary_arg, context); - const int other = static_cast(secondary_arg) - 2048; + Value* var = loadSymbolFromIndex(primary_arg, context); + const int other = static_cast(secondary_arg) - 2048; - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); - if (var->valueType() == ValueType::Number) - push(Value(var->number() * other), context); - else - throw types::TypeCheckingError( - "*", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(other) }); - } - DISPATCH(); + if (var->valueType() == ValueType::Number) + push(Value(var->number() * other), context); + else + throw types::TypeCheckingError( + "*", + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *var, Value(other) }); } + DISPATCH(); + } - TARGET(MUL_BY_INDEX) + TARGET(MUL_SET_VAL) + { + UNPACK_ARGS(); { - UNPACK_ARGS(); - { - Value* var = loadSymbolFromIndex(primary_arg, context); - const int other = static_cast(secondary_arg) - 2048; + Value* var = loadSymbol(primary_arg, context); + const int other = static_cast(secondary_arg) - 2048; - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); + // use internal reference, shouldn't break anything so far, unless it's already a ref + if (var->valueType() == ValueType::Reference) + var = var->reference(); - if (var->valueType() == ValueType::Number) - push(Value(var->number() * other), context); - else - throw types::TypeCheckingError( - "*", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(other) }); - } - DISPATCH(); - } - - TARGET(MUL_SET_VAL) - { - UNPACK_ARGS(); + if (var->valueType() == ValueType::Number) { - Value* var = loadSymbol(primary_arg, context); - const int other = static_cast(secondary_arg) - 2048; - - // use internal reference, shouldn't break anything so far, unless it's already a ref - if (var->valueType() == ValueType::Reference) - var = var->reference(); - - if (var->valueType() == ValueType::Number) - { - auto val = Value(var->number() * other); - setVal(primary_arg, &val, context); - } - else - throw types::TypeCheckingError( - "*", - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *var, Value(other) }); + auto val = Value(var->number() * other); + setVal(primary_arg, &val, context); } - DISPATCH(); - } - - TARGET(FUSED_MATH) - { - const auto op1 = static_cast(padding), - op2 = static_cast((arg & 0xff00) >> 8), - op3 = static_cast(arg & 0x00ff); - const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP); - - const Value* d = popAndResolveAsPtr(context); - const Value* c = popAndResolveAsPtr(context); - const Value* b = popAndResolveAsPtr(context); - - if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number) + else throw types::TypeCheckingError( - helper::mathInstToStr(op1), + "*", { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *c, *d }); + { *var, Value(other) }); + } + DISPATCH(); + } - double temp = helper::doMath(c->number(), d->number(), op1); - if (b->valueType() != ValueType::Number) + TARGET(FUSED_MATH) + { + const auto op1 = static_cast(padding), + op2 = static_cast((arg & 0xff00) >> 8), + op3 = static_cast(arg & 0x00ff); + const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP); + + const Value* d = popAndResolveAsPtr(context); + const Value* c = popAndResolveAsPtr(context); + const Value* b = popAndResolveAsPtr(context); + + if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number) + throw types::TypeCheckingError( + helper::mathInstToStr(op1), + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *c, *d }); + + double temp = helper::doMath(c->number(), d->number(), op1); + if (b->valueType() != ValueType::Number) + throw types::TypeCheckingError( + helper::mathInstToStr(op2), + { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, + { *b, Value(temp) }); + temp = helper::doMath(b->number(), temp, op2); + + if (arg_count == 2) + push(Value(temp), context); + else if (arg_count == 3) + { + const Value* a = popAndResolveAsPtr(context); + if (a->valueType() != ValueType::Number) throw types::TypeCheckingError( - helper::mathInstToStr(op2), + helper::mathInstToStr(op3), { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *b, Value(temp) }); - temp = helper::doMath(b->number(), temp, op2); - - if (arg_count == 2) - push(Value(temp), context); - else if (arg_count == 3) - { - const Value* a = popAndResolveAsPtr(context); - if (a->valueType() != ValueType::Number) - throw types::TypeCheckingError( - helper::mathInstToStr(op3), - { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } }, - { *a, Value(temp) }); + { *a, Value(temp) }); - temp = helper::doMath(a->number(), temp, op3); - push(Value(temp), context); - } - else - throw Error( - fmt::format( - "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!", - arg_count, static_cast(op1), static_cast(op2), static_cast(op3))); - DISPATCH(); + temp = helper::doMath(a->number(), temp, op3); + push(Value(temp), context); } -#pragma endregion + else + throw Error( + fmt::format( + "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!", + arg_count, static_cast(op1), static_cast(op2), static_cast(op3))); + DISPATCH(); } -#if ARK_USE_COMPUTED_GOTOS - dispatch_end: - do - { - } while (false); -#endif - } - } - catch (const Error& e) - { - if (fail_with_exception) - { - std::stringstream stream; - backtrace(context, stream, /* colorize= */ false); - // It's important we have an Ark::Error here, as the constructor for NestedError - // does more than just aggregate error messages, hence the code duplication. - throw NestedError(e, stream.str(), *this); +#pragma endregion } - else - showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context); - } - catch (const std::exception& e) - { - if (fail_with_exception) +#if ARK_USE_COMPUTED_GOTOS + dispatch_end: + do { - std::stringstream stream; - backtrace(context, stream, /* colorize= */ false); - throw NestedError(e, stream.str()); - } - else - showBacktraceWithException(e, context); - } - catch (...) - { - if (fail_with_exception) - throw; - -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - throw; + } while (false); #endif - fmt::println("Unknown error"); - backtrace(context); - m_exit_code = 1; } - - return m_exit_code; } uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept diff --git a/tests/unittests/resources/DebuggerSuite/basic.expected b/tests/unittests/resources/DebuggerSuite/basic.expected index 0f01725f8..d84103d95 100644 --- a/tests/unittests/resources/DebuggerSuite/basic.expected +++ b/tests/unittests/resources/DebuggerSuite/basic.expected @@ -13,6 +13,17 @@ dbg[pp:0,ip:3]:003: dbg[pp:0,ip:3]:004: b)) dbg[pp:0,ip:3]:005> (prn c) 11 +dbg[pp:0,ip:3]:006> trace +HALT +POP +CALL_SYMBOL prn, 1 +LOAD_FAST_BY_INDEX (0) +PUSH_RETURN_ADDRESS (0x7) +HALT +STORE ? +ADD +LOAD_FAST b +LOAD_FAST a dbg[pp:0,ip:3]:006> c dbg: continue ark: after first breakpoint, a=5, b=6 @@ -28,6 +39,17 @@ In file tests/unittests/resources/DebuggerSuite/basic.ark:7 dbg[pp:1,ip:5]:000> (prn x y z) 567 +dbg[pp:1,ip:5]:001> trace +HALT +POP +CALL_SYMBOL prn, 3 +LOAD_FAST z +LOAD_FAST y +LOAD_FAST x +PUSH_RETURN_ADDRESS (0x5) +BREAKPOINT +BUILTIN true +STORE x dbg[pp:1,ip:5]:001> help Available commands: help -- display this message @@ -36,6 +58,7 @@ Available commands: stack -- show the last n values on the stack locals -- show the last n values on the locals' stack ptr -- show the values of the VM pointers + trace -- show the last n executed instructions dbg[pp:1,ip:5]:001> continue dbg: continue ark: in (foo x y z), after second breakpoint diff --git a/tests/unittests/resources/DebuggerSuite/basic.prompt b/tests/unittests/resources/DebuggerSuite/basic.prompt index 42369549e..af8017ccb 100644 --- a/tests/unittests/resources/DebuggerSuite/basic.prompt +++ b/tests/unittests/resources/DebuggerSuite/basic.prompt @@ -4,7 +4,9 @@ a b)) (prn c) +trace c (prn x y z) +trace help continue diff --git a/tests/unittests/resources/DebuggerSuite/parsing.ark b/tests/unittests/resources/DebuggerSuite/parsing.ark new file mode 100644 index 000000000..9fc36058b --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/parsing.ark @@ -0,0 +1,3 @@ +(let a 5) +(let b 6) +(breakpoint) diff --git a/tests/unittests/resources/DebuggerSuite/parsing.expected b/tests/unittests/resources/DebuggerSuite/parsing.expected new file mode 100644 index 000000000..aba100c01 --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/parsing.expected @@ -0,0 +1,20 @@ +In file tests/unittests/resources/DebuggerSuite/parsing.ark:3 + 1 | (let a 5) + 2 | (let b 6) + 3 | (breakpoint) + | ^~~~~~~~~~~ + 4 | + +dbg[pp:0,ip:3]:000> trace +BREAKPOINT +LOAD_CONST_STORE 6 (Number), b +LOAD_CONST_STORE 5 (Number), a +dbg[pp:0,ip:3]:000> trace 0 +dbg[pp:0,ip:3]:000> trace 1 2 +Too many arguments provided to trace, expected 1, got 2 +dbg[pp:0,ip:3]:000> trace a +Couldn't parse argument as an unsigned integer +dbg[pp:0,ip:3]:000> trace -1 +Couldn't parse argument as an unsigned integer +dbg[pp:0,ip:3]:000> c +dbg: continue diff --git a/tests/unittests/resources/DebuggerSuite/parsing.prompt b/tests/unittests/resources/DebuggerSuite/parsing.prompt new file mode 100644 index 000000000..05e0bff1f --- /dev/null +++ b/tests/unittests/resources/DebuggerSuite/parsing.prompt @@ -0,0 +1,6 @@ +trace +trace 0 +trace 1 2 +trace a +trace -1 +c