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