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