ArkScript
A small, fast, functional and scripting language for video games
VM.hpp
Go to the documentation of this file.
1/**
2 * @file VM.hpp
3 * @author Alexandre Plateau ([email protected])
4 * @brief The ArkScript virtual machine
5 * @date 2020-10-27
6 *
7 * @copyright Copyright (c) 2020-2025
8 *
9 */
10
11#ifndef ARK_VM_VM_HPP
12#define ARK_VM_VM_HPP
13
14#include <array>
15#include <vector>
16#include <string>
17#include <cassert>
18#include <utility>
19#include <cinttypes>
20#include <unordered_map>
21#include <algorithm>
22#include <fmt/core.h>
23#include <fmt/ranges.h>
24
26#include <Ark/VM/Value.hpp>
27#include <Ark/VM/State.hpp>
28#include <Ark/VM/ScopeView.hpp>
29#include <Ark/VM/ErrorKind.hpp>
32#include <Ark/Platform.hpp>
33#include <Ark/VM/Plugin.hpp>
34#include <Ark/VM/Future.hpp>
35
36namespace Ark
37{
38 using namespace std::string_literals;
39
40 /**
41 * @brief The ArkScript virtual machine, executing ArkScript bytecode
42 *
43 */
44 class ARK_API VM final
45 {
46 public:
47 /**
48 * @brief Construct a new vm t object
49 *
50 * @param state a reference to an ArkScript state, which can be reused for multiple VMs
51 */
52 explicit VM(State& state) noexcept;
53
54 /**
55 * @brief Run the bytecode held in the state
56 *
57 * @param fail_with_exception throw if true, display a stacktrace if false
58 * @return int the exit code (default to 0 if no error)
59 */
60 int run(bool fail_with_exception = false);
61
62 /**
63 * @brief Retrieve a value from the virtual machine, given its symbol name
64 *
65 * @param name the name of the variable to retrieve
66 * @return Value&
67 */
68 Value& operator[](const std::string& name) noexcept;
69
70 /**
71 * @brief Call a function from ArkScript, by giving it arguments
72 *
73 * @tparam Args
74 * @param name the function name in the ArkScript code
75 * @param args C++ argument list, converted to internal representation
76 * @return Value
77 */
78 template <typename... Args>
79 Value call(const std::string& name, Args&&... args);
80
81 // ================================================
82 // function calling from plugins
83 // ================================================
84
85 /**
86 * @brief Resolves a function call (called by plugins and builtins)
87 *
88 * @param context the execution context to use
89 * @param n the function and its arguments
90 * @return Value
91 */
92 inline Value resolve(internal::ExecutionContext* context, const std::vector<Value>& n);
93
94 /**
95 * @brief Ask the VM to exit with a given exit code
96 *
97 * @param code an exit code
98 */
99 void exit(int code) noexcept;
100
101 /**
102 * @brief Return a pointer to the first execution context, for the main thread of the app
103 * @return internal::ExecutionContext*
104 */
106 {
107 return m_execution_contexts.front().get();
108 }
109
110 /**
111 * @brief Create an execution context and returns it
112 * @details This method is thread-safe VM wise.
114 * @return internal::ExecutionContext*
115 */
116 internal::ExecutionContext* createAndGetContext();
117
118 /**
119 * @brief Free a given execution context
120 * @details This method is thread-safe VM wise.
121 *
122 * @param ec
123 */
124 void deleteContext(internal::ExecutionContext* ec);
125
126 /**
127 * @brief Create a Future object from a function and its arguments and return a managed pointer to it
128 * @details This method is thread-safe VM wise.
129 *
130 * @param args
131 * @return internal::Future*
132 */
133 internal::Future* createFuture(std::vector<Value>& args);
134
135 /**
136 * @brief Free a given future
137 * @details This method is thread-safe VM wise.
138 *
139 * @param f
140 */
141 void deleteFuture(internal::Future* f);
142
143 /**
144 * @brief Used by the REPL to force reload all the plugins and their bound methods
145 *
146 * @return true on success
147 * @return false if one or more plugins couldn't be reloaded
148 */
149 [[nodiscard]] bool forceReloadPlugins() const;
151 friend class Value;
152 friend class internal::Closure;
153 friend class Repl;
154
155 private:
157 std::vector<std::unique_ptr<internal::ExecutionContext>> m_execution_contexts;
158 int m_exit_code; ///< VM exit code, defaults to 0. Can be changed through `sys:exit`
160 std::mutex m_mutex;
161 std::vector<std::shared_ptr<internal::SharedLibrary>> m_shared_lib_objects;
162 std::vector<std::unique_ptr<internal::Future>> m_futures; ///< Storing the promises while we are resolving them
163
164 // a little trick for operator[] and for pop
165 Value m_no_value = internal::Builtins::nil;
168 /**
169 * @brief Run ArkScript bytecode inside a try catch to retrieve all the exceptions and display a stack trace if needed
170 *
171 * @param context
172 * @param untilFrameCount the frame count we need to reach before stopping the VM
173 * @param fail_with_exception throw if true, display a stacktrace if false
174 * @return int the exit code
175 */
176 int safeRun(internal::ExecutionContext& context, std::size_t untilFrameCount = 0, bool fail_with_exception = false);
177
178 /**
179 * @brief Initialize the VM according to the parameters
180 *
181 */
182 void init() noexcept;
183
184 // ================================================
185 // instruction helpers
186 // ================================================
187
188 /**
189 * @brief Load a symbol by its id in the current context. Performs a lookup in the scope stack, in reverse order.
190 *
191 * @param id symbol id
192 * @param context
193 * @return Value* nullptr if the symbol could not be loaded
194 */
195 [[nodiscard]] inline Value* loadSymbol(uint16_t id, internal::ExecutionContext& context);
196
197 /**
198 * @brief Load a symbol by its (reversed) index in the current scope
199 *
200 * @param index index of the symbol to load, starting from the end
201 * @param context
202 * @return Value*
203 */
204 [[nodiscard]] inline Value* loadSymbolFromIndex(uint16_t index, internal::ExecutionContext& context);
205
206 /**
207 * @brief Load a constant from the constant table by its id
208 *
209 * @param id
210 * @return Value*
211 */
212 [[nodiscard]] inline Value* loadConstAsPtr(uint16_t id) const;
213
214 /**
215 * @brief Create a new symbol with an associated value in the current scope
216 *
217 * @param id
218 * @param val
219 * @param context
220 */
221 inline void store(uint16_t id, const Value* val, internal::ExecutionContext& context);
222
223 /**
224 * @brief Change the value of a symbol given its identifier
225 *
226 * @param id
227 * @param val
228 * @param context
229 */
230 inline void setVal(uint16_t id, const Value* val, internal::ExecutionContext& context);
231
232 // ================================================
233 // stack related
234 // ================================================
235
236 /**
237 * @brief Pop a value from the stack
238 *
239 * @param context
240 * @return Value*
241 */
242 inline Value* pop(internal::ExecutionContext& context);
243
244 /**
245 * @brief Push a value on the stack
247 * @param value
248 * @param context
249 */
250 inline void push(const Value& value, internal::ExecutionContext& context);
251
252 /**
253 * @brief Push a value on the stack
254 *
255 * @param value
256 * @param context
257 */
258 inline void push(Value&& value, internal::ExecutionContext& context);
259
260 /**
261 * @brief Push a value on the stack as a reference
262 *
263 * @param valptr
264 * @param context
265 */
266 inline void push(Value* valptr, internal::ExecutionContext& context);
267
268 /**
269 * @brief Pop a value from the stack and resolve it if possible, then return it
270 *
271 * @param context
272 * @return Value*
273 */
274 inline Value* popAndResolveAsPtr(internal::ExecutionContext& context);
275
276 /**
277 * @brief Move stack values around and invert them
278 * @details values: 1, 2, 3, _, _
279 * wanted: pp, ip, 3, 2, 1
280 * positions: 0, 1, 2, 3, 4
281 *
282 * @param argc number of arguments to swap around
283 * @param context
284 */
285 inline void swapStackForFunCall(uint16_t argc, internal::ExecutionContext& context);
286
287 // ================================================
288 // locals related
289 // ================================================
290
291 /**
292 * @brief Find the nearest variable of a given id
293 *
294 * @param id the id to find
295 * @param context
296 * @return Value*
297 */
298 inline Value* findNearestVariable(uint16_t id, internal::ExecutionContext& context) noexcept;
299
300 /**
301 * @brief Destroy the current frame and get back to the previous one, resuming execution
302 *
303 * Doing the job nobody wants to do: cleaning after everyone has finished to play.
304 * This is a sort of primitive garbage collector
305 *
306 * @param context
307 */
308 inline void returnFromFuncCall(internal::ExecutionContext& context);
309
310 /**
311 * @brief Load a plugin from a constant id
312 *
313 * @param id Id of the constant
314 * @param context
315 */
316 void loadPlugin(uint16_t id, internal::ExecutionContext& context);
317
318 // ================================================
319 // error handling
320 // ================================================
321
322 /**
323 * @brief Find the nearest variable id with a given value
324 *
325 * Only used to display the call stack traceback
326 *
327 * @param value the value to search for
328 * @param context
329 * @return uint16_t
330 */
331 uint16_t findNearestVariableIdWithValue(const Value& value, internal::ExecutionContext& context) const noexcept;
332
333 /**
334 * @brief Throw a VM error message
335 *
336 * @param kind type of VM error
337 * @param message
338 */
339 static void throwVMError(internal::ErrorKind kind, const std::string& message);
340
341 void throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context);
342
343 void showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context);
344
345 /**
346 * @brief Find the nearest source location information given instruction and page pointers
347 *
348 * @param ip
349 * @param pp
350 * @return std::optional<InstLoc>
351 */
352 std::optional<internal::InstLoc> findSourceLocation(std::size_t ip, std::size_t pp);
353
354 /**
355 * @brief Display a backtrace when the VM encounter an exception
356 *
357 * @param context
358 * @param os
359 * @param colorize
360 */
361 void backtrace(internal::ExecutionContext& context, std::ostream& os = std::cout, bool colorize = true);
362
363 /**
364 * @brief Function called when the CALL instruction is met in the bytecode
365 *
366 * @param context
367 * @param argc number of arguments already sent
368 */
369 inline void call(internal::ExecutionContext& context, uint16_t argc);
370
371 /**
372 * @brief Builtin called when the CALL_BUILTIN instruction is met in the bytecode
373 *
374 * @param context
375 * @param builtin the builtin to call
376 * @param argc number of arguments already sent
377 */
378 inline void callBuiltin(internal::ExecutionContext& context, const Value& builtin, uint16_t argc);
379 };
380
381#include "VM.inl"
382
383 /// ArkScript Nil value
384 const auto Nil = Value(ValueType::Nil);
385 /// ArkScript False value
386 const auto False = Value(ValueType::False);
387 /// ArkScript True value
388 const auto True = Value(ValueType::True);
389}
390
391#endif
Host the declaration of all the ArkScript builtins.
Keeping track of the internal data needed by the VM.
Internal object to resolve asynchronously a function call in ArkScript.
The different instructions used by the compiler and virtual machine.
#define ARK_API
Definition Module.hpp:28
ArkScript configuration macros.
Loads .dll/.so/.dynlib files.
State used by the virtual machine: it loads the bytecode, can compile it if needed,...
Default value type handled by the virtual machine.
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition State.hpp:32
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:45
internal::ExecutionContext * getDefaultContext()
Return a pointer to the first execution context, for the main thread of the app.
Definition VM.hpp:105
std::vector< std::unique_ptr< internal::Future > > m_futures
Storing the promises while we are resolving them.
Definition VM.hpp:162
int m_exit_code
VM exit code, defaults to 0. Can be changed through sys:exit
Definition VM.hpp:158
std::vector< std::shared_ptr< internal::SharedLibrary > > m_shared_lib_objects
Definition VM.hpp:161
std::vector< std::unique_ptr< internal::ExecutionContext > > m_execution_contexts
Definition VM.hpp:157
std::mutex m_mutex
Definition VM.hpp:160
bool m_running
Definition VM.hpp:159
Value m_undefined_value
Definition VM.hpp:166
State & m_state
Definition VM.hpp:156
Closure management.
Definition Closure.hpp:36
const auto False
ArkScript False value.
Definition VM.hpp:386
const auto Nil
ArkScript Nil value.
Definition VM.hpp:384
const auto True
ArkScript True value.
Definition VM.hpp:388