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