ArkScript
A small, fast, functional and scripting language for video games
VM.cpp
Go to the documentation of this file.
1 #define NOMINMAX
2 
3 #include <Ark/VM/VM.hpp>
4 
5 #include <numeric>
6 #include <limits>
7 
8 #include <termcolor/termcolor.hpp>
9 #include <Ark/Files.hpp>
10 #include <Ark/Utils.hpp>
11 #include <Ark/TypeChecker.hpp>
12 
13 #ifdef ARK_PROFILER_MIPS
14 # include <chrono>
15 #endif
16 
17 struct mapping
18 {
19  char* name;
20  Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
21 };
22 
23 namespace Ark
24 {
25  using namespace internal;
26 
27  VM::VM(State& state) noexcept :
28  m_state(state), m_exit_code(0),
29  m_running(false),
30  m_user_pointer(nullptr)
31  {
32  m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
33  }
34 
35  void VM::init() noexcept
36  {
37  ExecutionContext& context = *m_execution_contexts.back();
38 
39  context.sp = 0;
40  context.fc = 1;
41 
42  m_shared_lib_objects.clear();
43  context.scope_count_to_delete.clear();
44  context.scope_count_to_delete.emplace_back(0);
45 
46  context.saved_scope.reset();
47  m_exit_code = 0;
48 
49  context.locals.clear();
50  createNewScope(context);
51 
52  if (context.locals.size() == 0)
53  {
54  // if persistance is set but not scopes are present, add one
55  createNewScope(context);
56  }
57 
58  // loading binded stuff
59  // put them in the global frame if we can, aka the first one
60  for (auto name_val : m_state.m_binded)
61  {
62  auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), name_val.first);
63  if (it != m_state.m_symbols.end())
64  (*context.locals[0]).push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), name_val.second);
65  }
66  }
67 
68  Value& VM::operator[](const std::string& name) noexcept
69  {
70  ExecutionContext& context = *m_execution_contexts.front();
71 
72  // const std::lock_guard<std::mutex> lock(m_mutex);
73 
74  // find id of object
75  auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), name);
76  if (it == m_state.m_symbols.end())
77  {
78  m_no_value = Builtins::nil;
79  return m_no_value;
80  }
81 
82  uint16_t id = static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it));
83  Value* var = findNearestVariable(id, context);
84  if (var != nullptr)
85  return *var;
86  m_no_value = Builtins::nil;
87  return m_no_value;
88  }
89 
90  void VM::loadPlugin(uint16_t id, ExecutionContext& context)
91  {
92  namespace fs = std::filesystem;
93 
94  const std::string file = m_state.m_constants[id].stringRef().toString();
95 
96  std::string path = file;
97  // bytecode loaded from file
98  if (m_state.m_filename != ARK_NO_NAME_FILE)
99  path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
100 
101  std::shared_ptr<SharedLibrary> lib;
102  for (auto const& v : m_state.m_libenv)
103  {
104  std::string lib_path = (fs::path(v) / fs::path(file)).string();
105 
106  // if it's already loaded don't do anything
107  if (std::find_if(m_shared_lib_objects.begin(), m_shared_lib_objects.end(), [&](const auto& val) {
108  return (val->path() == path || val->path() == lib_path);
109  }) != m_shared_lib_objects.end())
110  return;
111 
112  // if it exists alongside the .arkc file
113  if (Utils::fileExists(path))
114  {
115  lib = std::make_shared<SharedLibrary>(path);
116  break;
117  }
118  // check in lib_path otherwise
119  else if (Utils::fileExists(lib_path))
120  {
121  lib = std::make_shared<SharedLibrary>(lib_path);
122  break;
123  }
124  }
125 
126  if (!lib)
127  {
128  auto lib_path = std::accumulate(
129  std::next(m_state.m_libenv.begin()),
130  m_state.m_libenv.end(),
131  m_state.m_libenv[0],
132  [](const std::string& a, const std::string& b) -> std::string {
133  return a + "\n\t- " + b;
134  });
135  throwVMError("Could not find module '" + file + "'. Searched in\n\t- " + path + "\n\t- " + lib_path);
136  }
137 
138  m_shared_lib_objects.emplace_back(lib);
139 
140  // load the mapping from the dynamic library
141  mapping* map = nullptr;
142  try
143  {
144  map = m_shared_lib_objects.back()->template get<mapping* (*)()>("getFunctionsMapping")();
145  }
146  catch (const std::system_error& e)
147  {
148  throwVMError(
149  "An error occurred while loading module '" + file + "': " + std::string(e.what()) + "\n" +
150  "It is most likely because the versions of the module and the language don't match.");
151  }
152 
153  // load the mapping data
154  std::size_t i = 0;
155  while (map[i].name != nullptr)
156  {
157  // put it in the global frame, aka the first one
158  auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), std::string(map[i].name));
159  if (it != m_state.m_symbols.end())
160  (*context.locals[0]).push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
161 
162  // free memory because we have used it and don't need it anymore
163  // no need to free map[i].value since it's a pointer to a function in the DLL
164  delete[] map[i].name;
165  ++i;
166  }
167 
168  // free memory
169  delete[] map;
170  }
171 
172  void VM::exit(int code) noexcept
173  {
174  m_exit_code = code;
175  m_running = false;
176  }
177 
178  // ------------------------------------------
179  // user pointer
180  // ------------------------------------------
181 
182  void VM::setUserPointer(void* ptr) noexcept
183  {
184  m_user_pointer = ptr;
185  }
186 
187  void* VM::getUserPointer() noexcept
188  {
189  return m_user_pointer;
190  }
191 
193  {
194  const std::lock_guard<std::mutex> lock(m_mutex);
195 
196  m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
197  ExecutionContext* ctx = m_execution_contexts.back().get();
198  ctx->scope_count_to_delete.emplace_back(0);
199 
200  ctx->locals.reserve(m_execution_contexts.front()->locals.size());
201  for (std::size_t i = 0, end = m_execution_contexts.front()->locals.size(); i < end; ++i)
202  {
203  ctx->locals.push_back(
204  std::make_shared<Scope>(*m_execution_contexts.front()->locals[i]));
205  }
206 
207  return ctx;
208  }
209 
211  {
212  const std::lock_guard<std::mutex> lock(m_mutex);
213 
214  auto it = std::remove_if(
215  m_execution_contexts.begin(),
216  m_execution_contexts.end(),
217  [ec](const std::unique_ptr<ExecutionContext>& ctx) {
218  return ctx.get() == ec;
219  });
220  m_execution_contexts.erase(it);
221  }
222 
223  Future* VM::createFuture(std::vector<Value>& args)
224  {
225  ExecutionContext* ctx = createAndGetContext();
226 
227  // doing this after having created the context
228  // because the context uses the mutex and we don't want a deadlock
229  const std::lock_guard<std::mutex> lock(m_mutex);
230  m_futures.push_back(std::make_unique<Future>(ctx, this, args));
231 
232  return m_futures.back().get();
233  }
234 
236  {
237  const std::lock_guard<std::mutex> lock(m_mutex);
238 
239  auto it = std::remove_if(
240  m_futures.begin(),
241  m_futures.end(),
242  [f](const std::unique_ptr<Future>& future) {
243  return future.get() == f;
244  });
245  m_futures.erase(it);
246  }
247 
248  // ------------------------------------------
249  // execution
250  // ------------------------------------------
251 
252  int VM::run() noexcept
253  {
254  init();
255  safeRun(*m_execution_contexts[0]);
256 
257  // reset VM after each run
258  for (auto& context : m_execution_contexts)
259  {
260  context->ip = 0;
261  context->pp = 0;
262  }
263 
264  return m_exit_code;
265  }
266 
267  int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount)
268  {
269 #ifdef ARK_PROFILER_MIPS
270  auto start_time = std::chrono::system_clock::now();
271  unsigned long long instructions_executed = 0;
272 #endif
273 
274  try
275  {
276  m_running = true;
277  while (m_running && context.fc > untilFrameCount)
278  {
279  // get current instruction
280  uint8_t inst = m_state.m_pages[context.pp][context.ip];
281 
282  // and it's time to du-du-du-du-duel!
283  switch (inst)
284  {
285 #pragma region "Instructions"
286 
288  {
289  /*
290  Argument: symbol id (two bytes, big endian)
291  Job: Load a symbol from its id onto the stack
292  */
293 
294  ++context.ip;
295  context.last_symbol = readNumber(context);
296 
297  if (Value* var = findNearestVariable(context.last_symbol, context); var != nullptr)
298  // push internal reference, shouldn't break anything so far
299  push(var, context);
300  else
301  throwVMError("unbound variable: " + m_state.m_symbols[context.last_symbol]);
302 
303  COZ_PROGRESS_NAMED("ark vm load_symbol");
304  break;
305  }
306 
308  {
309  /*
310  Argument: constant id (two bytes, big endian)
311  Job: Load a constant from its id onto the stack. Should check for a saved environment
312  and push a Closure with the page address + environment instead of the constant
313  */
314 
315  ++context.ip;
316  uint16_t id = readNumber(context);
317 
318  if (context.saved_scope && m_state.m_constants[id].valueType() == ValueType::PageAddr)
319  {
320  push(Value(Closure(context.saved_scope.value(), m_state.m_constants[id].pageAddr())), context);
321  context.saved_scope.reset();
322  }
323  else
324  {
325  // push internal ref
326  push(&(m_state.m_constants[id]), context);
327  }
328 
329  COZ_PROGRESS_NAMED("ark vm load_const");
330  break;
331  }
332 
334  {
335  /*
336  Argument: absolute address to jump to (two bytes, big endian)
337  Job: Jump to the provided address if the last value on the stack was equal to true.
338  Remove the value from the stack no matter what it is
339  */
340 
341  ++context.ip;
342  uint16_t id = readNumber(context);
343 
344  if (*popAndResolveAsPtr(context) == Builtins::trueSym)
345  context.ip = static_cast<int16_t>(id) - 1; // because we are doing a ++context.ip right after this
346  break;
347  }
348 
349  case Instruction::STORE:
350  {
351  /*
352  Argument: symbol id (two bytes, big endian)
353  Job: Take the value on top of the stack and put it inside a variable named following
354  the symbol id (cf symbols table), in the nearest scope. Raise an error if it
355  couldn't find a scope where the variable exists
356  */
357 
358  ++context.ip;
359  uint16_t id = readNumber(context);
360 
361  if (Value* var = findNearestVariable(id, context); var != nullptr)
362  {
363  if (var->isConst())
364  throwVMError("can not modify a constant: " + m_state.m_symbols[id]);
365 
366  *var = *popAndResolveAsPtr(context);
367  var->setConst(false);
368  break;
369  }
370 
371  COZ_PROGRESS_NAMED("ark vm store");
372 
373  throwVMError("unbound variable " + m_state.m_symbols[id] + ", can not change its value");
374  break;
375  }
376 
377  case Instruction::LET:
378  {
379  /*
380  Argument: symbol id (two bytes, big endian)
381  Job: Take the value on top of the stack and create a constant in the current scope, named
382  following the given symbol id (cf symbols table)
383  */
384 
385  ++context.ip;
386  uint16_t id = readNumber(context);
387 
388  // check if we are redefining a variable
389  if (auto val = (*context.locals.back())[id]; val != nullptr)
390  throwVMError("can not use 'let' to redefine the variable " + m_state.m_symbols[id]);
391 
392  Value val = *popAndResolveAsPtr(context);
393  val.setConst(true);
394  (*context.locals.back()).push_back(id, val);
395 
396  COZ_PROGRESS_NAMED("ark vm let");
397  break;
398  }
399 
401  {
402  /*
403  Argument: absolute address to jump to (two bytes, big endian)
404  Job: Jump to the provided address if the last value on the stack was equal to false. Remove
405  the value from the stack no matter what it is
406  */
407 
408  ++context.ip;
409  uint16_t id = readNumber(context);
410 
411  if (*popAndResolveAsPtr(context) == Builtins::falseSym)
412  context.ip = static_cast<int16_t>(id) - 1; // because we are doing a ++context.ip right after this
413  break;
414  }
415 
416  case Instruction::JUMP:
417  {
418  /*
419  Argument: absolute address to jump to (two byte, big endian)
420  Job: Jump to the provided address
421  */
422 
423  ++context.ip;
424  uint16_t id = readNumber(context);
425 
426  context.ip = static_cast<int16_t>(id) - 1; // because we are doing a ++context.ip right after this
427  break;
428  }
429 
430  case Instruction::RET:
431  {
432  /*
433  Argument: none
434  Job: If in a code segment other than the main one, quit it, and push the value on top of
435  the stack to the new stack ; should as well delete the current environment.
436  */
437 
438  Value ip_or_val = *popAndResolveAsPtr(context);
439  // no return value on the stack
440  if (ip_or_val.valueType() == ValueType::InstPtr)
441  {
442  context.ip = ip_or_val.pageAddr();
443  // we always push PP then IP, thus the next value
444  // MUST be the page pointer
445  context.pp = pop(context)->pageAddr();
446 
447  returnFromFuncCall(context);
448  push(Builtins::nil, context);
449  }
450  // value on the stack
451  else
452  {
453  Value* ip;
454  do
455  {
456  ip = popAndResolveAsPtr(context);
457  } while (ip->valueType() != ValueType::InstPtr);
458 
459  context.ip = ip->pageAddr();
460  context.pp = pop(context)->pageAddr();
461 
462  returnFromFuncCall(context);
463  push(std::move(ip_or_val), context);
464  }
465 
466  COZ_PROGRESS_NAMED("ark vm ret");
467  break;
468  }
469 
470  case Instruction::HALT:
471  m_running = false;
472  break;
473 
474  case Instruction::CALL:
475  call(context);
476  break;
477 
479  {
480  /*
481  Argument: symbol id (two bytes, big endian)
482  Job: Used to tell the Virtual Machine to capture the variable from the current environment.
483  Main goal is to be able to handle closures, which need to save the environment in which
484  they were created
485  */
486 
487  ++context.ip;
488  uint16_t id = readNumber(context);
489 
490  if (!context.saved_scope)
491  context.saved_scope = std::make_shared<Scope>();
492  // if it's a captured variable, it can not be nullptr
493  Value* ptr = (*context.locals.back())[id];
494  ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
495  (*context.saved_scope.value()).push_back(id, *ptr);
496 
497  COZ_PROGRESS_NAMED("ark vm capture");
498  break;
499  }
500 
502  {
503  /*
504  Argument: id of builtin (two bytes, big endian)
505  Job: Push the builtin function object on the stack
506  */
507 
508  ++context.ip;
509  uint16_t id = readNumber(context);
510 
511  push(Builtins::builtins[id].second, context);
512 
513  COZ_PROGRESS_NAMED("ark vm builtin");
514  break;
515  }
516 
517  case Instruction::MUT:
518  {
519  /*
520  Argument: symbol id (two bytes, big endian)
521  Job: Take the value on top of the stack and create a variable in the current scope,
522  named following the given symbol id (cf symbols table)
523  */
524 
525  ++context.ip;
526  uint16_t id = readNumber(context);
527 
528  Value val = *popAndResolveAsPtr(context);
529  val.setConst(false);
530 
531  // avoid adding the pair (id, _) multiple times, with different values
532  Value* local = (*context.locals.back())[id];
533  if (local == nullptr)
534  (*context.locals.back()).push_back(id, val);
535  else
536  *local = val;
537 
538  COZ_PROGRESS_NAMED("ark vm mut");
539  break;
540  }
541 
542  case Instruction::DEL:
543  {
544  /*
545  Argument: symbol id (two bytes, big endian)
546  Job: Remove a variable/constant named following the given symbol id (cf symbols table)
547  */
548 
549  ++context.ip;
550  uint16_t id = readNumber(context);
551 
552  if (Value* var = findNearestVariable(id, context); var != nullptr)
553  {
554  if (var->valueType() == ValueType::User)
555  var->usertypeRef().del();
556  *var = Value();
557  break;
558  }
559 
560  COZ_PROGRESS_NAMED("ark vm del");
561 
562  throwVMError("unbound variable: " + m_state.m_symbols[id]);
563  break;
564  }
565 
567  {
568  /*
569  Argument: none
570  Job: Save the current environment, useful for quoted code
571  */
572  context.saved_scope = context.locals.back();
573 
574  COZ_PROGRESS_NAMED("ark vm save_scope");
575  break;
576  }
577 
579  {
580  /*
581  Argument: symbol id (two bytes, big endian)
582  Job: Used to read the field named following the given symbol id (cf symbols table) of a `Closure`
583  stored in TS. Pop TS and push the value of field read on the stack
584  */
585 
586  ++context.ip;
587  uint16_t id = readNumber(context);
588 
589  Value* var = popAndResolveAsPtr(context);
590  if (var->valueType() != ValueType::Closure)
591  throwVMError("the variable `" + m_state.m_symbols[context.last_symbol] + "' isn't a closure, can not get the field `" + m_state.m_symbols[id] + "' from it");
592 
593  if (Value* field = (*var->refClosure().scope())[id]; field != nullptr)
594  {
595  // check for CALL instruction
596  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] == Instruction::CALL)
597  {
598  context.locals.push_back(var->refClosure().scope());
599  ++context.scope_count_to_delete.back();
600  }
601 
602  push(field, context);
603  break;
604  }
605 
606  throwVMError("couldn't find the variable " + m_state.m_symbols[id] + " in the closure enviroment");
607  break;
608  }
609 
610  case Instruction::PLUGIN:
611  {
612  /*
613  Argument: constant id (two bytes, big endian)
614  Job: Load a module named following the constant id (cf constants table).
615  Raise an error if it couldn't find the module.
616  */
617 
618  ++context.ip;
619  uint16_t id = readNumber(context);
620 
621  loadPlugin(id, context);
622 
623  COZ_PROGRESS_NAMED("ark vm plugin");
624  break;
625  }
626 
627  case Instruction::LIST:
628  {
629  /*
630  Takes at least 0 arguments and push a list on the stack.
631  The content is pushed in reverse order
632  */
633  ++context.ip;
634  uint16_t count = readNumber(context);
635 
637  if (count != 0)
638  l.list().reserve(count);
639 
640  for (uint16_t i = 0; i < count; ++i)
641  l.push_back(*popAndResolveAsPtr(context));
642  push(std::move(l), context);
643 
644  COZ_PROGRESS_NAMED("ark vm list");
645  break;
646  }
647 
648  case Instruction::APPEND:
649  {
650  ++context.ip;
651  uint16_t count = readNumber(context);
652 
653  Value* list = popAndResolveAsPtr(context);
654  if (list->valueType() != ValueType::List)
656  "append",
657  { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
658  { *list });
659 
660  const uint16_t size = list->constList().size();
661 
662  Value obj = Value(*list);
663  obj.list().reserve(size + count);
664 
665  for (uint16_t i = 0; i < count; ++i)
666  obj.push_back(*popAndResolveAsPtr(context));
667  push(std::move(obj), context);
668 
669  COZ_PROGRESS_NAMED("ark vm append");
670  break;
671  }
672 
673  case Instruction::CONCAT:
674  {
675  ++context.ip;
676  uint16_t count = readNumber(context);
677 
678  Value* list = popAndResolveAsPtr(context);
679  if (list->valueType() != ValueType::List)
681  "concat",
682  { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
683  { *list });
684 
685  Value obj = Value(*list);
686 
687  for (uint16_t i = 0; i < count; ++i)
688  {
689  Value* next = popAndResolveAsPtr(context);
690 
691  if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
693  "concat",
695  { *list, *next });
696 
697  for (auto it = next->list().begin(), end = next->list().end(); it != end; ++it)
698  obj.push_back(*it);
699  }
700  push(std::move(obj), context);
701 
702  COZ_PROGRESS_NAMED("ark vm concat");
703  break;
704  }
705 
707  {
708  ++context.ip;
709  uint16_t count = readNumber(context);
710 
711  Value* list = popAndResolveAsPtr(context);
712 
713  if (list->isConst())
714  throwVMError("can not modify a constant list using `append!'");
715  if (list->valueType() != ValueType::List)
717  "append!",
718  { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
719  { *list });
720 
721  for (uint16_t i = 0; i < count; ++i)
722  list->push_back(*popAndResolveAsPtr(context));
723 
724  push(Nil, context);
725 
726  COZ_PROGRESS_NAMED("ark vm append!");
727  break;
728  }
729 
731  {
732  ++context.ip;
733  uint16_t count = readNumber(context);
734 
735  Value* list = popAndResolveAsPtr(context);
736 
737  if (list->isConst())
738  throwVMError("can not modify a constant list using `concat!'");
739  if (list->valueType() != ValueType::List)
741  "concat",
742  { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
743  { *list });
744 
745  for (uint16_t i = 0; i < count; ++i)
746  {
747  Value* next = popAndResolveAsPtr(context);
748 
749  if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
751  "concat!",
753  { *list, *next });
754 
755  for (auto it = next->list().begin(), end = next->list().end(); it != end; ++it)
756  list->push_back(*it);
757  }
758 
759  push(Nil, context);
760 
761  COZ_PROGRESS_NAMED("ark vm concat!");
762  break;
763  }
764 
766  {
767  Value list = *popAndResolveAsPtr(context);
768  Value number = *popAndResolveAsPtr(context);
769 
770  if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
772  "pop",
774  { list, number });
775 
776  long idx = static_cast<long>(number.number());
777  idx = (idx < 0 ? list.list().size() + idx : idx);
778  if (static_cast<std::size_t>(idx) >= list.list().size())
779  throw std::runtime_error("pop: index out of range");
780 
781  list.list().erase(list.list().begin() + idx);
782  push(list, context);
783  break;
784  }
785 
787  {
788  Value* list = popAndResolveAsPtr(context);
789  Value number = *popAndResolveAsPtr(context);
790 
791  if (list->isConst())
792  throwVMError("can not modify a constant list using `pop!'");
793  if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
795  "pop!",
797  { *list, number });
798 
799  long idx = static_cast<long>(number.number());
800  idx = (idx < 0 ? list->list().size() + idx : idx);
801  if (static_cast<std::size_t>(idx) >= list->list().size())
802  throw std::runtime_error("pop!: index out of range");
803 
804  list->list().erase(list->list().begin() + idx);
805  break;
806  }
807 
808  case Instruction::POP:
809  {
810  pop(context);
811  break;
812  }
813 
814 #pragma endregion
815 
816 #pragma region "Operators"
817 
818  case Instruction::ADD:
819  {
820  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
821 
822  if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
823  push(Value(a->number() + b->number()), context);
824  else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
825  push(Value(a->string() + b->string()), context);
826  else
828  "+",
831  { *a, *b });
832  break;
833  }
834 
835  case Instruction::SUB:
836  {
837  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
838 
839  if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
841  "-",
843  { *a, *b });
844  else
845  push(Value(a->number() - b->number()), context);
846  break;
847  }
848 
849  case Instruction::MUL:
850  {
851  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
852 
853  if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
855  "*",
857  { *a, *b });
858  else
859  push(Value(a->number() * b->number()), context);
860  break;
861  }
862 
863  case Instruction::DIV:
864  {
865  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
866 
867  if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
869  "/",
871  { *a, *b });
872  else
873  {
874  auto d = b->number();
875  if (d == 0)
876  throw ZeroDivisionError();
877 
878  push(Value(a->number() / d), context);
879  }
880  break;
881  }
882 
883  case Instruction::GT:
884  {
885  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
886 
887  push((!(*a == *b) && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
888  break;
889  }
890 
891  case Instruction::LT:
892  {
893  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
894 
895  push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
896  break;
897  }
898 
899  case Instruction::LE:
900  {
901  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
902 
903  push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
904  break;
905  }
906 
907  case Instruction::GE:
908  {
909  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
910 
911  push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
912  break;
913  }
914 
915  case Instruction::NEQ:
916  {
917  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
918 
919  push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
920  break;
921  }
922 
923  case Instruction::EQ:
924  {
925  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
926 
927  push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
928  break;
929  }
930 
931  case Instruction::LEN:
932  {
933  Value* a = popAndResolveAsPtr(context);
934 
935  if (a->valueType() == ValueType::List)
936  push(Value(static_cast<int>(a->constList().size())), context);
937  else if (a->valueType() == ValueType::String)
938  push(Value(static_cast<int>(a->string().size())), context);
939  else
941  "len",
942  { { types::Contract { { types::Typedef("value", ValueType::List) } },
943  types::Contract { { types::Typedef("value", ValueType::String) } } } },
944  { *a });
945  break;
946  }
947 
948  case Instruction::EMPTY:
949  {
950  Value* a = popAndResolveAsPtr(context);
951 
952  if (a->valueType() == ValueType::List)
953  push((a->constList().size() == 0) ? Builtins::trueSym : Builtins::falseSym, context);
954  else if (a->valueType() == ValueType::String)
955  push((a->string().size() == 0) ? Builtins::trueSym : Builtins::falseSym, context);
956  else
958  "empty?",
959  { { types::Contract { { types::Typedef("value", ValueType::List) } },
960  types::Contract { { types::Typedef("value", ValueType::String) } } } },
961  { *a });
962  break;
963  }
964 
965  case Instruction::TAIL:
966  {
967  Value* a = popAndResolveAsPtr(context);
968 
969  if (a->valueType() == ValueType::List)
970  {
971  if (a->constList().size() < 2)
972  push(Value(ValueType::List), context);
973  else
974  {
975  std::vector<Value> tmp(a->constList().size() - 1);
976  for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
977  tmp[i - 1] = a->constList()[i];
978  push(Value(std::move(tmp)), context);
979  }
980  }
981  else if (a->valueType() == ValueType::String)
982  {
983  if (a->string().size() < 2)
984  push(Value(ValueType::String), context);
985  else
986  {
987  Value b = *a;
988  b.stringRef().erase_front(0);
989  push(std::move(b), context);
990  }
991  }
992  else
994  "tail",
995  { { types::Contract { { types::Typedef("value", ValueType::List) } },
996  types::Contract { { types::Typedef("value", ValueType::String) } } } },
997  { *a });
998 
999  break;
1000  }
1001 
1002  case Instruction::HEAD:
1003  {
1004  Value* a = popAndResolveAsPtr(context);
1005 
1006  if (a->valueType() == ValueType::List)
1007  {
1008  if (a->constList().size() == 0)
1009  push(Builtins::nil, context);
1010  else
1011  {
1012  Value b = a->constList()[0];
1013  push(b, context);
1014  }
1015  }
1016  else if (a->valueType() == ValueType::String)
1017  {
1018  if (a->string().size() == 0)
1019  push(Value(ValueType::String), context);
1020  else
1021  push(Value(std::string(1, a->stringRef()[0])), context);
1022  }
1023  else
1025  "head",
1026  { { types::Contract { { types::Typedef("value", ValueType::List) } },
1027  types::Contract { { types::Typedef("value", ValueType::String) } } } },
1028  { *a });
1029 
1030  break;
1031  }
1032 
1033  case Instruction::ISNIL:
1034  {
1035  Value* a = popAndResolveAsPtr(context);
1036  push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
1037  break;
1038  }
1039 
1040  case Instruction::ASSERT:
1041  {
1042  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1043 
1044  if (b->valueType() != ValueType::String)
1046  "assert",
1047  { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1048  { *a, *b });
1049 
1050  if (*a == Builtins::falseSym)
1051  throw AssertionFailed(b->stringRef().toString());
1052  break;
1053  }
1054 
1055  case Instruction::TO_NUM:
1056  {
1057  Value* a = popAndResolveAsPtr(context);
1058 
1059  if (a->valueType() != ValueType::String)
1061  "toNumber",
1062  { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1063  { *a });
1064 
1065  double val;
1066  if (Utils::isDouble(a->string().c_str(), &val))
1067  push(Value(val), context);
1068  else
1069  push(Builtins::nil, context);
1070  break;
1071  }
1072 
1073  case Instruction::TO_STR:
1074  {
1075  std::stringstream ss;
1076  Value* a = popAndResolveAsPtr(context);
1077  a->toString(ss, *this);
1078  push(Value(ss.str()), context);
1079  break;
1080  }
1081 
1082  case Instruction::AT:
1083  {
1084  Value* b = popAndResolveAsPtr(context);
1085  Value a = *popAndResolveAsPtr(context); // be careful, it's not a pointer
1086 
1087  if (b->valueType() != ValueType::Number)
1089  "@",
1092  { a, *b });
1093 
1094  long idx = static_cast<long>(b->number());
1095 
1096  if (a.valueType() == ValueType::List)
1097  push(a.list()[idx < 0 ? a.list().size() + idx : idx], context);
1098  else if (a.valueType() == ValueType::String)
1099  push(Value(std::string(1, a.string()[idx < 0 ? a.string().size() + idx : idx])), context);
1100  else
1102  "@",
1105  { a, *b });
1106  break;
1107  }
1108 
1109  case Instruction::AND_:
1110  {
1111  Value *a = popAndResolveAsPtr(context), *b = popAndResolveAsPtr(context);
1112 
1113  push((*a == Builtins::trueSym && *b == Builtins::trueSym) ? Builtins::trueSym : Builtins::falseSym, context);
1114  break;
1115  }
1116 
1117  case Instruction::OR_:
1118  {
1119  Value *a = popAndResolveAsPtr(context), *b = popAndResolveAsPtr(context);
1120 
1121  push((*b == Builtins::trueSym || *a == Builtins::trueSym) ? Builtins::trueSym : Builtins::falseSym, context);
1122  break;
1123  }
1124 
1125  case Instruction::MOD:
1126  {
1127  Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1128 
1129  if (a->valueType() != ValueType::Number)
1130  throw TypeError("Arguments of mod should be Numbers");
1131  if (b->valueType() != ValueType::Number)
1132  throw TypeError("Arguments of mod should be Numbers");
1133 
1134  push(Value(std::fmod(a->number(), b->number())), context);
1135  break;
1136  }
1137 
1138  case Instruction::TYPE:
1139  {
1140  Value* a = popAndResolveAsPtr(context);
1141 
1142  push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
1143  break;
1144  }
1145 
1146  case Instruction::HASFIELD:
1147  {
1148  Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
1149 
1150  if (closure->valueType() != ValueType::Closure)
1151  throw TypeError("Argument no 1 of hasField should be a Closure");
1152  if (field->valueType() != ValueType::String)
1153  throw TypeError("Argument no 2 of hasField should be a String");
1154 
1155  auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef().toString());
1156  if (it == m_state.m_symbols.end())
1157  {
1158  push(Builtins::falseSym, context);
1159  break;
1160  }
1161 
1162  uint16_t id = static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1163  push((*closure->refClosure().refScope())[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1164 
1165  break;
1166  }
1167 
1168  case Instruction::NOT:
1169  {
1170  Value* a = popAndResolveAsPtr(context);
1171 
1172  push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
1173  break;
1174  }
1175 
1176 #pragma endregion
1177 
1178  default:
1179  throwVMError("unknown instruction: " + std::to_string(static_cast<std::size_t>(inst)));
1180  break;
1181  }
1182 
1183  // move forward
1184  ++context.ip;
1185 
1186 #ifdef ARK_PROFILER_MIPS
1187  ++instructions_executed;
1188 #endif
1189  }
1190  }
1191  catch (const std::exception& e)
1192  {
1193  std::printf("%s\n", e.what());
1194  backtrace(context);
1195  m_exit_code = 1;
1196  }
1197  catch (...)
1198  {
1199  std::printf("Unknown error\n");
1200  backtrace(context);
1201  m_exit_code = 1;
1202  }
1203 
1204  if (m_state.m_debug_level > 0)
1205  std::cout << "Estimated stack trashing: " << context.sp << "/" << VMStackSize << "\n";
1206 
1207 #ifdef ARK_PROFILER_MIPS
1208  auto end_time = std::chrono::system_clock::now();
1209  auto d = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
1210 
1211  std::cout << "\nInstructions executed: " << instructions_executed << "\n"
1212  << "Time spent: " << d.count() << " us\n"
1213  << (static_cast<double>(instructions_executed) / d.count()) << " MIPS\n";
1214 #endif
1215 
1216  return m_exit_code;
1217  }
1218 
1219  // ------------------------------------------
1220  // error handling
1221  // ------------------------------------------
1222 
1223  uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
1224  {
1225  for (auto it = context.locals.rbegin(), it_end = context.locals.rend(); it != it_end; ++it)
1226  {
1227  if (auto id = (*it)->idFromValue(value); id < m_state.m_symbols.size())
1228  return id;
1229  }
1230  return std::numeric_limits<uint16_t>::max();
1231  }
1232 
1233  void VM::throwVMError(const std::string& message)
1234  {
1235  throw std::runtime_error(message);
1236  }
1237 
1238  void VM::backtrace(ExecutionContext& context) noexcept
1239  {
1240  int saved_ip = context.ip;
1241  std::size_t saved_pp = context.pp;
1242  uint16_t saved_sp = context.sp;
1243 
1244  if (context.fc > 1)
1245  {
1246  // display call stack trace
1247  uint16_t it = context.fc;
1248  Scope old_scope = *context.locals.back().get();
1249 
1250  while (it != 0)
1251  {
1252  std::cerr << "[" << termcolor::cyan << it << termcolor::reset << "] ";
1253  if (context.pp != 0)
1254  {
1255  uint16_t id = findNearestVariableIdWithValue(
1256  Value(static_cast<PageAddr_t>(context.pp)),
1257  context);
1258 
1259  if (id < m_state.m_symbols.size())
1260  std::cerr << "In function `" << termcolor::green << m_state.m_symbols[id] << termcolor::reset << "'\n";
1261  else // should never happen
1262  std::cerr << "In function `" << termcolor::yellow << "???" << termcolor::reset << "'\n";
1263 
1264  Value* ip;
1265  do
1266  {
1267  ip = popAndResolveAsPtr(context);
1268  } while (ip->valueType() != ValueType::InstPtr);
1269 
1270  context.ip = ip->pageAddr();
1271  context.pp = pop(context)->pageAddr();
1272  returnFromFuncCall(context);
1273  --it;
1274  }
1275  else
1276  {
1277  std::printf("In global scope\n");
1278  break;
1279  }
1280 
1281  if (context.fc - it > 7)
1282  {
1283  std::printf("...\n");
1284  break;
1285  }
1286  }
1287 
1288  // display variables values in the current scope
1289  std::printf("\nCurrent scope variables values:\n");
1290  for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
1291  {
1292  std::cerr << termcolor::cyan << m_state.m_symbols[old_scope.m_data[i].first] << termcolor::reset
1293  << " = ";
1294  old_scope.m_data[i].second.toString(std::cerr, *this);
1295  std::cerr << "\n";
1296  }
1297 
1298  while (context.fc != 1)
1299  {
1300  Value* tmp = pop(context);
1301  if (tmp->valueType() == ValueType::InstPtr)
1302  --context.fc;
1303  *tmp = m_no_value;
1304  }
1305  // pop the PP as well
1306  pop(context);
1307  }
1308 
1309  std::cerr << termcolor::reset
1310  << "At IP: " << (saved_ip != -1 ? saved_ip : 0)
1311  << ", PP: " << saved_pp
1312  << ", SP: " << saved_sp
1313  << "\n";
1314  }
1315 }
#define ARK_NO_NAME_FILE
Definition: Constants.hpp:26
Lots of utilities about the filesystem.
#define COZ_PROGRESS_NAMED(name)
Definition: Profiling.hpp:9
Lots of utilities about string, filesystem and more.
The ArkScript virtual machine.
An assertion error, only triggered from ArkScript code through (assert expr error-message)
Definition: Exceptions.hpp:71
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition: State.hpp:31
A type error triggered when types don't match.
Definition: Exceptions.hpp:29
The ArkScript virtual machine, executing ArkScript bytecode.
Definition: VM.hpp:47
int run() noexcept
Run the bytecode held in the state.
Definition: VM.cpp:252
void deleteContext(internal::ExecutionContext *ec)
Free a given execution context.
Definition: VM.cpp:210
Value & operator[](const std::string &name) noexcept
Retrieve a value from the virtual machine, given its symbol name.
Definition: VM.cpp:68
uint16_t findNearestVariableIdWithValue(const Value &value, internal::ExecutionContext &context) const noexcept
Find the nearest variable id with a given value.
Definition: VM.cpp:1223
void setUserPointer(void *ptr) noexcept
Set the User Pointer object.
Definition: VM.cpp:182
internal::ExecutionContext * createAndGetContext()
Create an execution context and returns it.
Definition: VM.cpp:192
void loadPlugin(uint16_t id, internal::ExecutionContext &context)
Load a plugin from a constant id.
Definition: VM.cpp:90
void deleteFuture(internal::Future *f)
Free a given future.
Definition: VM.cpp:235
void throwVMError(const std::string &message)
Throw a VM error message.
Definition: VM.cpp:1233
void backtrace(internal::ExecutionContext &context) noexcept
Display a backtrace when the VM encounter an exception.
Definition: VM.cpp:1238
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:267
void init() noexcept
Initialize the VM according to the parameters.
Definition: VM.cpp:35
void * getUserPointer() noexcept
Retrieves the stored pointer.
Definition: VM.cpp:187
VM(State &state) noexcept
Construct a new vm t object.
Definition: VM.cpp:27
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:223
void exit(int code) noexcept
Ask the VM to exit with a given exit code.
Definition: VM.cpp:172
std::vector< Value > & list()
Return the stored list as a reference.
Definition: Value.cpp:112
const String & string() const
Return the stored string.
bool isConst() const noexcept
Check if the value is const or not.
const std::vector< Value > & constList() const
Return the stored list.
internal::Closure & refClosure()
Return a reference to the closure held by the value.
Definition: Value.cpp:117
Value * reference() const
Return the stored internal object reference.
Definition: Value.cpp:132
double number() const
Return the stored number.
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:139
ValueType valueType() const noexcept
Return the value type.
String & stringRef()
Return the stored string as a reference.
Definition: Value.cpp:122
void setConst(bool value) noexcept
Set the Const object.
internal::PageAddr_t pageAddr() const
Return the page address held by the value.
void toString(std::ostream &os, VM &vm) const noexcept
Definition: Value.cpp:150
A special zero division error triggered when a number is divided by 0.
Definition: Exceptions.hpp:41
Closure management.
Definition: Closure.hpp:45
const Scope_t & scope() const noexcept
Return the scope held by the object.
Definition: Closure.cpp:24
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:88
std::size_t size() const noexcept
Return the size of the scope.
Definition: Scope.cpp:47
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:58
const std::vector< std::pair< std::string, Value > > builtins
uint16_t PageAddr_t
Definition: Closure.hpp:38
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:82
Definition: Builtins.hpp:21
constexpr std::size_t VMStackSize
Definition: Constants.hpp:53
const Value Nil
ArkScript Nil value.
Definition: VM.hpp:337
const std::array< std::string, 13 > types_to_str
Definition: Value.hpp:59
std::size_t pp
Page pointer.
std::optional< Scope_t > saved_scope
std::vector< uint8_t > scope_count_to_delete
A contract is a list of typed arguments that a function can follow.
Definition: TypeChecker.hpp:90
A type definition within a contract.
Definition: TypeChecker.hpp:71
Definition: VM.cpp:18
Ark::Value(* value)(std::vector< Ark::Value > &, Ark::VM *)
Definition: VM.cpp:20
char * name
Definition: VM.cpp:19