ArkScript
A small, lisp-inspired, functional scripting language
VM.cpp
Go to the documentation of this file.
1#include <Ark/VM/VM.hpp>
2
3#include <utility>
4#include <numeric>
5#include <fmt/core.h>
6#include <fmt/color.h>
7#include <fmt/ostream.h>
8
9#include <Ark/Utils/Files.hpp>
10#include <Ark/Utils/Utils.hpp>
12#include <Ark/TypeChecker.hpp>
15
16namespace Ark
17{
18 using namespace internal;
19
20 namespace helper
21 {
22 inline Value tail(Value* a)
23 {
24 if (a->valueType() == ValueType::List)
25 {
26 if (a->constList().size() < 2)
27 return Value(ValueType::List);
28
29 std::vector<Value> tmp(a->constList().size() - 1);
30 for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
31 tmp[i - 1] = a->constList()[i];
32 return Value(std::move(tmp));
33 }
34 if (a->valueType() == ValueType::String)
35 {
36 if (a->string().size() < 2)
38
39 Value b { *a };
40 b.stringRef().erase(b.stringRef().begin());
41 return b;
42 }
43
45 "tail",
48 { *a });
49 }
50
51 inline Value head(Value* a)
52 {
53 if (a->valueType() == ValueType::List)
54 {
55 if (a->constList().empty())
56 return Builtins::nil;
57 return a->constList()[0];
58 }
59 if (a->valueType() == ValueType::String)
60 {
61 if (a->string().empty())
63 return Value(std::string(1, a->stringRef()[0]));
64 }
65
67 "head",
70 { *a });
71 }
72
73 inline Value at(Value& container, Value& index, VM& vm)
74 {
75 if (index.valueType() != ValueType::Number)
77 "@",
80 { container, index });
81
82 const auto num = static_cast<long>(index.number());
83
84 if (container.valueType() == ValueType::List)
85 {
86 const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.list().size()) + num : num);
87 if (i < container.list().size())
88 return container.list()[i];
89 else
91 ErrorKind::Index,
92 fmt::format("{} out of range {} (length {})", num, container.toString(vm), container.list().size()));
93 }
94 else if (container.valueType() == ValueType::String)
95 {
96 const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.string().size()) + num : num);
97 if (i < container.string().size())
98 return Value(std::string(1, container.string()[i]));
99 else
101 ErrorKind::Index,
102 fmt::format("{} out of range \"{}\" (length {})", num, container.string(), container.string().size()));
103 }
104 else
106 "@",
109 { container, index });
110 }
111
112 inline double doMath(double a, double b, const Instruction op)
113 {
114 if (op == ADD)
115 a += b;
116 else if (op == SUB)
117 a -= b;
118 else if (op == MUL)
119 a *= b;
120 else if (op == DIV)
121 {
122 if (b == 0)
123 Ark::VM::throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a, b));
124 a /= b;
125 }
126
127 return a;
128 }
129
130 inline std::string mathInstToStr(const Instruction op)
131 {
132 if (op == ADD)
133 return "+";
134 if (op == SUB)
135 return "-";
136 if (op == MUL)
137 return "*";
138 if (op == DIV)
139 return "/";
140 return "???";
141 }
142 }
143
144 VM::VM(State& state) noexcept :
145 m_state(state), m_exit_code(0), m_running(false)
146 {
147 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
148 }
149
150 void VM::init() noexcept
151 {
152 ExecutionContext& context = *m_execution_contexts.back();
153 for (const auto& c : m_execution_contexts)
154 {
155 c->ip = 0;
156 c->pp = 0;
157 c->sp = 0;
158 }
159
160 context.sp = 0;
161 context.fc = 1;
162
163 m_shared_lib_objects.clear();
164 context.stacked_closure_scopes.clear();
165 context.stacked_closure_scopes.emplace_back(nullptr);
166
167 context.saved_scope.reset();
168 m_exit_code = 0;
169
170 context.locals.clear();
171 context.locals.reserve(128);
172 context.locals.emplace_back(context.scopes_storage.data(), 0);
173
174 // loading bound stuff
175 // put them in the global frame if we can, aka the first one
176 for (const auto& [sym_id, value] : m_state.m_binded)
177 {
178 auto it = std::ranges::find(m_state.m_symbols, sym_id);
179 if (it != m_state.m_symbols.end())
180 context.locals[0].pushBack(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
181 }
182 }
183
184 Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context)
185 {
186 if (closure->valueType() != ValueType::Closure)
187 {
188 if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
190 ErrorKind::Type,
191 fmt::format(
192 "`{}' is a {}, not a Closure, can not get the field `{}' from it",
194 std::to_string(closure->valueType()),
195 m_state.m_symbols[id]));
196 else
197 throwVMError(ErrorKind::Type,
198 fmt::format(
199 "{} is not a Closure, can not get the field `{}' from it",
200 std::to_string(closure->valueType()),
201 m_state.m_symbols[id]));
202 }
203
204 if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
205 {
206 // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
207 if (m_state.inst(context.pp, context.ip) == CALL)
208 return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
209 else
210 return *field;
211 }
212 else
213 {
214 if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
216 ErrorKind::Scope,
217 fmt::format(
218 "`{0}' isn't in the closure environment: {1}",
219 m_state.m_symbols[id],
220 closure->refClosure().toString(*this)));
222 ErrorKind::Scope,
223 fmt::format(
224 "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
225 "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
226 m_state.m_symbols[id],
227 closure->refClosure().toString(*this)));
228 }
229 }
230
231 Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
232 {
234 if (count != 0)
235 l.list().reserve(count);
236
237 for (std::size_t i = 0; i < count; ++i)
238 l.push_back(*popAndResolveAsPtr(context));
239
240 return l;
241 }
242
243 void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
244 {
245 if (list->valueType() != ValueType::List)
246 {
247 std::vector<Value> args = { *list };
248 for (std::size_t i = 0; i < count; ++i)
249 args.push_back(*popAndResolveAsPtr(context));
251 "append!",
252 { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
253 args);
254 }
255
256 for (std::size_t i = 0; i < count; ++i)
257 list->push_back(*popAndResolveAsPtr(context));
258 }
259
260 Value& VM::operator[](const std::string& name) noexcept
261 {
262 // find id of object
263 const auto it = std::ranges::find(m_state.m_symbols, name);
264 if (it == m_state.m_symbols.end())
265 {
266 m_no_value = Builtins::nil;
267 return m_no_value;
268 }
269
270 const auto dist = std::distance(m_state.m_symbols.begin(), it);
271 if (std::cmp_less(dist, MaxValue16Bits))
272 {
273 ExecutionContext& context = *m_execution_contexts.front();
274
275 const auto id = static_cast<uint16_t>(dist);
276 Value* var = findNearestVariable(id, context);
277 if (var != nullptr)
278 return *var;
279 }
280
281 m_no_value = Builtins::nil;
282 return m_no_value;
283 }
284
285 void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
286 {
287 namespace fs = std::filesystem;
288
289 const std::string file = m_state.m_constants[id].stringRef();
290
291 std::string path = file;
292 // bytecode loaded from file
294 path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
295
296 std::shared_ptr<SharedLibrary> lib;
297 // if it exists alongside the .arkc file
298 if (Utils::fileExists(path))
299 lib = std::make_shared<SharedLibrary>(path);
300 else
301 {
302 for (auto const& v : m_state.m_libenv)
303 {
304 std::string lib_path = (fs::path(v) / fs::path(file)).string();
305
306 // if it's already loaded don't do anything
307 if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
308 return (val->path() == path || val->path() == lib_path);
309 }) != m_shared_lib_objects.end())
310 return;
311
312 // check in lib_path
313 if (Utils::fileExists(lib_path))
314 {
315 lib = std::make_shared<SharedLibrary>(lib_path);
316 break;
317 }
318 }
319 }
320
321 if (!lib)
322 {
323 auto lib_path = std::accumulate(
324 std::next(m_state.m_libenv.begin()),
325 m_state.m_libenv.end(),
326 m_state.m_libenv[0].string(),
327 [](const std::string& a, const fs::path& b) -> std::string {
328 return a + "\n\t- " + b.string();
329 });
331 ErrorKind::Module,
332 fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
333 }
334
335 m_shared_lib_objects.emplace_back(lib);
336
337 // load the mapping from the dynamic library
338 try
339 {
340 std::vector<ScopeView::pair_t> data;
341 const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
342
343 std::size_t i = 0;
344 while (map[i].name != nullptr)
345 {
346 const auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
347 if (it != m_state.m_symbols.end())
348 data.emplace_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
349
350 ++i;
351 }
352
353 context.locals.back().insertFront(data);
354 }
355 catch (const std::system_error& e)
356 {
358 ErrorKind::Module,
359 fmt::format(
360 "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
361 file, e.what()));
362 }
363 }
364
365 void VM::exit(const int code) noexcept
366 {
367 m_exit_code = code;
368 m_running = false;
369 }
370
372 {
373 const std::lock_guard lock(m_mutex);
374
375 ExecutionContext* ctx = nullptr;
376
377 // Try and find a free execution context.
378 // If there is only one context, this is the primary one, which can't be reused.
379 // Otherwise, we can check if a context is marked as free and reserve it!
380 // It is possible that all contexts are being used, thus we will create one (active by default) in that case.
381
382 if (m_execution_contexts.size() > 1)
383 {
384 const auto it = std::ranges::find_if(
386 [](const std::unique_ptr<ExecutionContext>& context) -> bool {
387 return !context->primary && context->isFree();
388 });
389
390 if (it != m_execution_contexts.end())
391 {
392 ctx = it->get();
393 ctx->setActive(true);
394 // reset the context before using it
395 ctx->sp = 0;
396 ctx->saved_scope.reset();
397 ctx->stacked_closure_scopes.clear();
398 ctx->locals.clear();
399 }
400 }
401
402 if (ctx == nullptr)
403 ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
404
405 assert(!ctx->primary && "The new context shouldn't be marked as primary!");
406 assert(ctx != m_execution_contexts.front().get() && "The new context isn't really new!");
407
408 const ExecutionContext& primary_ctx = *m_execution_contexts.front();
409 ctx->locals.reserve(primary_ctx.locals.size());
410 ctx->scopes_storage = primary_ctx.scopes_storage;
411 ctx->stacked_closure_scopes.emplace_back(nullptr);
412 ctx->fc = 1;
413
414 for (const auto& scope_view : primary_ctx.locals)
415 {
416 auto& new_scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), scope_view.m_start);
417 for (std::size_t i = 0; i < scope_view.size(); ++i)
418 {
419 const auto& [id, val] = scope_view.atPos(i);
420 new_scope.pushBack(id, val);
421 }
422 }
423
424 return ctx;
425 }
426
428 {
429 const std::lock_guard lock(m_mutex);
430
431 // 1 + 4 additional contexts, it's a bit much (~600kB per context) to have in memory
432 if (m_execution_contexts.size() > 5)
433 {
434 const auto it =
435 std::ranges::remove_if(
437 [ec](const std::unique_ptr<ExecutionContext>& ctx) {
438 return ctx.get() == ec;
439 })
440 .begin();
441 m_execution_contexts.erase(it);
442 }
443 else
444 {
445 // mark the used context as ready to be used again
446 for (std::size_t i = 1; i < m_execution_contexts.size(); ++i)
447 {
448 if (m_execution_contexts[i].get() == ec)
449 {
450 ec->setActive(false);
451 break;
452 }
453 }
454 }
455 }
456
457 Future* VM::createFuture(std::vector<Value>& args)
458 {
459 const std::lock_guard lock(m_mutex_futures);
460
462 // so that we have access to the presumed symbol id of the function we are calling
463 // assuming that the callee is always the global context
464 ctx->last_symbol = m_execution_contexts.front()->last_symbol;
465
466 m_futures.push_back(std::make_unique<Future>(ctx, this, args));
467 return m_futures.back().get();
468 }
469
471 {
472 const std::lock_guard lock(m_mutex_futures);
473
474 std::erase_if(
475 m_futures,
476 [f](const std::unique_ptr<Future>& future) {
477 return future.get() == f;
478 });
479 }
480
482 {
483 // load the mapping from the dynamic library
484 try
485 {
486 for (const auto& shared_lib : m_shared_lib_objects)
487 {
488 const mapping* map = shared_lib->get<mapping* (*)()>("getFunctionsMapping")();
489 // load the mapping data
490 std::size_t i = 0;
491 while (map[i].name != nullptr)
492 {
493 // put it in the global frame, aka the first one
494 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
495 if (it != m_state.m_symbols.end())
496 m_execution_contexts[0]->locals[0].pushBack(
497 static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
498 Value(map[i].value));
499
500 ++i;
501 }
502 }
503
504 return true;
505 }
506 catch (const std::system_error&)
507 {
508 return false;
509 }
510 }
511
512 void VM::throwVMError(ErrorKind kind, const std::string& message)
513 {
514 throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
515 }
516
517 int VM::run(const bool fail_with_exception)
518 {
519 init();
520 safeRun(*m_execution_contexts[0], 0, fail_with_exception);
521 return m_exit_code;
522 }
523
524 int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
525 {
526#if ARK_USE_COMPUTED_GOTOS
527# define TARGET(op) TARGET_##op:
528# define DISPATCH_GOTO() \
529 _Pragma("GCC diagnostic push") \
530 _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
531 _Pragma("GCC diagnostic pop")
532# define GOTO_HALT() goto dispatch_end
533#else
534# define TARGET(op) case op:
535# define DISPATCH_GOTO() goto dispatch_opcode
536# define GOTO_HALT() break
537#endif
538
539#define NEXTOPARG() \
540 do \
541 { \
542 inst = m_state.inst(context.pp, context.ip); \
543 padding = m_state.inst(context.pp, context.ip + 1); \
544 arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) + \
545 m_state.inst(context.pp, context.ip + 3)); \
546 context.ip += 4; \
547 context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize; \
548 if (context.inst_exec_counter < 2 && context.sp >= VMStackSize) \
549 { \
550 if (context.pp != 0) \
551 throw Error("Stack overflow. You could consider rewriting your function to make use of tail-call optimization."); \
552 else \
553 throw Error("Stack overflow. Are you trying to call a function with too many arguments?"); \
554 } \
555 } while (false)
556#define DISPATCH() \
557 NEXTOPARG(); \
558 DISPATCH_GOTO();
559#define UNPACK_ARGS() \
560 do \
561 { \
562 secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
563 primary_arg = arg & 0x0fff; \
564 } while (false)
565
566#if ARK_USE_COMPUTED_GOTOS
567# pragma GCC diagnostic push
568# pragma GCC diagnostic ignored "-Wpedantic"
569 constexpr std::array opcode_targets = {
570 // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
571 &&TARGET_NOP,
572 &&TARGET_LOAD_SYMBOL,
573 &&TARGET_LOAD_SYMBOL_BY_INDEX,
574 &&TARGET_LOAD_CONST,
575 &&TARGET_POP_JUMP_IF_TRUE,
576 &&TARGET_STORE,
577 &&TARGET_STORE_REF,
578 &&TARGET_SET_VAL,
579 &&TARGET_POP_JUMP_IF_FALSE,
580 &&TARGET_JUMP,
581 &&TARGET_RET,
582 &&TARGET_HALT,
583 &&TARGET_PUSH_RETURN_ADDRESS,
584 &&TARGET_CALL,
585 &&TARGET_CAPTURE,
586 &&TARGET_RENAME_NEXT_CAPTURE,
587 &&TARGET_BUILTIN,
588 &&TARGET_DEL,
589 &&TARGET_MAKE_CLOSURE,
590 &&TARGET_GET_FIELD,
591 &&TARGET_PLUGIN,
592 &&TARGET_LIST,
593 &&TARGET_APPEND,
594 &&TARGET_CONCAT,
595 &&TARGET_APPEND_IN_PLACE,
596 &&TARGET_CONCAT_IN_PLACE,
597 &&TARGET_POP_LIST,
598 &&TARGET_POP_LIST_IN_PLACE,
599 &&TARGET_SET_AT_INDEX,
600 &&TARGET_SET_AT_2_INDEX,
601 &&TARGET_POP,
602 &&TARGET_SHORTCIRCUIT_AND,
603 &&TARGET_SHORTCIRCUIT_OR,
604 &&TARGET_CREATE_SCOPE,
605 &&TARGET_RESET_SCOPE_JUMP,
606 &&TARGET_POP_SCOPE,
607 &&TARGET_GET_CURRENT_PAGE_ADDR,
608 &&TARGET_ADD,
609 &&TARGET_SUB,
610 &&TARGET_MUL,
611 &&TARGET_DIV,
612 &&TARGET_GT,
613 &&TARGET_LT,
614 &&TARGET_LE,
615 &&TARGET_GE,
616 &&TARGET_NEQ,
617 &&TARGET_EQ,
618 &&TARGET_LEN,
619 &&TARGET_EMPTY,
620 &&TARGET_TAIL,
621 &&TARGET_HEAD,
622 &&TARGET_ISNIL,
623 &&TARGET_ASSERT,
624 &&TARGET_TO_NUM,
625 &&TARGET_TO_STR,
626 &&TARGET_AT,
627 &&TARGET_AT_AT,
628 &&TARGET_MOD,
629 &&TARGET_TYPE,
630 &&TARGET_HASFIELD,
631 &&TARGET_NOT,
632 &&TARGET_LOAD_CONST_LOAD_CONST,
633 &&TARGET_LOAD_CONST_STORE,
634 &&TARGET_LOAD_CONST_SET_VAL,
635 &&TARGET_STORE_FROM,
636 &&TARGET_STORE_FROM_INDEX,
637 &&TARGET_SET_VAL_FROM,
638 &&TARGET_SET_VAL_FROM_INDEX,
639 &&TARGET_INCREMENT,
640 &&TARGET_INCREMENT_BY_INDEX,
641 &&TARGET_INCREMENT_STORE,
642 &&TARGET_DECREMENT,
643 &&TARGET_DECREMENT_BY_INDEX,
644 &&TARGET_DECREMENT_STORE,
645 &&TARGET_STORE_TAIL,
646 &&TARGET_STORE_TAIL_BY_INDEX,
647 &&TARGET_STORE_HEAD,
648 &&TARGET_STORE_HEAD_BY_INDEX,
649 &&TARGET_STORE_LIST,
650 &&TARGET_SET_VAL_TAIL,
651 &&TARGET_SET_VAL_TAIL_BY_INDEX,
652 &&TARGET_SET_VAL_HEAD,
653 &&TARGET_SET_VAL_HEAD_BY_INDEX,
654 &&TARGET_CALL_BUILTIN,
655 &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
656 &&TARGET_LT_CONST_JUMP_IF_FALSE,
657 &&TARGET_LT_CONST_JUMP_IF_TRUE,
658 &&TARGET_LT_SYM_JUMP_IF_FALSE,
659 &&TARGET_GT_CONST_JUMP_IF_TRUE,
660 &&TARGET_GT_CONST_JUMP_IF_FALSE,
661 &&TARGET_GT_SYM_JUMP_IF_FALSE,
662 &&TARGET_EQ_CONST_JUMP_IF_TRUE,
663 &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
664 &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
665 &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
666 &&TARGET_CALL_SYMBOL,
667 &&TARGET_CALL_CURRENT_PAGE,
668 &&TARGET_GET_FIELD_FROM_SYMBOL,
669 &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
670 &&TARGET_AT_SYM_SYM,
671 &&TARGET_AT_SYM_INDEX_SYM_INDEX,
672 &&TARGET_AT_SYM_INDEX_CONST,
673 &&TARGET_CHECK_TYPE_OF,
674 &&TARGET_CHECK_TYPE_OF_BY_INDEX,
675 &&TARGET_APPEND_IN_PLACE_SYM,
676 &&TARGET_APPEND_IN_PLACE_SYM_INDEX,
677 &&TARGET_STORE_LEN,
678 &&TARGET_LT_LEN_SYM_JUMP_IF_FALSE,
679 &&TARGET_MUL_BY,
680 &&TARGET_MUL_BY_INDEX,
681 &&TARGET_MUL_SET_VAL,
682 &&TARGET_FUSED_MATH
683 };
684
685 static_assert(opcode_targets.size() == static_cast<std::size_t>(Instruction::InstructionsCount) && "Some instructions are not implemented in the VM");
686# pragma GCC diagnostic pop
687#endif
688
689 try
690 {
691 uint8_t inst = 0;
692 uint8_t padding = 0;
693 uint16_t arg = 0;
694 uint16_t primary_arg = 0;
695 uint16_t secondary_arg = 0;
696
697 m_running = true;
698
699 DISPATCH();
700 // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
701 {
702#if !ARK_USE_COMPUTED_GOTOS
703 dispatch_opcode:
704 switch (inst)
705#endif
706 {
707#pragma region "Instructions"
708 TARGET(NOP)
709 {
710 DISPATCH();
711 }
712
714 {
715 push(loadSymbol(arg, context), context);
716 DISPATCH();
717 }
718
720 {
721 push(loadSymbolFromIndex(arg, context), context);
722 DISPATCH();
723 }
724
726 {
727 push(loadConstAsPtr(arg), context);
728 DISPATCH();
729 }
730
732 {
733 if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
734 jump(arg, context);
735 DISPATCH();
736 }
737
739 {
740 store(arg, popAndResolveAsPtr(context), context);
741 DISPATCH();
742 }
743
745 {
746 // Not resolving a potential ref is on purpose!
747 // This instruction is only used by functions when storing arguments
748 const Value* tmp = pop(context);
749 store(arg, tmp, context);
750 DISPATCH();
751 }
752
754 {
755 setVal(arg, popAndResolveAsPtr(context), context);
756 DISPATCH();
757 }
758
760 {
761 if (Value boolean = *popAndResolveAsPtr(context); !boolean)
762 jump(arg, context);
763 DISPATCH();
764 }
765
766 TARGET(JUMP)
767 {
768 jump(arg, context);
769 DISPATCH();
770 }
771
772 TARGET(RET)
773 {
774 {
775 Value ip_or_val = *popAndResolveAsPtr(context);
776 // no return value on the stack
777 if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
778 {
779 context.ip = ip_or_val.pageAddr();
780 // we always push PP then IP, thus the next value
781 // MUST be the page pointer
782 context.pp = pop(context)->pageAddr();
783
784 returnFromFuncCall(context);
785 push(Builtins::nil, context);
786 }
787 // value on the stack
788 else [[likely]]
789 {
790 const Value* ip = popAndResolveAsPtr(context);
791 assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
792 context.ip = ip->pageAddr();
793 context.pp = pop(context)->pageAddr();
794
795 returnFromFuncCall(context);
796 push(std::move(ip_or_val), context);
797 }
798
799 if (context.fc <= untilFrameCount)
800 GOTO_HALT();
801 }
802
803 DISPATCH();
804 }
805
806 TARGET(HALT)
807 {
808 m_running = false;
809 GOTO_HALT();
810 }
811
813 {
814 push(Value(static_cast<PageAddr_t>(context.pp)), context);
815 // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
816 push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
817 context.inst_exec_counter++;
818 DISPATCH();
819 }
820
821 TARGET(CALL)
822 {
823 call(context, arg);
824 if (!m_running)
825 GOTO_HALT();
826 DISPATCH();
827 }
828
830 {
831 if (!context.saved_scope)
832 context.saved_scope = ClosureScope();
833
834 const Value* ptr = findNearestVariable(arg, context);
835 if (!ptr)
836 throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
837 else
838 {
839 ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
840 uint16_t id = context.capture_rename_id.value_or(arg);
841 context.saved_scope.value().push_back(id, *ptr);
842 context.capture_rename_id.reset();
843 }
844
845 DISPATCH();
846 }
847
849 {
850 context.capture_rename_id = arg;
851 DISPATCH();
852 }
853
855 {
856 push(Builtins::builtins[arg].second, context);
857 DISPATCH();
858 }
859
860 TARGET(DEL)
861 {
862 if (Value* var = findNearestVariable(arg, context); var != nullptr)
863 {
864 if (var->valueType() == ValueType::User)
865 var->usertypeRef().del();
866 *var = Value();
867 DISPATCH();
868 }
869
870 throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
871 }
872
874 {
875 push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
876 context.saved_scope.reset();
877 DISPATCH();
878 }
879
881 {
882 Value* var = popAndResolveAsPtr(context);
883 push(getField(var, arg, context), context);
884 DISPATCH();
885 }
886
888 {
889 loadPlugin(arg, context);
890 DISPATCH();
891 }
892
893 TARGET(LIST)
894 {
895 {
896 Value l = createList(arg, context);
897 push(std::move(l), context);
898 }
899 DISPATCH();
900 }
901
903 {
904 {
905 Value* list = popAndResolveAsPtr(context);
906 if (list->valueType() != ValueType::List)
907 {
908 std::vector<Value> args = { *list };
909 for (uint16_t i = 0; i < arg; ++i)
910 args.push_back(*popAndResolveAsPtr(context));
912 "append",
913 { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
914 args);
915 }
916
917 const auto size = static_cast<uint16_t>(list->constList().size());
918
919 Value obj { *list };
920 obj.list().reserve(size + arg);
921
922 for (uint16_t i = 0; i < arg; ++i)
923 obj.push_back(*popAndResolveAsPtr(context));
924 push(std::move(obj), context);
925 }
926 DISPATCH();
927 }
928
930 {
931 {
932 Value* list = popAndResolveAsPtr(context);
933 Value obj { *list };
934
935 for (uint16_t i = 0; i < arg; ++i)
936 {
937 Value* next = popAndResolveAsPtr(context);
938
939 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
941 "concat",
943 { *list, *next });
944
945 std::ranges::copy(next->list(), std::back_inserter(obj.list()));
946 }
947 push(std::move(obj), context);
948 }
949 DISPATCH();
950 }
951
953 {
954 Value* list = popAndResolveAsPtr(context);
955 listAppendInPlace(list, arg, context);
956 DISPATCH();
957 }
958
960 {
961 Value* list = popAndResolveAsPtr(context);
962
963 for (uint16_t i = 0; i < arg; ++i)
964 {
965 Value* next = popAndResolveAsPtr(context);
966
967 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
969 "concat!",
971 { *list, *next });
972
973 std::ranges::copy(next->list(), std::back_inserter(list->list()));
974 }
975 DISPATCH();
976 }
977
979 {
980 {
981 Value list = *popAndResolveAsPtr(context);
982 Value number = *popAndResolveAsPtr(context);
983
984 if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
986 "pop",
988 { list, number });
989
990 long idx = static_cast<long>(number.number());
991 idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
992 if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
994 ErrorKind::Index,
995 fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
996
997 list.list().erase(list.list().begin() + idx);
998 push(list, context);
999 }
1000 DISPATCH();
1001 }
1002
1004 {
1005 {
1006 Value* list = popAndResolveAsPtr(context);
1007 Value number = *popAndResolveAsPtr(context);
1008
1009 if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
1011 "pop!",
1013 { *list, number });
1014
1015 long idx = static_cast<long>(number.number());
1016 idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
1017 if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
1019 ErrorKind::Index,
1020 fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
1021
1022 list->list().erase(list->list().begin() + idx);
1023 }
1024 DISPATCH();
1025 }
1026
1028 {
1029 {
1030 Value* list = popAndResolveAsPtr(context);
1031 Value number = *popAndResolveAsPtr(context);
1032 Value new_value = *popAndResolveAsPtr(context);
1033
1034 if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
1036 "@=",
1037 { { types::Contract {
1040 types::Typedef("new_value", ValueType::Any) } } },
1041 { types::Contract {
1042 { types::Typedef("string", ValueType::String),
1044 types::Typedef("char", ValueType::String) } } } },
1045 { *list, number, new_value });
1046
1047 const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
1048 long idx = static_cast<long>(number.number());
1049 idx = idx < 0 ? static_cast<long>(size) + idx : idx;
1050 if (std::cmp_greater_equal(idx, size) || idx < 0)
1052 ErrorKind::Index,
1053 fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
1054
1055 if (list->valueType() == ValueType::List)
1056 list->list()[static_cast<std::size_t>(idx)] = new_value;
1057 else
1058 list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
1059 }
1060 DISPATCH();
1061 }
1062
1064 {
1065 {
1066 Value* list = popAndResolveAsPtr(context);
1067 Value x = *popAndResolveAsPtr(context);
1068 Value y = *popAndResolveAsPtr(context);
1069 Value new_value = *popAndResolveAsPtr(context);
1070
1073 "@@=",
1074 { { types::Contract {
1078 types::Typedef("new_value", ValueType::Any) } } } },
1079 { *list, x, y, new_value });
1080
1081 long idx_y = static_cast<long>(x.number());
1082 idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
1083 if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
1085 ErrorKind::Index,
1086 fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
1087
1088 if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
1089 (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
1091 "@@=",
1092 { { types::Contract {
1096 types::Typedef("new_value", ValueType::Any) } } },
1097 { types::Contract {
1098 { types::Typedef("string", ValueType::String),
1101 types::Typedef("char", ValueType::String) } } } },
1102 { *list, x, y, new_value });
1103
1104 const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
1105 const std::size_t size =
1106 is_list
1107 ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
1108 : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
1109
1110 long idx_x = static_cast<long>(y.number());
1111 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1112 if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
1114 ErrorKind::Index,
1115 fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1116
1117 if (is_list)
1118 list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
1119 else
1120 list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
1121 }
1122 DISPATCH();
1123 }
1124
1125 TARGET(POP)
1126 {
1127 pop(context);
1128 DISPATCH();
1129 }
1130
1132 {
1133 if (!*peekAndResolveAsPtr(context))
1134 jump(arg, context);
1135 else
1136 pop(context);
1137 DISPATCH();
1138 }
1139
1141 {
1142 if (!!*peekAndResolveAsPtr(context))
1143 jump(arg, context);
1144 else
1145 pop(context);
1146 DISPATCH();
1147 }
1148
1150 {
1151 context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
1152 DISPATCH();
1153 }
1154
1156 {
1157 context.locals.back().reset();
1158 jump(arg, context);
1159 DISPATCH();
1160 }
1161
1163 {
1164 context.locals.pop_back();
1165 DISPATCH();
1166 }
1167
1169 {
1170 context.last_symbol = arg;
1171 push(Value(static_cast<PageAddr_t>(context.pp)), context);
1172 DISPATCH();
1173 }
1174
1175#pragma endregion
1176
1177#pragma region "Operators"
1178
1179 TARGET(ADD)
1180 {
1181 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1182
1183 if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
1184 push(Value(a->number() + b->number()), context);
1185 else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
1186 push(Value(a->string() + b->string()), context);
1187 else
1189 "+",
1192 { *a, *b });
1193 DISPATCH();
1194 }
1195
1196 TARGET(SUB)
1197 {
1198 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1199
1200 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1202 "-",
1204 { *a, *b });
1205 push(Value(a->number() - b->number()), context);
1206 DISPATCH();
1207 }
1208
1209 TARGET(MUL)
1210 {
1211 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1212
1213 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1215 "*",
1217 { *a, *b });
1218 push(Value(a->number() * b->number()), context);
1219 DISPATCH();
1220 }
1221
1222 TARGET(DIV)
1223 {
1224 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1225
1226 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1228 "/",
1230 { *a, *b });
1231 auto d = b->number();
1232 if (d == 0)
1233 throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1234
1235 push(Value(a->number() / d), context);
1236 DISPATCH();
1237 }
1238
1239 TARGET(GT)
1240 {
1241 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1242 push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
1243 DISPATCH();
1244 }
1245
1246 TARGET(LT)
1247 {
1248 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1249 push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
1250 DISPATCH();
1251 }
1252
1253 TARGET(LE)
1254 {
1255 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1256 push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
1257 DISPATCH();
1258 }
1259
1260 TARGET(GE)
1261 {
1262 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1263 push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
1264 DISPATCH();
1265 }
1266
1267 TARGET(NEQ)
1268 {
1269 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1270 push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1271 DISPATCH();
1272 }
1273
1274 TARGET(EQ)
1275 {
1276 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1277 push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
1278 DISPATCH();
1279 }
1280
1281 TARGET(LEN)
1282 {
1283 const Value* a = popAndResolveAsPtr(context);
1284
1285 if (a->valueType() == ValueType::List)
1286 push(Value(static_cast<int>(a->constList().size())), context);
1287 else if (a->valueType() == ValueType::String)
1288 push(Value(static_cast<int>(a->string().size())), context);
1289 else
1291 "len",
1292 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1293 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1294 { *a });
1295 DISPATCH();
1296 }
1297
1298 TARGET(EMPTY)
1299 {
1300 const Value* a = popAndResolveAsPtr(context);
1301
1302 if (a->valueType() == ValueType::List)
1303 push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1304 else if (a->valueType() == ValueType::String)
1305 push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1306 else if (a->valueType() == ValueType::Nil)
1307 push(Builtins::trueSym, context);
1308 else
1310 "empty?",
1311 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1313 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1314 { *a });
1315 DISPATCH();
1316 }
1317
1318 TARGET(TAIL)
1319 {
1320 Value* const a = popAndResolveAsPtr(context);
1321 push(helper::tail(a), context);
1322 DISPATCH();
1323 }
1324
1325 TARGET(HEAD)
1326 {
1327 Value* const a = popAndResolveAsPtr(context);
1328 push(helper::head(a), context);
1329 DISPATCH();
1330 }
1331
1332 TARGET(ISNIL)
1333 {
1334 const Value* a = popAndResolveAsPtr(context);
1336 DISPATCH();
1337 }
1338
1339 TARGET(ASSERT)
1340 {
1341 Value* const b = popAndResolveAsPtr(context);
1342 Value* const a = popAndResolveAsPtr(context);
1343
1344 if (b->valueType() != ValueType::String)
1346 "assert",
1348 { *a, *b });
1349
1350 if (*a == Builtins::falseSym)
1351 throw AssertionFailed(b->stringRef());
1352 DISPATCH();
1353 }
1354
1355 TARGET(TO_NUM)
1356 {
1357 const Value* a = popAndResolveAsPtr(context);
1358
1359 if (a->valueType() != ValueType::String)
1361 "toNumber",
1362 { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1363 { *a });
1364
1365 double val;
1366 if (Utils::isDouble(a->string(), &val))
1367 push(Value(val), context);
1368 else
1369 push(Builtins::nil, context);
1370 DISPATCH();
1371 }
1372
1373 TARGET(TO_STR)
1374 {
1375 const Value* a = popAndResolveAsPtr(context);
1376 push(Value(a->toString(*this)), context);
1377 DISPATCH();
1378 }
1379
1380 TARGET(AT)
1381 {
1382 Value& b = *popAndResolveAsPtr(context);
1383 Value& a = *popAndResolveAsPtr(context);
1384 push(helper::at(a, b, *this), context);
1385 DISPATCH();
1386 }
1387
1388 TARGET(AT_AT)
1389 {
1390 {
1391 const Value* x = popAndResolveAsPtr(context);
1392 const Value* y = popAndResolveAsPtr(context);
1393 Value& list = *popAndResolveAsPtr(context);
1394
1395 if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
1396 list.valueType() != ValueType::List)
1398 "@@",
1399 { { types::Contract {
1402 types::Typedef("x", ValueType::Number) } } } },
1403 { list, *y, *x });
1404
1405 long idx_y = static_cast<long>(y->number());
1406 idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
1407 if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
1409 ErrorKind::Index,
1410 fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1411
1412 const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
1413 const std::size_t size =
1414 is_list
1415 ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
1416 : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
1417
1418 long idx_x = static_cast<long>(x->number());
1419 idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1420 if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
1422 ErrorKind::Index,
1423 fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1424
1425 if (is_list)
1426 push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
1427 else
1428 push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
1429 }
1430 DISPATCH();
1431 }
1432
1433 TARGET(MOD)
1434 {
1435 const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1436 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1438 "mod",
1440 { *a, *b });
1441 push(Value(std::fmod(a->number(), b->number())), context);
1442 DISPATCH();
1443 }
1444
1445 TARGET(TYPE)
1446 {
1447 const Value* a = popAndResolveAsPtr(context);
1448 push(Value(std::to_string(a->valueType())), context);
1449 DISPATCH();
1450 }
1451
1453 {
1454 {
1455 Value* const field = popAndResolveAsPtr(context);
1456 Value* const closure = popAndResolveAsPtr(context);
1457 if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
1459 "hasField",
1461 { *closure, *field });
1462
1463 auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
1464 if (it == m_state.m_symbols.end())
1465 {
1466 push(Builtins::falseSym, context);
1467 DISPATCH();
1468 }
1469
1470 auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1471 push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1472 }
1473 DISPATCH();
1474 }
1475
1476 TARGET(NOT)
1477 {
1478 const Value* a = popAndResolveAsPtr(context);
1479 push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
1480 DISPATCH();
1481 }
1482
1483#pragma endregion
1484
1485#pragma region "Super Instructions"
1487 {
1488 UNPACK_ARGS();
1489 push(loadConstAsPtr(primary_arg), context);
1490 push(loadConstAsPtr(secondary_arg), context);
1491 context.inst_exec_counter++;
1492 DISPATCH();
1493 }
1494
1496 {
1497 UNPACK_ARGS();
1498 store(secondary_arg, loadConstAsPtr(primary_arg), context);
1499 DISPATCH();
1500 }
1501
1503 {
1504 UNPACK_ARGS();
1505 setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
1506 DISPATCH();
1507 }
1508
1510 {
1511 UNPACK_ARGS();
1512 store(secondary_arg, loadSymbol(primary_arg, context), context);
1513 DISPATCH();
1514 }
1515
1517 {
1518 UNPACK_ARGS();
1519 store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1520 DISPATCH();
1521 }
1522
1524 {
1525 UNPACK_ARGS();
1526 setVal(secondary_arg, loadSymbol(primary_arg, context), context);
1527 DISPATCH();
1528 }
1529
1531 {
1532 UNPACK_ARGS();
1533 setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1534 DISPATCH();
1535 }
1536
1538 {
1539 UNPACK_ARGS();
1540 {
1541 Value* var = loadSymbol(primary_arg, context);
1542
1543 // use internal reference, shouldn't break anything so far, unless it's already a ref
1544 if (var->valueType() == ValueType::Reference)
1545 var = var->reference();
1546
1547 if (var->valueType() == ValueType::Number)
1548 push(Value(var->number() + secondary_arg), context);
1549 else
1551 "+",
1553 { *var, Value(secondary_arg) });
1554 }
1555 DISPATCH();
1556 }
1557
1559 {
1560 UNPACK_ARGS();
1561 {
1562 Value* var = loadSymbolFromIndex(primary_arg, context);
1563
1564 // use internal reference, shouldn't break anything so far, unless it's already a ref
1565 if (var->valueType() == ValueType::Reference)
1566 var = var->reference();
1567
1568 if (var->valueType() == ValueType::Number)
1569 push(Value(var->number() + secondary_arg), context);
1570 else
1572 "+",
1574 { *var, Value(secondary_arg) });
1575 }
1576 DISPATCH();
1577 }
1578
1580 {
1581 UNPACK_ARGS();
1582 {
1583 Value* var = loadSymbol(primary_arg, context);
1584
1585 // use internal reference, shouldn't break anything so far, unless it's already a ref
1586 if (var->valueType() == ValueType::Reference)
1587 var = var->reference();
1588
1589 if (var->valueType() == ValueType::Number)
1590 {
1591 auto val = Value(var->number() + secondary_arg);
1592 setVal(primary_arg, &val, context);
1593 }
1594 else
1596 "+",
1598 { *var, Value(secondary_arg) });
1599 }
1600 DISPATCH();
1601 }
1602
1604 {
1605 UNPACK_ARGS();
1606 {
1607 Value* var = loadSymbol(primary_arg, context);
1608
1609 // use internal reference, shouldn't break anything so far, unless it's already a ref
1610 if (var->valueType() == ValueType::Reference)
1611 var = var->reference();
1612
1613 if (var->valueType() == ValueType::Number)
1614 push(Value(var->number() - secondary_arg), context);
1615 else
1617 "-",
1619 { *var, Value(secondary_arg) });
1620 }
1621 DISPATCH();
1622 }
1623
1625 {
1626 UNPACK_ARGS();
1627 {
1628 Value* var = loadSymbolFromIndex(primary_arg, context);
1629
1630 // use internal reference, shouldn't break anything so far, unless it's already a ref
1631 if (var->valueType() == ValueType::Reference)
1632 var = var->reference();
1633
1634 if (var->valueType() == ValueType::Number)
1635 push(Value(var->number() - secondary_arg), context);
1636 else
1638 "-",
1640 { *var, Value(secondary_arg) });
1641 }
1642 DISPATCH();
1643 }
1644
1646 {
1647 UNPACK_ARGS();
1648 {
1649 Value* var = loadSymbol(primary_arg, context);
1650
1651 // use internal reference, shouldn't break anything so far, unless it's already a ref
1652 if (var->valueType() == ValueType::Reference)
1653 var = var->reference();
1654
1655 if (var->valueType() == ValueType::Number)
1656 {
1657 auto val = Value(var->number() - secondary_arg);
1658 setVal(primary_arg, &val, context);
1659 }
1660 else
1662 "-",
1664 { *var, Value(secondary_arg) });
1665 }
1666 DISPATCH();
1667 }
1668
1670 {
1671 UNPACK_ARGS();
1672 {
1673 Value* list = loadSymbol(primary_arg, context);
1674 Value tail = helper::tail(list);
1675 store(secondary_arg, &tail, context);
1676 }
1677 DISPATCH();
1678 }
1679
1681 {
1682 UNPACK_ARGS();
1683 {
1684 Value* list = loadSymbolFromIndex(primary_arg, context);
1685 Value tail = helper::tail(list);
1686 store(secondary_arg, &tail, context);
1687 }
1688 DISPATCH();
1689 }
1690
1692 {
1693 UNPACK_ARGS();
1694 {
1695 Value* list = loadSymbol(primary_arg, context);
1696 Value head = helper::head(list);
1697 store(secondary_arg, &head, context);
1698 }
1699 DISPATCH();
1700 }
1701
1703 {
1704 UNPACK_ARGS();
1705 {
1706 Value* list = loadSymbolFromIndex(primary_arg, context);
1707 Value head = helper::head(list);
1708 store(secondary_arg, &head, context);
1709 }
1710 DISPATCH();
1711 }
1712
1714 {
1715 UNPACK_ARGS();
1716 {
1717 Value l = createList(primary_arg, context);
1718 store(secondary_arg, &l, context);
1719 }
1720 DISPATCH();
1721 }
1722
1724 {
1725 UNPACK_ARGS();
1726 {
1727 Value* list = loadSymbol(primary_arg, context);
1728 Value tail = helper::tail(list);
1729 setVal(secondary_arg, &tail, context);
1730 }
1731 DISPATCH();
1732 }
1733
1735 {
1736 UNPACK_ARGS();
1737 {
1738 Value* list = loadSymbolFromIndex(primary_arg, context);
1739 Value tail = helper::tail(list);
1740 setVal(secondary_arg, &tail, context);
1741 }
1742 DISPATCH();
1743 }
1744
1746 {
1747 UNPACK_ARGS();
1748 {
1749 Value* list = loadSymbol(primary_arg, context);
1750 Value head = helper::head(list);
1751 setVal(secondary_arg, &head, context);
1752 }
1753 DISPATCH();
1754 }
1755
1757 {
1758 UNPACK_ARGS();
1759 {
1760 Value* list = loadSymbolFromIndex(primary_arg, context);
1761 Value head = helper::head(list);
1762 setVal(secondary_arg, &head, context);
1763 }
1764 DISPATCH();
1765 }
1766
1768 {
1769 UNPACK_ARGS();
1770 // no stack size check because we do not push IP/PP since we are just calling a builtin
1771 callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
1772 if (!m_running)
1773 GOTO_HALT();
1774 DISPATCH();
1775 }
1776
1778 {
1779 UNPACK_ARGS();
1780 // no stack size check because we do not push IP/PP since we are just calling a builtin
1781 callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
1782 if (!m_running)
1783 GOTO_HALT();
1784 DISPATCH();
1785 }
1786
1788 {
1789 UNPACK_ARGS();
1790 const Value* sym = popAndResolveAsPtr(context);
1791 if (!(*sym < *loadConstAsPtr(primary_arg)))
1792 jump(secondary_arg, context);
1793 DISPATCH();
1794 }
1795
1797 {
1798 UNPACK_ARGS();
1799 const Value* sym = popAndResolveAsPtr(context);
1800 if (*sym < *loadConstAsPtr(primary_arg))
1801 jump(secondary_arg, context);
1802 DISPATCH();
1803 }
1804
1806 {
1807 UNPACK_ARGS();
1808 const Value* sym = popAndResolveAsPtr(context);
1809 if (!(*sym < *loadSymbol(primary_arg, context)))
1810 jump(secondary_arg, context);
1811 DISPATCH();
1812 }
1813
1815 {
1816 UNPACK_ARGS();
1817 const Value* sym = popAndResolveAsPtr(context);
1818 const Value* cst = loadConstAsPtr(primary_arg);
1819 if (*cst < *sym)
1820 jump(secondary_arg, context);
1821 DISPATCH();
1822 }
1823
1825 {
1826 UNPACK_ARGS();
1827 const Value* sym = popAndResolveAsPtr(context);
1828 const Value* cst = loadConstAsPtr(primary_arg);
1829 if (!(*cst < *sym))
1830 jump(secondary_arg, context);
1831 DISPATCH();
1832 }
1833
1835 {
1836 UNPACK_ARGS();
1837 const Value* sym = popAndResolveAsPtr(context);
1838 const Value* rhs = loadSymbol(primary_arg, context);
1839 if (!(*rhs < *sym))
1840 jump(secondary_arg, context);
1841 DISPATCH();
1842 }
1843
1845 {
1846 UNPACK_ARGS();
1847 const Value* sym = popAndResolveAsPtr(context);
1848 if (*sym == *loadConstAsPtr(primary_arg))
1849 jump(secondary_arg, context);
1850 DISPATCH();
1851 }
1852
1854 {
1855 UNPACK_ARGS();
1856 const Value* sym = popAndResolveAsPtr(context);
1857 if (*sym == *loadSymbolFromIndex(primary_arg, context))
1858 jump(secondary_arg, context);
1859 DISPATCH();
1860 }
1861
1863 {
1864 UNPACK_ARGS();
1865 const Value* sym = popAndResolveAsPtr(context);
1866 if (*sym != *loadConstAsPtr(primary_arg))
1867 jump(secondary_arg, context);
1868 DISPATCH();
1869 }
1870
1872 {
1873 UNPACK_ARGS();
1874 const Value* sym = popAndResolveAsPtr(context);
1875 if (*sym == *loadSymbol(primary_arg, context))
1876 jump(secondary_arg, context);
1877 DISPATCH();
1878 }
1879
1881 {
1882 UNPACK_ARGS();
1883 call(context, secondary_arg, loadSymbol(primary_arg, context));
1884 if (!m_running)
1885 GOTO_HALT();
1886 DISPATCH();
1887 }
1888
1890 {
1891 UNPACK_ARGS();
1892 context.last_symbol = primary_arg;
1893 call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
1894 if (!m_running)
1895 GOTO_HALT();
1896 DISPATCH();
1897 }
1898
1900 {
1901 UNPACK_ARGS();
1902 push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1903 DISPATCH();
1904 }
1905
1907 {
1908 UNPACK_ARGS();
1909 push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
1910 DISPATCH();
1911 }
1912
1914 {
1915 UNPACK_ARGS();
1916 push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
1917 DISPATCH();
1918 }
1919
1921 {
1922 UNPACK_ARGS();
1923 push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
1924 DISPATCH();
1925 }
1926
1928 {
1929 UNPACK_ARGS();
1930 push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
1931 DISPATCH();
1932 }
1933
1935 {
1936 UNPACK_ARGS();
1937 const Value* sym = loadSymbol(primary_arg, context);
1938 const Value* cst = loadConstAsPtr(secondary_arg);
1939 push(
1940 cst->valueType() == ValueType::String &&
1941 std::to_string(sym->valueType()) == cst->string()
1944 context);
1945 DISPATCH();
1946 }
1947
1949 {
1950 UNPACK_ARGS();
1951 const Value* sym = loadSymbolFromIndex(primary_arg, context);
1952 const Value* cst = loadConstAsPtr(secondary_arg);
1953 push(
1954 cst->valueType() == ValueType::String &&
1955 std::to_string(sym->valueType()) == cst->string()
1958 context);
1959 DISPATCH();
1960 }
1961
1963 {
1964 UNPACK_ARGS();
1965 listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1966 DISPATCH();
1967 }
1968
1970 {
1971 UNPACK_ARGS();
1972 listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
1973 DISPATCH();
1974 }
1975
1977 {
1978 UNPACK_ARGS();
1979 {
1980 Value* a = loadSymbolFromIndex(primary_arg, context);
1981 Value len;
1982 if (a->valueType() == ValueType::List)
1983 len = Value(static_cast<int>(a->constList().size()));
1984 else if (a->valueType() == ValueType::String)
1985 len = Value(static_cast<int>(a->string().size()));
1986 else
1988 "len",
1989 { { types::Contract { { types::Typedef("value", ValueType::List) } },
1990 types::Contract { { types::Typedef("value", ValueType::String) } } } },
1991 { *a });
1992 store(secondary_arg, &len, context);
1993 }
1994 DISPATCH();
1995 }
1996
1998 {
1999 UNPACK_ARGS();
2000 {
2001 const Value* sym = loadSymbol(primary_arg, context);
2002 Value size;
2003
2004 if (sym->valueType() == ValueType::List)
2005 size = Value(static_cast<int>(sym->constList().size()));
2006 else if (sym->valueType() == ValueType::String)
2007 size = Value(static_cast<int>(sym->string().size()));
2008 else
2010 "len",
2011 { { types::Contract { { types::Typedef("value", ValueType::List) } },
2012 types::Contract { { types::Typedef("value", ValueType::String) } } } },
2013 { *sym });
2014
2015 if (!(*popAndResolveAsPtr(context) < size))
2016 jump(secondary_arg, context);
2017 }
2018 DISPATCH();
2019 }
2020
2021 TARGET(MUL_BY)
2022 {
2023 UNPACK_ARGS();
2024 {
2025 Value* var = loadSymbol(primary_arg, context);
2026 const int other = static_cast<int>(secondary_arg) - 2048;
2027
2028 // use internal reference, shouldn't break anything so far, unless it's already a ref
2029 if (var->valueType() == ValueType::Reference)
2030 var = var->reference();
2031
2032 if (var->valueType() == ValueType::Number)
2033 push(Value(var->number() * other), context);
2034 else
2036 "*",
2038 { *var, Value(other) });
2039 }
2040 DISPATCH();
2041 }
2042
2044 {
2045 UNPACK_ARGS();
2046 {
2047 Value* var = loadSymbolFromIndex(primary_arg, context);
2048 const int other = static_cast<int>(secondary_arg) - 2048;
2049
2050 // use internal reference, shouldn't break anything so far, unless it's already a ref
2051 if (var->valueType() == ValueType::Reference)
2052 var = var->reference();
2053
2054 if (var->valueType() == ValueType::Number)
2055 push(Value(var->number() * other), context);
2056 else
2058 "*",
2060 { *var, Value(other) });
2061 }
2062 DISPATCH();
2063 }
2064
2066 {
2067 UNPACK_ARGS();
2068 {
2069 Value* var = loadSymbol(primary_arg, context);
2070 const int other = static_cast<int>(secondary_arg) - 2048;
2071
2072 // use internal reference, shouldn't break anything so far, unless it's already a ref
2073 if (var->valueType() == ValueType::Reference)
2074 var = var->reference();
2075
2076 if (var->valueType() == ValueType::Number)
2077 {
2078 auto val = Value(var->number() * other);
2079 setVal(primary_arg, &val, context);
2080 }
2081 else
2083 "*",
2085 { *var, Value(other) });
2086 }
2087 DISPATCH();
2088 }
2089
2091 {
2092 const auto op1 = static_cast<Instruction>(padding),
2093 op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
2094 op3 = static_cast<Instruction>(arg & 0x00ff);
2095 const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
2096
2097 const Value* d = popAndResolveAsPtr(context);
2098 const Value* c = popAndResolveAsPtr(context);
2099 const Value* b = popAndResolveAsPtr(context);
2100
2105 { *c, *d });
2106
2107 double temp = helper::doMath(c->number(), d->number(), op1);
2108 if (b->valueType() != ValueType::Number)
2112 { *b, Value(temp) });
2113 temp = helper::doMath(b->number(), temp, op2);
2114
2115 if (arg_count == 2)
2116 push(Value(temp), context);
2117 else if (arg_count == 3)
2118 {
2119 const Value* a = popAndResolveAsPtr(context);
2120 if (a->valueType() != ValueType::Number)
2124 { *a, Value(temp) });
2125
2126 temp = helper::doMath(a->number(), temp, op3);
2127 push(Value(temp), context);
2128 }
2129 else
2130 throw Error(
2131 fmt::format(
2132 "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
2133 arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
2134 DISPATCH();
2135 }
2136#pragma endregion
2137 }
2138#if ARK_USE_COMPUTED_GOTOS
2139 dispatch_end:
2140 do
2141 {
2142 } while (false);
2143#endif
2144 }
2145 }
2146 catch (const Error& e)
2147 {
2148 if (fail_with_exception)
2149 {
2150 std::stringstream stream;
2151 backtrace(context, stream, /* colorize= */ false);
2152 // It's important we have an Ark::Error here, as the constructor for NestedError
2153 // does more than just aggregate error messages, hence the code duplication.
2154 throw NestedError(e, stream.str(), *this);
2155 }
2156 else
2157 showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context);
2158 }
2159 catch (const std::exception& e)
2160 {
2161 if (fail_with_exception)
2162 {
2163 std::stringstream stream;
2164 backtrace(context, stream, /* colorize= */ false);
2165 throw NestedError(e, stream.str());
2166 }
2167 else
2168 showBacktraceWithException(e, context);
2169 }
2170 catch (...)
2171 {
2172 if (fail_with_exception)
2173 throw;
2174
2175#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2176 throw;
2177#endif
2178 fmt::println("Unknown error");
2179 backtrace(context);
2180 m_exit_code = 1;
2181 }
2182
2183 return m_exit_code;
2184 }
2185
2186 uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2187 {
2188 for (auto& local : std::ranges::reverse_view(context.locals))
2189 {
2190 if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2191 return id;
2192 }
2193 return MaxValue16Bits;
2194 }
2195
2196 void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
2197 {
2198 std::vector<std::string> arg_names;
2199 arg_names.reserve(expected_arg_count + 1);
2200 if (expected_arg_count > 0)
2201 arg_names.emplace_back(""); // for formatting, so that we have a space between the function and the args
2202
2203 std::size_t index = 0;
2204 while (m_state.inst(context.pp, index) == STORE ||
2205 m_state.inst(context.pp, index) == STORE_REF)
2206 {
2207 const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
2208 arg_names.push_back(m_state.m_symbols[id]);
2209 index += 4;
2210 }
2211 // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2212 if (arg_names.size() == 1 && index == 0)
2213 {
2214 assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2215 for (std::size_t i = 0; i < expected_arg_count; ++i)
2216 arg_names.push_back(std::string(1, static_cast<char>('a' + i)));
2217 }
2218
2219 std::vector<std::string> arg_vals;
2220 arg_vals.reserve(passed_arg_count + 1);
2221 if (passed_arg_count > 0)
2222 arg_vals.emplace_back(""); // for formatting, so that we have a space between the function and the args
2223
2224 for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.sp; ++i)
2225 // -1 on the stack because we always point to the next available slot
2226 arg_vals.push_back(context.stack[context.sp - i - 1].toString(*this));
2227
2228 // set ip/pp to the callee location so that the error can pinpoint the line
2229 // where the bad call happened
2230 if (context.sp >= 2 + passed_arg_count)
2231 {
2232 context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
2233 context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
2234 returnFromFuncCall(context);
2235 }
2236
2237 std::string function_name = (context.last_symbol < m_state.m_symbols.size())
2238 ? m_state.m_symbols[context.last_symbol]
2239 : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
2240
2242 ErrorKind::Arity,
2243 fmt::format(
2244 "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
2245 function_name,
2246 fmt::join(arg_vals, " "),
2247 passed_arg_count,
2248 passed_arg_count > 1 ? "s" : "",
2249 expected_arg_count,
2250 function_name,
2251 fmt::join(arg_names, " ")));
2252 }
2253
2254 void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
2255 {
2256 std::string text = e.what();
2257 if (!text.empty() && text.back() != '\n')
2258 text += '\n';
2259 fmt::println("{}", text);
2260 backtrace(context);
2261#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2262 // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2263 m_exit_code = 0;
2264#else
2265 m_exit_code = 1;
2266#endif
2267 }
2268
2269 std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2270 {
2271 std::optional<InstLoc> match = std::nullopt;
2272
2273 for (const auto location : m_state.m_inst_locations)
2274 {
2275 if (location.page_pointer == pp && !match)
2276 match = location;
2277
2278 // select the best match: we want to find the location that's nearest our instruction pointer,
2279 // but not equal to it as the IP will always be pointing to the next instruction,
2280 // not yet executed. Thus, the erroneous instruction is the previous one.
2281 if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
2282 match = location;
2283
2284 // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2285 if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
2286 break;
2287 }
2288
2289 return match;
2290 }
2291
2292 std::string VM::debugShowSource() const
2293 {
2294 const auto& context = m_execution_contexts.front();
2295 auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
2296 if (maybe_source_loc)
2297 {
2298 const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
2299 return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
2300 }
2301 return "No source location found";
2302 }
2303
2304 void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
2305 {
2306 const std::size_t saved_ip = context.ip;
2307 const std::size_t saved_pp = context.pp;
2308 const uint16_t saved_sp = context.sp;
2309 constexpr std::size_t max_consecutive_traces = 7;
2310
2311 const auto maybe_location = findSourceLocation(context.ip, context.pp);
2312 if (maybe_location)
2313 {
2314 const auto filename = m_state.m_filenames[maybe_location->filename_id];
2315
2316 if (Utils::fileExists(filename))
2319 .filename = filename,
2320 .start = FilePos { .line = maybe_location->line, .column = 0 },
2321 .end = std::nullopt },
2322 os,
2323 /* maybe_context= */ std::nullopt,
2324 /* colorize= */ colorize);
2325 fmt::println(os, "");
2326 }
2327
2328 if (context.fc > 1)
2329 {
2330 // display call stack trace
2331 const ScopeView old_scope = context.locals.back();
2332
2333 std::string previous_trace;
2334 std::size_t displayed_traces = 0;
2335 std::size_t consecutive_similar_traces = 0;
2336
2337 while (context.fc != 0 && context.pp != 0)
2338 {
2339 const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2340 const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
2341
2342 const uint16_t id = findNearestVariableIdWithValue(
2343 Value(static_cast<PageAddr_t>(context.pp)),
2344 context);
2345 const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2346
2347 if (func_name + loc_as_text != previous_trace)
2348 {
2349 fmt::println(
2350 os,
2351 "[{:4}] In function `{}'{}",
2352 fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2353 fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
2354 loc_as_text);
2355 previous_trace = func_name + loc_as_text;
2356 ++displayed_traces;
2357 consecutive_similar_traces = 0;
2358 }
2359 else if (consecutive_similar_traces == 0)
2360 {
2361 fmt::println(os, " ...");
2362 ++consecutive_similar_traces;
2363 }
2364
2365 const Value* ip;
2366 do
2367 {
2368 ip = popAndResolveAsPtr(context);
2369 } while (ip->valueType() != ValueType::InstPtr);
2370
2371 context.ip = ip->pageAddr();
2372 context.pp = pop(context)->pageAddr();
2373 returnFromFuncCall(context);
2374
2375 if (displayed_traces > max_consecutive_traces)
2376 {
2377 fmt::println(os, " ...");
2378 break;
2379 }
2380 }
2381
2382 if (context.pp == 0)
2383 {
2384 const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2385 const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
2386 fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
2387 }
2388
2389 // display variables values in the current scope
2390 fmt::println(os, "\nCurrent scope variables values:");
2391 for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
2392 {
2393 fmt::println(
2394 os,
2395 "{} = {}",
2396 fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2397 old_scope.atPos(i).second.toString(*this));
2398 }
2399 }
2400
2401 fmt::println(
2402 os,
2403 "At IP: {}, PP: {}, SP: {}",
2404 // dividing by 4 because the instructions are actually on 4 bytes
2405 fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2406 fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
2407 fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
2408 }
2409}
Lots of utilities about string, filesystem and more.
#define ARK_NO_NAME_FILE
Definition Constants.hpp:28
Tools to report code errors nicely to the user.
Lots of utilities about the filesystem.
The different instructions used by the compiler and virtual machine.
#define GOTO_HALT()
#define TARGET(op)
#define UNPACK_ARGS()
#define DISPATCH()
The ArkScript virtual machine.
An assertion error, only triggered from ArkScript code through (assert expr error-message)
virtual std::string details(bool colorize, VM &vm) const
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition State.hpp:33
std::vector< std::filesystem::path > m_libenv
Definition State.hpp:148
std::string m_filename
Definition State.hpp:149
std::vector< Value > m_constants
Definition State.hpp:153
std::vector< internal::InstLoc > m_inst_locations
Definition State.hpp:155
std::vector< std::string > m_filenames
Definition State.hpp:154
std::unordered_map< std::string, Value > m_binded
Values binded to the State, to be used by the VM.
Definition State.hpp:160
constexpr uint8_t inst(const std::size_t pp, const std::size_t ip) const noexcept
Get an instruction in a given page, with a given instruction pointer.
Definition State.hpp:169
std::vector< std::string > m_symbols
Definition State.hpp:152
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:47
void deleteContext(internal::ExecutionContext *ec)
Free a given execution context.
Definition VM.cpp:427
void showBacktraceWithException(const std::exception &e, internal::ExecutionContext &context)
Definition VM.cpp:2254
std::vector< std::unique_ptr< internal::Future > > m_futures
Storing the promises while we are resolving them.
Definition VM.hpp:177
void jump(uint16_t address, internal::ExecutionContext &context)
Definition VM.hpp:159
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Definition VM.hpp:173
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Definition VM.cpp:260
Value * popAndResolveAsPtr(internal::ExecutionContext &context)
Pop a value from the stack and resolve it if possible, then return it.
Definition VM.hpp:210
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
Definition VM.hpp:176
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition VM.hpp:172
Value * peekAndResolveAsPtr(internal::ExecutionContext &context)
Return a pointer to the top of the stack without consuming it, and resolve it if possible.
Definition VM.hpp:179
std::string debugShowSource() const
Definition VM.cpp:2292
Value * loadSymbol(uint16_t id, internal::ExecutionContext &context)
Load a symbol by its id in the current context. Performs a lookup in the scope stack,...
Definition VM.hpp:99
void listAppendInPlace(Value *list, std::size_t count, internal::ExecutionContext &context)
Definition VM.cpp:243
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Definition VM.cpp:2186
Value * loadSymbolFromIndex(uint16_t index, internal::ExecutionContext &context)
Load a symbol by its (reversed) index in the current scope.
Definition VM.hpp:114
void callBuiltin(internal::ExecutionContext &context, const Value &builtin, uint16_t argc, bool remove_return_address=true)
Builtin called when the CALL_BUILTIN instruction is met in the bytecode.
Definition VM.hpp:349
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
Definition VM.cpp:481
void backtrace(internal::ExecutionContext &context, std::ostream &os=std::cout, bool colorize=true)
Display a backtrace when the VM encounter an exception.
Definition VM.cpp:2304
internal::ExecutionContext * createAndGetContext()
Create an execution context and returns it.
Definition VM.cpp:371
std::mutex m_mutex
Definition VM.hpp:175
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
Definition VM.cpp:285
int safeRun(internal::ExecutionContext &context, std::size_t untilFrameCount=0, bool fail_with_exception=false)
Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if...
Definition VM.cpp:524
void deleteFuture(internal::Future *f)
Free a given future.
Definition VM.cpp:470
bool m_running
Definition VM.hpp:174
std::mutex m_mutex_futures
Definition VM.hpp:175
Value call(const std::string &name, Args &&... args)
Call a function from ArkScript, by giving it arguments.
Definition VM.hpp:6
Value * pop(internal::ExecutionContext &context)
Pop a value from the stack.
Definition VM.hpp:169
void returnFromFuncCall(internal::ExecutionContext &context)
Destroy the current frame and get back to the previous one, resuming execution.
Definition VM.hpp:230
Value getField(Value *closure, uint16_t id, const internal::ExecutionContext &context)
Definition VM.cpp:184
void store(uint16_t id, const Value *val, internal::ExecutionContext &context)
Create a new symbol with an associated value in the current scope.
Definition VM.hpp:131
void setVal(uint16_t id, const Value *val, internal::ExecutionContext &context)
Change the value of a symbol given its identifier.
Definition VM.hpp:141
friend class internal::Closure
Definition VM.hpp:167
State & m_state
Definition VM.hpp:171
std::optional< internal::InstLoc > findSourceLocation(std::size_t ip, std::size_t pp) const
Find the nearest source location information given instruction and page pointers.
Definition VM.cpp:2269
void push(const Value &value, internal::ExecutionContext &context) noexcept
Push a value on the stack.
Definition VM.hpp:191
Value * loadConstAsPtr(uint16_t id) const
Load a constant from the constant table by its id.
Definition VM.hpp:126
Value createList(std::size_t count, internal::ExecutionContext &context)
Definition VM.cpp:231
void init() noexcept
Initialize the VM according to the parameters.
Definition VM.cpp:150
Value * findNearestVariable(uint16_t id, internal::ExecutionContext &context) noexcept
Find the nearest variable of a given id.
Definition VM.hpp:220
void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext &context)
Definition VM.cpp:2196
static void throwVMError(internal::ErrorKind kind, const std::string &message)
Throw a VM error message.
Definition VM.cpp:512
friend class Value
Definition VM.hpp:166
VM(State &state) noexcept
Construct a new vm t object.
Definition VM.cpp:144
internal::Future * createFuture(std::vector< Value > &args)
Create a Future object from a function and its arguments and return a managed pointer to it.
Definition VM.cpp:457
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:517
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
Definition VM.cpp:365
const String_t & string() const
Definition Value.hpp:164
const List_t & constList() const
Definition Value.hpp:168
internal::Closure & refClosure()
Definition Value.hpp:213
String_t & stringRef()
Definition Value.hpp:165
List_t & list()
Definition Value.hpp:169
Ref_t reference() const
Definition Value.hpp:177
void push_back(const Value &value)
Add an element to the list held by the value (if the value type is set to list)
Definition Value.cpp:67
ValueType valueType() const noexcept
Definition Value.hpp:151
Number_t number() const
Definition Value.hpp:162
std::string toString(VM &vm, bool show_as_code=false) const noexcept
Definition Value.cpp:77
bool isIndexable() const noexcept
Definition Value.hpp:157
internal::PageAddr_t pageAddr() const
Definition Value.hpp:179
A class to store fields captured by a closure.
std::string toString(VM &vm) const noexcept
Print the closure to a string.
Definition Closure.cpp:27
ClosureScope & refScope() const noexcept
Definition Closure.hpp:53
bool hasFieldEndingWith(const std::string &end, const VM &vm) const
Used when generating error messages in the VM, to see if a symbol might have been wrongly fully quali...
Definition Closure.cpp:20
const std::shared_ptr< ClosureScope > & scopePtr() const
Definition Closure.hpp:54
A class to handle the VM scope more efficiently.
Definition ScopeView.hpp:27
std::size_t size() const noexcept
Return the size of the scope.
const pair_t & atPos(const std::size_t i) const noexcept
Return the element at index in scope.
ARK_API void makeContext(const ErrorLocation &loc, std::ostream &os, const std::optional< CodeErrorContext > &maybe_context, bool colorize)
Helper to create a colorized context to report errors to the user.
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
Definition Files.hpp:28
bool isDouble(const std::string &s, double *output=nullptr)
Checks if a string is a valid double.
Definition Utils.hpp:85
double doMath(double a, double b, const Instruction op)
Definition VM.cpp:112
Value head(Value *a)
Definition VM.cpp:51
Value at(Value &container, Value &index, VM &vm)
Definition VM.cpp:73
std::string mathInstToStr(const Instruction op)
Definition VM.cpp:130
Value tail(Value *a)
Definition VM.cpp:22
ARK_API const std::vector< std::pair< std::string, Value > > builtins
constexpr std::array< std::string_view, 7 > errorKinds
Definition ErrorKind.hpp:20
uint16_t PageAddr_t
Definition Closure.hpp:26
Instruction
The different bytecodes are stored here.
@ CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
constexpr uint16_t MaxValue16Bits
Definition Constants.hpp:71
@ Any
Used only for typechecking.
std::string to_string(const Ark::ValueType type) noexcept
Definition Value.hpp:258
std::array< ScopeView::pair_t, ScopeStackSize > scopes_storage
All the ScopeView use this array to store id->value.
std::vector< std::shared_ptr< ClosureScope > > stacked_closure_scopes
Stack the closure scopes to keep the closure alive as long as we are calling them.
std::optional< uint16_t > capture_rename_id
std::vector< ScopeView > locals
std::array< Value, VMStackSizeWithOverflowBuffer > stack
void setActive(const bool toggle)
const bool primary
Tells if the current ExecutionContext is the primary one or not.
std::size_t ip
Instruction pointer.
std::optional< ClosureScope > saved_scope
Scope created by CAPTURE <x> instructions, used by the MAKE_CLOSURE instruction.
const char * name
A contract is a list of typed arguments that a function can follow.
A type definition within a contract.