ArkScript
A small, fast, functional and scripting language for video games
VM.cpp
Go to the documentation of this file.
1#include <Ark/VM/VM.hpp>
2
3#include <numeric>
4#include <limits>
5
6#include <fmt/core.h>
7#include <ranges>
8#include <termcolor/proxy.hpp>
9#include <Ark/Files.hpp>
10#include <Ark/Utils.hpp>
11#include <Ark/TypeChecker.hpp>
13
14#ifdef ARK_PROFILER_MIPS
15# include <chrono>
16#endif
17
18struct mapping
19{
20 char* name;
21 Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
22};
23
24namespace Ark
25{
26 using namespace internal;
27
28 VM::VM(State& state) noexcept :
29 m_state(state), m_exit_code(0), m_running(false)
30 {
31 m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
32 }
33
34 void VM::init() noexcept
35 {
36 ExecutionContext& context = *m_execution_contexts.back();
37 for (const auto& c : m_execution_contexts)
38 {
39 c->ip = 0;
40 c->pp = 0;
41 c->sp = 0;
42 }
43
44 context.sp = 0;
45 context.fc = 1;
46
48 context.stacked_closure_scopes.clear();
49 context.stacked_closure_scopes.emplace_back(nullptr);
50
51 context.saved_scope.reset();
52 m_exit_code = 0;
53
54 context.locals.clear();
55 context.locals.emplace_back();
56
57 // loading bound stuff
58 // put them in the global frame if we can, aka the first one
59 for (const auto& [sym_id, value] : m_state.m_binded)
60 {
61 auto it = std::ranges::find(m_state.m_symbols, sym_id);
62 if (it != m_state.m_symbols.end())
63 context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
64 }
65 }
66
67 Value& VM::operator[](const std::string& name) noexcept
68 {
69 ExecutionContext& context = *m_execution_contexts.front();
70
71 // find id of object
72 const auto it = std::ranges::find(m_state.m_symbols, name);
73 if (it == m_state.m_symbols.end())
74 {
75 m_no_value = Builtins::nil;
76 return m_no_value;
77 }
78
79 const auto id = static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it));
80 Value* var = findNearestVariable(id, context);
81 if (var != nullptr)
82 return *var;
83 m_no_value = Builtins::nil;
84 return m_no_value;
85 }
86
87 void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
88 {
89 namespace fs = std::filesystem;
90
91 const std::string file = m_state.m_constants[id].stringRef();
92
93 std::string path = file;
94 // bytecode loaded from file
96 path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
97
98 std::shared_ptr<SharedLibrary> lib;
99 // if it exists alongside the .arkc file
100 if (Utils::fileExists(path))
101 lib = std::make_shared<SharedLibrary>(path);
102 else
103 {
104 for (auto const& v : m_state.m_libenv)
105 {
106 std::string lib_path = (fs::path(v) / fs::path(file)).string();
107
108 // if it's already loaded don't do anything
109 if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
110 return (val->path() == path || val->path() == lib_path);
111 }) != m_shared_lib_objects.end())
112 return;
113
114 // check in lib_path
115 if (Utils::fileExists(lib_path))
116 {
117 lib = std::make_shared<SharedLibrary>(lib_path);
118 break;
119 }
120 }
121 }
122
123 if (!lib)
124 {
125 auto lib_path = std::accumulate(
126 std::next(m_state.m_libenv.begin()),
127 m_state.m_libenv.end(),
128 m_state.m_libenv[0].string(),
129 [](const std::string& a, const fs::path& b) -> std::string {
130 return a + "\n\t- " + b.string();
131 });
133 ErrorKind::Module,
134 fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
135 }
136
137 m_shared_lib_objects.emplace_back(lib);
138
139 // load the mapping from the dynamic library
140 try
141 {
142 const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
143 // load the mapping data
144 std::size_t i = 0;
145 while (map[i].name != nullptr)
146 {
147 // put it in the global frame, aka the first one
148 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
149 if (it != m_state.m_symbols.end())
150 (context.locals[0]).push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
151
152 ++i;
153 }
154 }
155 catch (const std::system_error& e)
156 {
158 ErrorKind::Module,
159 fmt::format(
160 "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
161 file, e.what()));
162 }
163 }
164
165 void VM::exit(const int code) noexcept
166 {
167 m_exit_code = code;
168 m_running = false;
169 }
170
172 {
173 const std::lock_guard lock(m_mutex);
174
175 m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
176 ExecutionContext* ctx = m_execution_contexts.back().get();
177 ctx->stacked_closure_scopes.emplace_back(nullptr);
178
179 ctx->locals.reserve(m_execution_contexts.front()->locals.size());
180 for (const auto& local : m_execution_contexts.front()->locals)
181 ctx->locals.push_back(local);
182
183 return ctx;
184 }
185
187 {
188 const std::lock_guard lock(m_mutex);
189
190 const auto it = std::ranges::remove_if(m_execution_contexts,
191 [ec](const std::unique_ptr<ExecutionContext>& ctx) {
192 return ctx.get() == ec;
193 })
194 .begin();
195 m_execution_contexts.erase(it);
196 }
197
198 Future* VM::createFuture(std::vector<Value>& args)
199 {
201
202 // doing this after having created the context
203 // because the context uses the mutex and we don't want a deadlock
204 const std::lock_guard lock(m_mutex);
205 m_futures.push_back(std::make_unique<Future>(ctx, this, args));
206
207 return m_futures.back().get();
208 }
209
211 {
212 const std::lock_guard lock(m_mutex);
213
214 const auto it = std::ranges::remove_if(m_futures,
215 [f](const std::unique_ptr<Future>& future) {
216 return future.get() == f;
217 })
218 .begin();
219 m_futures.erase(it);
220 }
221
223 {
224 // load the mapping from the dynamic library
225 try
226 {
227 for (auto& shared_lib : m_shared_lib_objects)
228 {
229 const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
230 // load the mapping data
231 std::size_t i = 0;
232 while (map[i].name != nullptr)
233 {
234 // put it in the global frame, aka the first one
235 auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
236 if (it != m_state.m_symbols.end())
237 m_execution_contexts[0]->locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
238
239 ++i;
240 }
241 }
242
243 return true;
244 }
245 catch (const std::system_error& e)
246 {
247 return false;
248 }
249 }
250
251 int VM::run() noexcept
252 {
253 init();
255 return m_exit_code;
256 }
257
258 int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount)
259 {
260#ifdef ARK_PROFILER_MIPS
261 auto start_time = std::chrono::system_clock::now();
262 unsigned long long instructions_executed = 0;
263#endif
264
265 try
266 {
267 m_running = true;
268 while (m_running && context.fc > untilFrameCount)
269 {
270 // get current instruction
271 [[maybe_unused]] uint8_t padding = m_state.m_pages[context.pp][context.ip];
272 uint8_t inst = m_state.m_pages[context.pp][context.ip + 1];
273 uint16_t arg = (static_cast<uint16_t>(m_state.m_pages[context.pp][context.ip + 2]) << 8) + static_cast<uint16_t>(m_state.m_pages[context.pp][context.ip + 3]);
274 context.ip += 4;
275
276 switch (inst)
277 {
278#pragma region "Instructions"
279
280 case LOAD_SYMBOL:
281 {
282 context.last_symbol = arg;
283
284 if (Value* var = findNearestVariable(context.last_symbol, context); var != nullptr) [[likely]]
285 {
286 // push internal reference, shouldn't break anything so far, unless it's already a ref
287 if (var->valueType() == ValueType::Reference)
288 push(var->reference(), context);
289 else
290 push(var, context);
291 }
292 else [[unlikely]]
293 throwVMError(ErrorKind::Scope, fmt::format("Unbound variable `{}'", m_state.m_symbols[context.last_symbol]));
294
295 break;
296 }
297
298 case LOAD_CONST:
299 {
300 if (context.saved_scope && m_state.m_constants[arg].valueType() == ValueType::PageAddr)
301 {
302 push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
303 context.saved_scope.reset();
304 }
305 else [[likely]] // push internal ref
306 push(&(m_state.m_constants[arg]), context);
307
308 break;
309 }
310
311 case POP_JUMP_IF_TRUE:
312 {
313 if (*popAndResolveAsPtr(context) == Builtins::trueSym)
314 context.ip = static_cast<int16_t>(arg) * 4; // instructions are 4 bytes
315 break;
316 }
317
318 case STORE:
319 {
320 Value val = *popAndResolveAsPtr(context);
321 if (Value* var = findNearestVariable(arg, context); var != nullptr) [[likely]]
322 {
323 if (var->isConst() && var->valueType() != ValueType::Reference)
324 throwVMError(ErrorKind::Mutability, fmt::format("Can not set the constant `{}' to {}", m_state.m_symbols[arg], val.toString(*this)));
325
326 if (var->valueType() == ValueType::Reference)
327 *var->reference() = val;
328 else [[likely]]
329 {
330 *var = val;
331 var->setConst(false);
332 }
333 break;
334 }
335
336 throwVMError(ErrorKind::Scope, fmt::format("Unbound variable `{}', can not change its value to {}", m_state.m_symbols[arg], val.toString(*this)));
337 break;
338 }
339
340 case LET:
341 {
342 // check if we are redefining a variable
343 if (auto val = (context.locals.back())[arg]; val != nullptr) [[unlikely]]
344 throwVMError(ErrorKind::Mutability, fmt::format("Can not use 'let' to redefine variable `{}'", m_state.m_symbols[arg]));
345
346 Value val = *popAndResolveAsPtr(context);
347 val.setConst(true);
348 context.locals.back().push_back(arg, val);
349
350 break;
351 }
352
354 {
355 if (*popAndResolveAsPtr(context) == Builtins::falseSym)
356 context.ip = static_cast<int16_t>(arg) * 4; // instructions are 4 bytes
357 break;
358 }
359
360 case JUMP:
361 {
362 context.ip = static_cast<int16_t>(arg) * 4; // instructions are 4 bytes
363 break;
364 }
365
366 case RET:
367 {
368 Value ip_or_val = *popAndResolveAsPtr(context);
369 // no return value on the stack
370 if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
371 {
372 context.ip = ip_or_val.pageAddr();
373 // we always push PP then IP, thus the next value
374 // MUST be the page pointer
375 context.pp = pop(context)->pageAddr();
376
377 returnFromFuncCall(context);
378 push(Builtins::nil, context);
379 }
380 // value on the stack
381 else [[likely]]
382 {
383 Value* ip;
384 do
385 {
386 ip = popAndResolveAsPtr(context);
387 } while (ip->valueType() != ValueType::InstPtr);
388
389 context.ip = ip->pageAddr();
390 context.pp = pop(context)->pageAddr();
391
392 returnFromFuncCall(context);
393 push(std::move(ip_or_val), context);
394 }
395
396 break;
397 }
398
399 case HALT:
400 m_running = false;
401 break;
402
403 case CALL:
404 {
405 // stack pointer + 2 because we push IP and PP
406 if (context.sp + 2 >= VMStackSize) [[unlikely]]
408 ErrorKind::VM,
409 fmt::format(
410 "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
411 m_state.m_symbols[context.last_symbol]));
412 call(context, arg);
413 break;
414 }
415
416 case CAPTURE:
417 {
418 if (!context.saved_scope)
419 context.saved_scope = Scope();
420
421 Value* ptr = (context.locals.back())[arg];
422 if (!ptr)
423 throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
424 ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
425 context.saved_scope.value().push_back(arg, *ptr);
426
427 break;
428 }
429
430 case BUILTIN:
431 {
432 push(Builtins::builtins[arg].second, context);
433 break;
434 }
435
436 case MUT:
437 {
438 Value val = *popAndResolveAsPtr(context);
439 val.setConst(false);
440
441 // avoid adding the pair (id, _) multiple times, with different values
442 Value* local = context.locals.back()[arg];
443 if (local == nullptr) [[likely]]
444 context.locals.back().push_back(arg, val);
445 else
446 *local = val;
447
448 break;
449 }
450
451 case DEL:
452 {
453 if (Value* var = findNearestVariable(arg, context); var != nullptr)
454 {
455 if (var->valueType() == ValueType::User)
456 var->usertypeRef().del();
457 *var = Value();
458 break;
459 }
460
461 throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
462 break;
463 }
464
465 case SAVE_ENV:
466 {
467 context.saved_scope = context.locals.back();
468 break;
469 }
470
471 case GET_FIELD:
472 {
473 Value* var = popAndResolveAsPtr(context);
474 if (var->valueType() != ValueType::Closure)
476 ErrorKind::Type,
477 fmt::format("`{}' is a {}, not a closure, can not get the field `{}' from it",
478 m_state.m_symbols[context.last_symbol], types_to_str[static_cast<std::size_t>(var->valueType())], m_state.m_symbols[arg]));
479
480 if (Value* field = (var->refClosure().refScope())[arg]; field != nullptr)
481 {
482 // check for CALL instruction
483 // doing a +1 on the IP to read the instruction because context.ip is already on the next instruction word (the padding)
484 if (static_cast<std::size_t>(context.ip) + 1 < m_state.m_pages[context.pp].size() && m_state.m_pages[context.pp][context.ip + 1] == CALL)
485 push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
486 else
487 push(field, context);
488 break;
489 }
490
491 throwVMError(ErrorKind::Scope, fmt::format("`{}' isn't in the closure environment: {}", m_state.m_symbols[arg], var->refClosure().toString(*this)));
492 break;
493 }
494
495 case PLUGIN:
496 {
497 loadPlugin(arg, context);
498 break;
499 }
500
501 case LIST:
502 {
504 if (arg != 0)
505 l.list().reserve(arg);
506
507 for (uint16_t i = 0; i < arg; ++i)
508 l.push_back(*popAndResolveAsPtr(context));
509 push(std::move(l), context);
510 break;
511 }
512
513 case APPEND:
514 {
515 Value* list = popAndResolveAsPtr(context);
516 if (list->valueType() != ValueType::List)
518 "append",
519 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
520 { *list });
521
522 const uint16_t size = list->constList().size();
523
524 Value obj { *list };
525 obj.list().reserve(size + arg);
526
527 for (uint16_t i = 0; i < arg; ++i)
528 obj.push_back(*popAndResolveAsPtr(context));
529 push(std::move(obj), context);
530 break;
531 }
532
533 case CONCAT:
534 {
535 Value* list = popAndResolveAsPtr(context);
536 if (list->valueType() != ValueType::List)
538 "concat",
539 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
540 { *list });
541
542 Value obj { *list };
543
544 for (uint16_t i = 0; i < arg; ++i)
545 {
546 Value* next = popAndResolveAsPtr(context);
547
548 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
550 "concat",
552 { *list, *next });
553
554 for (auto& val : next->list())
555 obj.push_back(val);
556 }
557 push(std::move(obj), context);
558 break;
559 }
560
561 case APPEND_IN_PLACE:
562 {
563 Value* list = popAndResolveAsPtr(context);
564
565 if (list->isConst())
566 throwVMError(ErrorKind::Mutability, "Can not modify a constant list using `append!'");
567 if (list->valueType() != ValueType::List)
569 "append!",
570 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
571 { *list });
572
573 for (uint16_t i = 0; i < arg; ++i)
574 list->push_back(*popAndResolveAsPtr(context));
575
576 break;
577 }
578
579 case CONCAT_IN_PLACE:
580 {
581 Value* list = popAndResolveAsPtr(context);
582
583 if (list->isConst())
584 throwVMError(ErrorKind::Mutability, "Can not modify a constant list using `concat!'");
585 if (list->valueType() != ValueType::List)
587 "concat",
588 { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
589 { *list });
590
591 for (uint16_t i = 0; i < arg; ++i)
592 {
593 Value* next = popAndResolveAsPtr(context);
594
595 if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
597 "concat!",
599 { *list, *next });
600
601 for (auto& it : next->list())
602 list->push_back(it);
603 }
604
605 break;
606 }
607
608 case POP_LIST:
609 {
610 Value list = *popAndResolveAsPtr(context);
611 Value number = *popAndResolveAsPtr(context);
612
613 if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
615 "pop",
617 { list, number });
618
619 long idx = static_cast<long>(number.number());
620 idx = (idx < 0 ? list.list().size() + idx : idx);
621 if (static_cast<std::size_t>(idx) >= list.list().size())
623 ErrorKind::Index,
624 fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
625
626 list.list().erase(list.list().begin() + idx);
627 push(list, context);
628 break;
629 }
630
632 {
633 Value* list = popAndResolveAsPtr(context);
634 Value number = *popAndResolveAsPtr(context);
635
636 if (list->isConst())
637 throwVMError(ErrorKind::Mutability, "Can not modify a constant list using `pop!'");
638 if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
640 "pop!",
642 { *list, number });
643
644 long idx = static_cast<long>(number.number());
645 idx = (idx < 0 ? list->list().size() + idx : idx);
646 if (static_cast<std::size_t>(idx) >= list->list().size())
648 ErrorKind::Index,
649 fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
650
651 list->list().erase(list->list().begin() + idx);
652 break;
653 }
654
655 case POP:
656 {
657 pop(context);
658 break;
659 }
660
661#pragma endregion
662
663#pragma region "Operators"
664
665 case ADD:
666 {
667 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
668
669 if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
670 push(Value(a->number() + b->number()), context);
671 else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
672 push(Value(a->string() + b->string()), context);
673 else
675 "+",
678 { *a, *b });
679 break;
680 }
681
682 case SUB:
683 {
684 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
685
686 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
688 "-",
690 { *a, *b });
691 push(Value(a->number() - b->number()), context);
692 break;
693 }
694
695 case MUL:
696 {
697 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
698
699 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
701 "*",
703 { *a, *b });
704 push(Value(a->number() * b->number()), context);
705 break;
706 }
707
708 case DIV:
709 {
710 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
711
712 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
714 "/",
716 { *a, *b });
717 auto d = b->number();
718 if (d == 0)
719 throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
720
721 push(Value(a->number() / d), context);
722 break;
723 }
724
725 case GT:
726 {
727 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
728
729 push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
730 break;
731 }
732
733 case LT:
734 {
735 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
736
737 push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
738 break;
739 }
740
741 case LE:
742 {
743 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
744
745 push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
746 break;
747 }
748
749 case GE:
750 {
751 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
752
753 push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
754 break;
755 }
756
757 case NEQ:
758 {
759 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
760
761 push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
762 break;
763 }
764
765 case EQ:
766 {
767 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
768
769 push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
770 break;
771 }
772
773 case LEN:
774 {
775 Value* a = popAndResolveAsPtr(context);
776
777 if (a->valueType() == ValueType::List)
778 push(Value(static_cast<int>(a->constList().size())), context);
779 else if (a->valueType() == ValueType::String)
780 push(Value(static_cast<int>(a->string().size())), context);
781 else
783 "len",
784 { { types::Contract { { types::Typedef("value", ValueType::List) } },
786 { *a });
787 break;
788 }
789
790 case EMPTY:
791 {
792 Value* a = popAndResolveAsPtr(context);
793
794 if (a->valueType() == ValueType::List)
795 push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
796 else if (a->valueType() == ValueType::String)
797 push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
798 else
800 "empty?",
801 { { types::Contract { { types::Typedef("value", ValueType::List) } },
803 { *a });
804 break;
805 }
806
807 case TAIL:
808 {
809 Value* a = popAndResolveAsPtr(context);
810
811 if (a->valueType() == ValueType::List)
812 {
813 if (a->constList().size() < 2)
814 push(Value(ValueType::List), context);
815 else
816 {
817 std::vector<Value> tmp(a->constList().size() - 1);
818 for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
819 tmp[i - 1] = a->constList()[i];
820 push(Value(std::move(tmp)), context);
821 }
822 }
823 else if (a->valueType() == ValueType::String)
824 {
825 if (a->string().size() < 2)
826 push(Value(ValueType::String), context);
827 else
828 {
829 Value b { *a };
830 b.stringRef().erase(b.stringRef().begin());
831 push(std::move(b), context);
832 }
833 }
834 else
836 "tail",
837 { { types::Contract { { types::Typedef("value", ValueType::List) } },
839 { *a });
840
841 break;
842 }
843
844 case HEAD:
845 {
846 Value* a = popAndResolveAsPtr(context);
847
848 if (a->valueType() == ValueType::List)
849 {
850 if (a->constList().empty())
851 push(Builtins::nil, context);
852 else
853 push(a->constList()[0], context);
854 }
855 else if (a->valueType() == ValueType::String)
856 {
857 if (a->string().empty())
858 push(Value(ValueType::String), context);
859 else
860 push(Value(std::string(1, a->stringRef()[0])), context);
861 }
862 else
864 "head",
865 { { types::Contract { { types::Typedef("value", ValueType::List) } },
867 { *a });
868
869 break;
870 }
871
872 case ISNIL:
873 {
874 Value* a = popAndResolveAsPtr(context);
876 break;
877 }
878
879 case ASSERT:
880 {
881 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
882
883 if (b->valueType() != ValueType::String)
885 "assert",
887 { *a, *b });
888
889 if (*a == Builtins::falseSym)
890 throw AssertionFailed(b->stringRef());
891 break;
892 }
893
894 case TO_NUM:
895 {
896 Value* a = popAndResolveAsPtr(context);
897
898 if (a->valueType() != ValueType::String)
900 "toNumber",
901 { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
902 { *a });
903
904 double val;
905 if (Utils::isDouble(a->string(), &val))
906 push(Value(val), context);
907 else
908 push(Builtins::nil, context);
909 break;
910 }
911
912 case TO_STR:
913 {
914 Value* a = popAndResolveAsPtr(context);
915 push(Value(a->toString(*this)), context);
916 break;
917 }
918
919 case AT:
920 {
921 Value* b = popAndResolveAsPtr(context);
922 Value a = *popAndResolveAsPtr(context); // be careful, it's not a pointer
923
924 if (b->valueType() != ValueType::Number)
926 "@",
929 { a, *b });
930
931 long idx = static_cast<long>(b->number());
932
933 if (a.valueType() == ValueType::List)
934 {
935 if (static_cast<std::size_t>(std::abs(idx)) < a.list().size())
936 push(a.list()[idx < 0 ? a.list().size() + idx : idx], context);
937 else
939 ErrorKind::Index,
940 fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
941 }
942 else if (a.valueType() == ValueType::String)
943 {
944 if (static_cast<std::size_t>(std::abs(idx)) < a.string().size())
945 push(Value(std::string(1, a.string()[idx < 0 ? a.string().size() + idx : idx])), context);
946 else
948 ErrorKind::Index,
949 fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
950 }
951 else
953 "@",
956 { a, *b });
957 break;
958 }
959
960 case AND_:
961 {
962 Value *a = popAndResolveAsPtr(context), *b = popAndResolveAsPtr(context);
963
965 break;
966 }
967
968 case OR_:
969 {
970 Value *a = popAndResolveAsPtr(context), *b = popAndResolveAsPtr(context);
971
973 break;
974 }
975
976 case MOD:
977 {
978 Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
979 if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
981 "mod",
983 { *a, *b });
984
985 push(Value(std::fmod(a->number(), b->number())), context);
986 break;
987 }
988
989 case TYPE:
990 {
991 Value* a = popAndResolveAsPtr(context);
992
993 push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
994 break;
995 }
996
997 case HASFIELD:
998 {
999 Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
1000 if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
1002 "hasField",
1004 { *closure, *field });
1005
1006 auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef());
1007 if (it == m_state.m_symbols.end())
1008 {
1009 push(Builtins::falseSym, context);
1010 break;
1011 }
1012
1013 auto id = static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1014 push((closure->refClosure().refScope())[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1015
1016 break;
1017 }
1018
1019 case NOT:
1020 {
1021 Value* a = popAndResolveAsPtr(context);
1022
1023 push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
1024 break;
1025 }
1026
1027#pragma endregion
1028
1029 default:
1030 throwVMError(ErrorKind::VM, fmt::format("Unknown instruction: {:x}{:x}{:x}", padding, inst, arg));
1031 break;
1032 }
1033
1034#ifdef ARK_PROFILER_MIPS
1035 ++instructions_executed;
1036#endif
1037 }
1038 }
1039 catch (const std::exception& e)
1040 {
1041 std::printf("%s\n", e.what());
1042 backtrace(context);
1043 m_exit_code = 1;
1044 }
1045 catch (...)
1046 {
1047 std::printf("Unknown error\n");
1048 backtrace(context);
1049 m_exit_code = 1;
1050
1051#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1052 throw;
1053#endif
1054 }
1055
1056#ifdef ARK_PROFILER_MIPS
1057 auto end_time = std::chrono::system_clock::now();
1058 auto d = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
1059
1060 std::cout << "\nInstructions executed: " << instructions_executed << "\n"
1061 << "Time spent: " << d.count() << " us\n"
1062 << (static_cast<double>(instructions_executed) / d.count()) << " MIPS\n";
1063#endif
1064
1065 return m_exit_code;
1066 }
1067
1068 uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
1069 {
1070 for (auto& local : std::ranges::reverse_view(context.locals))
1071 {
1072 if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
1073 return id;
1074 }
1075 return std::numeric_limits<uint16_t>::max();
1076 }
1077
1078 void VM::throwVMError(ErrorKind kind, const std::string& message)
1079 {
1080 throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
1081 }
1082
1083 void VM::backtrace(ExecutionContext& context) noexcept
1084 {
1085 const int saved_ip = context.ip;
1086 const std::size_t saved_pp = context.pp;
1087 const uint16_t saved_sp = context.sp;
1088
1089 if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
1090 {
1091 // display call stack trace
1092 const Scope old_scope = context.locals.back();
1093
1094 while (context.fc != 0)
1095 {
1096 std::cerr << "[" << termcolor::cyan << context.fc << termcolor::reset << "] ";
1097 if (context.pp != 0)
1098 {
1099 const uint16_t id = findNearestVariableIdWithValue(
1100 Value(static_cast<PageAddr_t>(context.pp)),
1101 context);
1102
1103 if (id < m_state.m_symbols.size())
1104 std::cerr << "In function `" << termcolor::green << m_state.m_symbols[id] << termcolor::reset << "'\n";
1105 else // should never happen
1106 std::cerr << "In function `" << termcolor::yellow << "???" << termcolor::reset << "'\n";
1107
1108 Value* ip;
1109 do
1110 {
1111 ip = popAndResolveAsPtr(context);
1112 } while (ip->valueType() != ValueType::InstPtr);
1113
1114 context.ip = ip->pageAddr();
1115 context.pp = pop(context)->pageAddr();
1116 returnFromFuncCall(context);
1117 }
1118 else
1119 {
1120 std::printf("In global scope\n");
1121 break;
1122 }
1123
1124 if (original_frame_count - context.fc > 7)
1125 {
1126 std::printf("...\n");
1127 break;
1128 }
1129 }
1130
1131 // display variables values in the current scope
1132 std::printf("\nCurrent scope variables values:\n");
1133 for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
1134 {
1135 std::cerr << termcolor::cyan << m_state.m_symbols[old_scope.m_data[i].first] << termcolor::reset
1136 << " = " << old_scope.m_data[i].second.toString(*this);
1137 std::cerr << "\n";
1138 }
1139
1140 while (context.fc != 1)
1141 {
1142 Value* tmp = pop(context);
1143 if (tmp->valueType() == ValueType::InstPtr)
1144 --context.fc;
1145 *tmp = m_no_value;
1146 }
1147 // pop the PP as well
1148 pop(context);
1149 }
1150
1151 std::cerr << termcolor::reset
1152 << "At IP: " << (saved_ip != -1 ? saved_ip / 4 : 0) // dividing by 4 because the instructions are actually on 4 bytes
1153 << ", PP: " << saved_pp
1154 << ", SP: " << saved_sp
1155 << "\n";
1156 }
1157}
Lots of utilities about string, filesystem and more.
#define ARK_NO_NAME_FILE
Definition: Constants.hpp:27
Lots of utilities about the filesystem.
The different instructions used by the compiler and virtual machine.
The ArkScript virtual machine.
An assertion error, only triggered from ArkScript code through (assert expr error-message)
Definition: Exceptions.hpp:73
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition: State.hpp:31
std::vector< std::filesystem::path > m_libenv
Definition: State.hpp:142
std::string m_filename
Definition: State.hpp:143
std::vector< Value > m_constants
Definition: State.hpp:147
std::unordered_map< std::string, Value > m_binded
Definition: State.hpp:151
std::vector< std::string > m_symbols
Definition: State.hpp:146
std::vector< bytecode_t > m_pages
Definition: State.hpp:148
The ArkScript virtual machine, executing ArkScript bytecode.
Definition: VM.hpp:41
int run() noexcept
Run the bytecode held in the state.
Definition: VM.cpp:251
void deleteContext(internal::ExecutionContext *ec)
Free a given execution context.
Definition: VM.cpp:186
std::vector< std::unique_ptr< internal::Future > > m_futures
Storing the promises while we are resolving them.
Definition: VM.hpp:159
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Definition: VM.hpp:155
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Definition: VM.cpp:67
Value * popAndResolveAsPtr(internal::ExecutionContext &context)
Pop a value from the stack and resolve it if possible, then return it.
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
Definition: VM.hpp:158
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition: VM.hpp:154
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Definition: VM.cpp:1068
bool forceReloadPlugins() const
Used by the REPL to force reload all the plugins and their bound methods.
Definition: VM.cpp:222
internal::ExecutionContext * createAndGetContext()
Create an execution context and returns it.
Definition: VM.cpp:171
std::mutex m_mutex
Definition: VM.hpp:157
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
Definition: VM.cpp:87
void deleteFuture(internal::Future *f)
Free a given future.
Definition: VM.cpp:210
bool m_running
Definition: VM.hpp:156
Value call(const std::string &name, Args &&... args)
Call a function from ArkScript, by giving it arguments.
Value * pop(internal::ExecutionContext &context)
Pop a value from the stack.
void returnFromFuncCall(internal::ExecutionContext &context)
Destroy the current frame and get back to the previous one, resuming execution.
void backtrace(internal::ExecutionContext &context) noexcept
Display a backtrace when the VM encounter an exception.
Definition: VM.cpp:1083
void push(const Value &value, internal::ExecutionContext &context)
Push a value on the stack.
friend class internal::Closure
Definition: VM.hpp:149
int safeRun(internal::ExecutionContext &context, std::size_t untilFrameCount=0)
Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if...
Definition: VM.cpp:258
State & m_state
Definition: VM.hpp:153
void init() noexcept
Initialize the VM according to the parameters.
Definition: VM.cpp:34
Value * findNearestVariable(uint16_t id, internal::ExecutionContext &context) noexcept
Find the nearest variable of a given id.
static void throwVMError(internal::ErrorKind kind, const std::string &message)
Throw a VM error message.
Definition: VM.cpp:1078
friend class Value
Definition: VM.hpp:148
VM(State &state) noexcept
Construct a new vm t object.
Definition: VM.cpp:28
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:198
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
Definition: VM.cpp:165
const std::vector< Value > & constList() const
Definition: Value.hpp:128
std::vector< Value > & list()
Definition: Value.hpp:130
bool isConst() const noexcept
Definition: Value.hpp:168
internal::Closure & refClosure()
Definition: Value.hpp:166
Value * reference() const
Definition: Value.hpp:133
double number() const
Definition: Value.hpp:126
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
void setConst(const bool value) noexcept
Definition: Value.hpp:169
ValueType valueType() const noexcept
Definition: Value.hpp:118
const std::string & string() const
Definition: Value.hpp:127
std::string toString(VM &vm) const noexcept
Definition: Value.cpp:77
internal::PageAddr_t pageAddr() const
Definition: Value.hpp:163
std::string & stringRef()
Definition: Value.hpp:131
const std::shared_ptr< Scope > & scopePtr() const
Definition: Closure.hpp:70
std::string toString(VM &vm) const noexcept
Print the closure to a string.
Definition: Closure.cpp:29
Scope & refScope() const noexcept
Definition: Closure.hpp:69
A class to handle the VM scope more efficiently.
Definition: Scope.hpp:28
std::vector< std::pair< uint16_t, Value > > m_data
Definition: Scope.hpp:105
std::size_t size() const noexcept
Return the size of the scope.
Definition: Scope.cpp:79
bool fileExists(const std::string &name) noexcept
Checks if a file exists.
Definition: Files.hpp:29
bool isDouble(const std::string &s, double *output=nullptr)
Checks if a string is a valid double.
Definition: Utils.hpp:54
const std::vector< std::pair< std::string, Value > > builtins
constexpr std::array< std::string_view, 8 > errorKinds
Definition: ErrorKind.hpp:21
uint16_t PageAddr_t
Definition: Closure.hpp:30
ARK_API void generateError(std::string_view funcname, const std::vector< Contract > &contracts, const std::vector< Value > &args)
Generate an error message based on a given set of types contracts provided argument list.
Definition: TypeChecker.cpp:80
Definition: Builtins.hpp:21
constexpr std::size_t VMStackSize
Definition: Constants.hpp:55
@ Any
Used only for typechecking.
const std::array< std::string, 13 > types_to_str
Definition: Value.hpp:53
std::optional< Scope > saved_scope
std::size_t pp
Page pointer.
std::vector< std::shared_ptr< Scope > > stacked_closure_scopes
const char * name
Definition: Module.hpp:11
A contract is a list of typed arguments that a function can follow.
Definition: TypeChecker.hpp:83
A type definition within a contract.
Definition: TypeChecker.hpp:64
Definition: VM.cpp:19
Ark::Value(* value)(std::vector< Ark::Value > &, Ark::VM *)
Definition: VM.cpp:21
char * name
Definition: VM.cpp:20