• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ArkScript-lang / Ark / 21916613917

11 Feb 2026 05:55PM UTC coverage: 93.464% (+0.05%) from 93.413%
21916613917

Pull #640

github

web-flow
Merge 00c850ec3 into 9cb43e0cb
Pull Request #640: Feat/apply

623 of 648 new or added lines in 8 files covered. (96.14%)

1 existing line in 1 file now uncovered.

9195 of 9838 relevant lines covered (93.46%)

267918.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.34
/src/arkreactor/VM/VM.cpp
1
#include <Ark/VM/VM.hpp>
2

3
#include <utility>
4
#include <numeric>
5
#include <fmt/core.h>
6
#include <fmt/color.h>
7
#include <fmt/ostream.h>
8

9
#include <Ark/Utils/Files.hpp>
10
#include <Ark/Utils/Utils.hpp>
11
#include <Ark/Error/Diagnostics.hpp>
12
#include <Ark/TypeChecker.hpp>
13
#include <Ark/VM/ModuleMapping.hpp>
14
#include <Ark/Compiler/Instructions.hpp>
15
#include <Ark/VM/Value/Dict.hpp>
16
#include <Ark/VM/Helpers.hpp>
17

18
namespace Ark
19
{
20
    using namespace internal;
21

22
    VM::VM(State& state) noexcept :
792✔
23
        m_state(state), m_exit_code(0), m_running(false)
264✔
24
    {
264✔
25
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
264✔
26
    }
264✔
27

28
    void VM::init() noexcept
258✔
29
    {
258✔
30
        ExecutionContext& context = *m_execution_contexts.back();
258✔
31
        for (const auto& c : m_execution_contexts)
516✔
32
        {
33
            c->ip = 0;
258✔
34
            c->pp = 0;
258✔
35
            c->sp = 0;
258✔
36
        }
258✔
37

38
        context.sp = 0;
258✔
39
        context.fc = 1;
258✔
40

41
        m_shared_lib_objects.clear();
258✔
42
        context.stacked_closure_scopes.clear();
258✔
43
        context.stacked_closure_scopes.emplace_back(nullptr);
258✔
44

45
        context.saved_scope.reset();
258✔
46
        m_exit_code = 0;
258✔
47

48
        context.locals.clear();
258✔
49
        context.locals.reserve(128);
258✔
50
        context.locals.emplace_back(context.scopes_storage.data(), 0);
258✔
51

52
        // loading bound stuff
53
        // put them in the global frame if we can, aka the first one
54
        for (const auto& [sym_id, value] : m_state.m_bound)
810✔
55
        {
56
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
529✔
57
            if (it != m_state.m_symbols.end())
529✔
58
                context.locals[0].pushBack(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
23✔
59
        }
529✔
60
    }
258✔
61

62
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context)
4,242✔
63
    {
4,242✔
64
        if (closure->valueType() != ValueType::Closure)
4,242✔
65
        {
66
            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
67
                throwVMError(
2✔
68
                    ErrorKind::Type,
69
                    fmt::format(
3✔
70
                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
71
                        m_state.m_symbols[context.last_symbol],
1✔
72
                        std::to_string(closure->valueType()),
1✔
73
                        m_state.m_symbols[id]));
1✔
74
            else
75
                throwVMError(
×
76
                    ErrorKind::Type,
77
                    fmt::format(
×
78
                        "{} is not a Closure, can not get the field `{}' from it",
×
79
                        std::to_string(closure->valueType()),
×
80
                        m_state.m_symbols[id]));
×
81
        }
82

83
        if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
8,482✔
84
        {
85
            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
86
            if (m_state.inst(context.pp, context.ip) == CALL)
4,240✔
87
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
2,378✔
88
            else
89
                return *field;
1,862✔
90
        }
91
        else
92
        {
93
            if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
1✔
94
                throwVMError(
1✔
95
                    ErrorKind::Scope,
96
                    fmt::format(
2✔
97
                        "`{0}' isn't in the closure environment: {1}",
1✔
98
                        m_state.m_symbols[id],
1✔
99
                        closure->refClosure().toString(*this)));
1✔
100
            throwVMError(
×
101
                ErrorKind::Scope,
102
                fmt::format(
×
103
                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
×
104
                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
105
                    m_state.m_symbols[id],
×
106
                    closure->refClosure().toString(*this)));
×
107
        }
108
    }
4,242✔
109

110
    Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
2,073✔
111
    {
2,073✔
112
        Value l(ValueType::List);
2,073✔
113
        if (count != 0)
2,073✔
114
            l.list().reserve(count);
950✔
115

116
        for (std::size_t i = 0; i < count; ++i)
4,374✔
117
            l.push_back(*popAndResolveAsPtr(context));
2,301✔
118

119
        return l;
2,073✔
120
    }
2,073✔
121

122
    void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
3,501✔
123
    {
3,501✔
124
        if (list->valueType() != ValueType::List)
3,501✔
125
        {
126
            std::vector<Value> args = { *list };
1✔
127
            for (std::size_t i = 0; i < count; ++i)
2✔
128
                args.push_back(*popAndResolveAsPtr(context));
1✔
129
            throw types::TypeCheckingError(
2✔
130
                "append!",
1✔
131
                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
132
                args);
133
        }
1✔
134

135
        for (std::size_t i = 0; i < count; ++i)
7,000✔
136
            list->push_back(*popAndResolveAsPtr(context));
3,500✔
137
    }
3,501✔
138

139
    Value& VM::operator[](const std::string& name) noexcept
36✔
140
    {
36✔
141
        // find id of object
142
        const auto it = std::ranges::find(m_state.m_symbols, name);
36✔
143
        if (it == m_state.m_symbols.end())
36✔
144
        {
145
            m_no_value = Builtins::nil;
1✔
146
            return m_no_value;
1✔
147
        }
148

149
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
35✔
150
        if (std::cmp_less(dist, MaxValue16Bits))
35✔
151
        {
152
            ExecutionContext& context = *m_execution_contexts.front();
35✔
153

154
            const auto id = static_cast<uint16_t>(dist);
35✔
155
            Value* var = findNearestVariable(id, context);
35✔
156
            if (var != nullptr)
35✔
157
                return *var;
35✔
158
        }
35✔
159

160
        m_no_value = Builtins::nil;
×
161
        return m_no_value;
×
162
    }
36✔
163

164
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
1✔
165
    {
1✔
166
        namespace fs = std::filesystem;
167

168
        const std::string file = m_state.m_constants[id].stringRef();
1✔
169

170
        std::string path = file;
1✔
171
        // bytecode loaded from file
172
        if (m_state.m_filename != ARK_NO_NAME_FILE)
1✔
173
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
1✔
174

175
        std::shared_ptr<SharedLibrary> lib;
1✔
176
        // if it exists alongside the .arkc file
177
        if (Utils::fileExists(path))
1✔
178
            lib = std::make_shared<SharedLibrary>(path);
×
179
        else
180
        {
181
            for (auto const& v : m_state.m_libenv)
3✔
182
            {
183
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
2✔
184

185
                // if it's already loaded don't do anything
186
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
2✔
187
                        return (val->path() == path || val->path() == lib_path);
×
188
                    }) != m_shared_lib_objects.end())
266✔
189
                    return;
×
190

191
                // check in lib_path
264✔
192
                if (Utils::fileExists(lib_path))
2✔
193
                {
194
                    lib = std::make_shared<SharedLibrary>(lib_path);
1✔
195
                    break;
1✔
196
                }
197
            }
2✔
198
        }
199

200
        if (!lib)
1✔
201
        {
202
            auto lib_path = std::accumulate(
×
203
                std::next(m_state.m_libenv.begin()),
×
204
                m_state.m_libenv.end(),
×
205
                m_state.m_libenv[0].string(),
×
206
                [](const std::string& a, const fs::path& b) -> std::string {
×
207
                    return a + "\n\t- " + b.string();
×
208
                });
×
209
            throwVMError(
×
210
                ErrorKind::Module,
211
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
212
        }
×
213

214
        m_shared_lib_objects.emplace_back(lib);
1✔
215

216
        // load the mapping from the dynamic library
217
        try
218
        {
219
            std::vector<ScopeView::pair_t> data;
1✔
220
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
1✔
221

222
            std::size_t i = 0;
1✔
223
            while (map[i].name != nullptr)
2✔
224
            {
225
                const auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
1✔
226
                if (it != m_state.m_symbols.end())
1✔
227
                    data.emplace_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
1✔
228

229
                ++i;
1✔
230
            }
1✔
231

232
            context.locals.back().insertFront(data);
1✔
233
        }
1✔
234
        catch (const std::system_error& e)
235
        {
236
            throwVMError(
×
237
                ErrorKind::Module,
238
                fmt::format(
×
239
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
240
                    file, e.what()));
×
241
        }
1✔
242
    }
1✔
243

244
    void VM::exit(const int code) noexcept
×
245
    {
×
246
        m_exit_code = code;
×
247
        m_running = false;
×
248
    }
×
249

250
    ExecutionContext* VM::createAndGetContext()
17✔
251
    {
17✔
252
        const std::lock_guard lock(m_mutex);
17✔
253

254
        ExecutionContext* ctx = nullptr;
17✔
255

256
        // Try and find a free execution context.
257
        // If there is only one context, this is the primary one, which can't be reused.
258
        // Otherwise, we can check if a context is marked as free and reserve it!
259
        // It is possible that all contexts are being used, thus we will create one (active by default) in that case.
260

261
        if (m_execution_contexts.size() > 1)
17✔
262
        {
263
            const auto it = std::ranges::find_if(
28✔
264
                m_execution_contexts,
14✔
265
                [](const std::unique_ptr<ExecutionContext>& context) -> bool {
38✔
266
                    return !context->primary && context->isFree();
38✔
267
                });
268

269
            if (it != m_execution_contexts.end())
14✔
270
            {
271
                ctx = it->get();
10✔
272
                ctx->setActive(true);
10✔
273
                // reset the context before using it
274
                ctx->sp = 0;
10✔
275
                ctx->saved_scope.reset();
10✔
276
                ctx->stacked_closure_scopes.clear();
10✔
277
                ctx->locals.clear();
10✔
278
            }
10✔
279
        }
14✔
280

281
        if (ctx == nullptr)
17✔
282
            ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
7✔
283

284
        assert(!ctx->primary && "The new context shouldn't be marked as primary!");
17✔
285
        assert(ctx != m_execution_contexts.front().get() && "The new context isn't really new!");
17✔
286

287
        const ExecutionContext& primary_ctx = *m_execution_contexts.front();
17✔
288
        ctx->locals.reserve(primary_ctx.locals.size());
17✔
289
        ctx->scopes_storage = primary_ctx.scopes_storage;
17✔
290
        ctx->stacked_closure_scopes.emplace_back(nullptr);
17✔
291
        ctx->fc = 1;
17✔
292

293
        for (const auto& scope_view : primary_ctx.locals)
62✔
294
        {
295
            auto& new_scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), scope_view.m_start);
45✔
296
            for (std::size_t i = 0; i < scope_view.size(); ++i)
3,212✔
297
            {
298
                const auto& [id, val] = scope_view.atPos(i);
3,167✔
299
                new_scope.pushBack(id, val);
3,167✔
300
            }
3,167✔
301
        }
45✔
302

303
        return ctx;
17✔
304
    }
17✔
305

306
    void VM::deleteContext(ExecutionContext* ec)
16✔
307
    {
16✔
308
        const std::lock_guard lock(m_mutex);
16✔
309

310
        // 1 + 4 additional contexts, it's a bit much (~600kB per context) to have in memory
311
        if (m_execution_contexts.size() > 5)
16✔
312
        {
313
            const auto it =
1✔
314
                std::ranges::remove_if(
2✔
315
                    m_execution_contexts,
1✔
316
                    [ec](const std::unique_ptr<ExecutionContext>& ctx) {
7✔
317
                        return ctx.get() == ec;
6✔
318
                    })
319
                    .begin();
1✔
320
            m_execution_contexts.erase(it);
1✔
321
        }
1✔
322
        else
323
        {
324
            // mark the used context as ready to be used again
325
            for (std::size_t i = 1; i < m_execution_contexts.size(); ++i)
40✔
326
            {
327
                if (m_execution_contexts[i].get() == ec)
25✔
328
                {
329
                    ec->setActive(false);
15✔
330
                    break;
15✔
331
                }
332
            }
10✔
333
        }
334
    }
16✔
335

336
    Future* VM::createFuture(std::vector<Value>& args)
17✔
337
    {
17✔
338
        const std::lock_guard lock(m_mutex_futures);
17✔
339

340
        ExecutionContext* ctx = createAndGetContext();
17✔
341
        // so that we have access to the presumed symbol id of the function we are calling
342
        // assuming that the callee is always the global context
343
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
17✔
344

345
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
17✔
346
        return m_futures.back().get();
17✔
347
    }
17✔
348

349
    void VM::deleteFuture(Future* f)
1✔
350
    {
1✔
351
        const std::lock_guard lock(m_mutex_futures);
1✔
352

353
        std::erase_if(
1✔
354
            m_futures,
1✔
355
            [f](const std::unique_ptr<Future>& future) {
3✔
356
                return future.get() == f;
2✔
357
            });
358
    }
1✔
359

360
    bool VM::forceReloadPlugins() const
×
361
    {
×
362
        // load the mapping from the dynamic library
363
        try
364
        {
365
            for (const auto& shared_lib : m_shared_lib_objects)
×
366
            {
367
                const mapping* map = shared_lib->get<mapping* (*)()>("getFunctionsMapping")();
×
368
                // load the mapping data
369
                std::size_t i = 0;
×
370
                while (map[i].name != nullptr)
×
371
                {
372
                    // put it in the global frame, aka the first one
373
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
374
                    if (it != m_state.m_symbols.end())
×
375
                        m_execution_contexts[0]->locals[0].pushBack(
×
376
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
377
                            Value(map[i].value));
×
378

379
                    ++i;
×
380
                }
×
381
            }
×
382

383
            return true;
×
384
        }
×
385
        catch (const std::system_error&)
386
        {
387
            return false;
×
388
        }
×
389
    }
×
390

391
    void VM::usePromptFileForDebugger(const std::string& path, std::ostream& os)
5✔
392
    {
5✔
393
        m_debugger = std::make_unique<Debugger>(m_state.m_libenv, path, os, m_state.m_symbols, m_state.m_constants);
5✔
394
    }
5✔
395

396
    void VM::throwVMError(ErrorKind kind, const std::string& message)
36✔
397
    {
36✔
398
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
36✔
399
    }
36✔
400

401
    int VM::run(const bool fail_with_exception)
258✔
402
    {
258✔
403
        init();
258✔
404
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
258✔
405
        return m_exit_code;
258✔
406
    }
407

408
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
289✔
409
    {
289✔
410
#if ARK_USE_COMPUTED_GOTOS
411
#    define TARGET(op) TARGET_##op:
412
#    define DISPATCH_GOTO()            \
413
        _Pragma("GCC diagnostic push") \
414
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
415
        _Pragma("GCC diagnostic pop")
416
#    define GOTO_HALT() goto dispatch_end
417
#else
418
#    define TARGET(op) case op:
419
#    define DISPATCH_GOTO() goto dispatch_opcode
420
#    define GOTO_HALT() break
421
#endif
422

423
#define NEXTOPARG()                                                                                                               \
424
    do                                                                                                                            \
425
    {                                                                                                                             \
426
        inst = m_state.inst(context.pp, context.ip);                                                                              \
427
        padding = m_state.inst(context.pp, context.ip + 1);                                                                       \
428
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) +                                             \
429
                                    m_state.inst(context.pp, context.ip + 3));                                                    \
430
        context.ip += 4;                                                                                                          \
431
        context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize;                                       \
432
        if (context.inst_exec_counter < 2 && context.sp >= VMStackSize)                                                           \
433
        {                                                                                                                         \
434
            if (context.pp != 0)                                                                                                  \
435
                throw Error("Stack overflow. You could consider rewriting your function to make use of tail-call optimization."); \
436
            else                                                                                                                  \
437
                throw Error("Stack overflow. Are you trying to call a function with too many arguments?");                        \
438
        }                                                                                                                         \
439
    } while (false)
440
#define DISPATCH() \
441
    NEXTOPARG();   \
442
    DISPATCH_GOTO();
443
#define UNPACK_ARGS()                                                                 \
444
    do                                                                                \
445
    {                                                                                 \
446
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
447
        primary_arg = arg & 0x0fff;                                                   \
448
    } while (false)
449

450
#if ARK_USE_COMPUTED_GOTOS
451
#    pragma GCC diagnostic push
452
#    pragma GCC diagnostic ignored "-Wpedantic"
453
            constexpr std::array opcode_targets = {
289✔
454
                // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
455
                &&TARGET_NOP,
456
                &&TARGET_LOAD_FAST,
457
                &&TARGET_LOAD_FAST_BY_INDEX,
458
                &&TARGET_LOAD_SYMBOL,
459
                &&TARGET_LOAD_CONST,
460
                &&TARGET_POP_JUMP_IF_TRUE,
461
                &&TARGET_STORE,
462
                &&TARGET_STORE_REF,
463
                &&TARGET_SET_VAL,
464
                &&TARGET_POP_JUMP_IF_FALSE,
465
                &&TARGET_JUMP,
466
                &&TARGET_RET,
467
                &&TARGET_HALT,
468
                &&TARGET_PUSH_RETURN_ADDRESS,
469
                &&TARGET_CALL,
470
                &&TARGET_CAPTURE,
471
                &&TARGET_RENAME_NEXT_CAPTURE,
472
                &&TARGET_BUILTIN,
473
                &&TARGET_DEL,
474
                &&TARGET_MAKE_CLOSURE,
475
                &&TARGET_GET_FIELD,
476
                &&TARGET_PLUGIN,
477
                &&TARGET_LIST,
478
                &&TARGET_APPEND,
479
                &&TARGET_CONCAT,
480
                &&TARGET_APPEND_IN_PLACE,
481
                &&TARGET_CONCAT_IN_PLACE,
482
                &&TARGET_POP_LIST,
483
                &&TARGET_POP_LIST_IN_PLACE,
484
                &&TARGET_SET_AT_INDEX,
485
                &&TARGET_SET_AT_2_INDEX,
486
                &&TARGET_POP,
487
                &&TARGET_SHORTCIRCUIT_AND,
488
                &&TARGET_SHORTCIRCUIT_OR,
489
                &&TARGET_CREATE_SCOPE,
490
                &&TARGET_RESET_SCOPE_JUMP,
491
                &&TARGET_POP_SCOPE,
492
                &&TARGET_GET_CURRENT_PAGE_ADDR,
493
                &&TARGET_APPLY,
494
                &&TARGET_BREAKPOINT,
495
                &&TARGET_ADD,
496
                &&TARGET_SUB,
497
                &&TARGET_MUL,
498
                &&TARGET_DIV,
499
                &&TARGET_GT,
500
                &&TARGET_LT,
501
                &&TARGET_LE,
502
                &&TARGET_GE,
503
                &&TARGET_NEQ,
504
                &&TARGET_EQ,
505
                &&TARGET_LEN,
506
                &&TARGET_IS_EMPTY,
507
                &&TARGET_TAIL,
508
                &&TARGET_HEAD,
509
                &&TARGET_IS_NIL,
510
                &&TARGET_TO_NUM,
511
                &&TARGET_TO_STR,
512
                &&TARGET_AT,
513
                &&TARGET_AT_AT,
514
                &&TARGET_MOD,
515
                &&TARGET_TYPE,
516
                &&TARGET_HAS_FIELD,
517
                &&TARGET_NOT,
518
                &&TARGET_LOAD_CONST_LOAD_CONST,
519
                &&TARGET_LOAD_CONST_STORE,
520
                &&TARGET_LOAD_CONST_SET_VAL,
521
                &&TARGET_STORE_FROM,
522
                &&TARGET_STORE_FROM_INDEX,
523
                &&TARGET_SET_VAL_FROM,
524
                &&TARGET_SET_VAL_FROM_INDEX,
525
                &&TARGET_INCREMENT,
526
                &&TARGET_INCREMENT_BY_INDEX,
527
                &&TARGET_INCREMENT_STORE,
528
                &&TARGET_DECREMENT,
529
                &&TARGET_DECREMENT_BY_INDEX,
530
                &&TARGET_DECREMENT_STORE,
531
                &&TARGET_STORE_TAIL,
532
                &&TARGET_STORE_TAIL_BY_INDEX,
533
                &&TARGET_STORE_HEAD,
534
                &&TARGET_STORE_HEAD_BY_INDEX,
535
                &&TARGET_STORE_LIST,
536
                &&TARGET_SET_VAL_TAIL,
537
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
538
                &&TARGET_SET_VAL_HEAD,
539
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
540
                &&TARGET_CALL_BUILTIN,
541
                &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
542
                &&TARGET_LT_CONST_JUMP_IF_FALSE,
543
                &&TARGET_LT_CONST_JUMP_IF_TRUE,
544
                &&TARGET_LT_SYM_JUMP_IF_FALSE,
545
                &&TARGET_GT_CONST_JUMP_IF_TRUE,
546
                &&TARGET_GT_CONST_JUMP_IF_FALSE,
547
                &&TARGET_GT_SYM_JUMP_IF_FALSE,
548
                &&TARGET_EQ_CONST_JUMP_IF_TRUE,
549
                &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
550
                &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
551
                &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
552
                &&TARGET_CALL_SYMBOL,
553
                &&TARGET_CALL_CURRENT_PAGE,
554
                &&TARGET_GET_FIELD_FROM_SYMBOL,
555
                &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
556
                &&TARGET_AT_SYM_SYM,
557
                &&TARGET_AT_SYM_INDEX_SYM_INDEX,
558
                &&TARGET_AT_SYM_INDEX_CONST,
559
                &&TARGET_CHECK_TYPE_OF,
560
                &&TARGET_CHECK_TYPE_OF_BY_INDEX,
561
                &&TARGET_APPEND_IN_PLACE_SYM,
562
                &&TARGET_APPEND_IN_PLACE_SYM_INDEX,
563
                &&TARGET_STORE_LEN,
564
                &&TARGET_LT_LEN_SYM_JUMP_IF_FALSE,
565
                &&TARGET_MUL_BY,
566
                &&TARGET_MUL_BY_INDEX,
567
                &&TARGET_MUL_SET_VAL,
568
                &&TARGET_FUSED_MATH
569
            };
570

571
        static_assert(opcode_targets.size() == static_cast<std::size_t>(Instruction::InstructionsCount) && "Some instructions are not implemented in the VM");
572
#    pragma GCC diagnostic pop
573
#endif
574

575
        try
576
        {
577
            uint8_t inst = 0;
289✔
578
            uint8_t padding = 0;
289✔
579
            uint16_t arg = 0;
289✔
580
            uint16_t primary_arg = 0;
289✔
581
            uint16_t secondary_arg = 0;
289✔
582

583
            m_running = true;
289✔
584

585
            DISPATCH();
289✔
586
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
587
            {
588
#if !ARK_USE_COMPUTED_GOTOS
589
            dispatch_opcode:
590
                switch (inst)
591
#endif
592
                {
×
593
#pragma region "Instructions"
594
                    TARGET(NOP)
595
                    {
596
                        DISPATCH();
×
597
                    }
144,792✔
598

599
                    TARGET(LOAD_FAST)
600
                    {
601
                        push(loadSymbol(arg, context), context);
144,792✔
602
                        DISPATCH();
144,792✔
603
                    }
335,472✔
604

605
                    TARGET(LOAD_FAST_BY_INDEX)
606
                    {
607
                        push(loadSymbolFromIndex(arg, context), context);
335,472✔
608
                        DISPATCH();
335,472✔
609
                    }
4,598✔
610

611
                    TARGET(LOAD_SYMBOL)
612
                    {
613
                        // force resolving the reference
614
                        push(*loadSymbol(arg, context), context);
4,598✔
615
                        DISPATCH();
4,598✔
616
                    }
116,562✔
617

618
                    TARGET(LOAD_CONST)
619
                    {
620
                        push(loadConstAsPtr(arg), context);
116,562✔
621
                        DISPATCH();
116,562✔
622
                    }
31,238✔
623

624
                    TARGET(POP_JUMP_IF_TRUE)
625
                    {
626
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
39,791✔
627
                            jump(arg, context);
8,553✔
628
                        DISPATCH();
31,238✔
629
                    }
403,441✔
630

631
                    TARGET(STORE)
632
                    {
633
                        store(arg, popAndResolveAsPtr(context), context);
403,441✔
634
                        DISPATCH();
403,441✔
635
                    }
472✔
636

637
                    TARGET(STORE_REF)
638
                    {
639
                        // Not resolving a potential ref is on purpose!
640
                        // This instruction is only used by functions when storing arguments
641
                        const Value* tmp = pop(context);
472✔
642
                        store(arg, tmp, context);
472✔
643
                        DISPATCH();
472✔
644
                    }
23,855✔
645

646
                    TARGET(SET_VAL)
647
                    {
648
                        setVal(arg, popAndResolveAsPtr(context), context);
23,855✔
649
                        DISPATCH();
23,855✔
650
                    }
18,987✔
651

652
                    TARGET(POP_JUMP_IF_FALSE)
653
                    {
654
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
20,054✔
655
                            jump(arg, context);
1,067✔
656
                        DISPATCH();
18,987✔
657
                    }
209,111✔
658

659
                    TARGET(JUMP)
660
                    {
661
                        jump(arg, context);
209,111✔
662
                        DISPATCH();
209,111✔
663
                    }
139,433✔
664

665
                    TARGET(RET)
666
                    {
667
                        {
668
                            Value ip_or_val = *popAndResolveAsPtr(context);
139,433✔
669
                            // no return value on the stack
670
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
139,433✔
671
                            {
672
                                context.ip = ip_or_val.pageAddr();
4,186✔
673
                                // we always push PP then IP, thus the next value
674
                                // MUST be the page pointer
675
                                context.pp = pop(context)->pageAddr();
4,186✔
676

677
                                returnFromFuncCall(context);
4,186✔
678
                                push(Builtins::nil, context);
4,186✔
679
                            }
4,186✔
680
                            // value on the stack
681
                            else [[likely]]
682
                            {
683
                                const Value* ip = popAndResolveAsPtr(context);
135,247✔
684
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
135,247✔
685
                                context.ip = ip->pageAddr();
135,247✔
686
                                context.pp = pop(context)->pageAddr();
135,247✔
687

688
                                returnFromFuncCall(context);
135,247✔
689
                                push(std::move(ip_or_val), context);
135,247✔
690
                            }
691

692
                            if (context.fc <= untilFrameCount)
139,433✔
693
                                GOTO_HALT();
18✔
694
                        }
139,433✔
695

696
                        DISPATCH();
139,415✔
697
                    }
88✔
698

699
                    TARGET(HALT)
700
                    {
701
                        m_running = false;
88✔
702
                        GOTO_HALT();
88✔
703
                    }
143,471✔
704

705
                    TARGET(PUSH_RETURN_ADDRESS)
706
                    {
707
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
143,471✔
708
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
709
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
143,471✔
710
                        context.inst_exec_counter++;
143,471✔
711
                        DISPATCH();
143,471✔
712
                    }
3,519✔
713

714
                    TARGET(CALL)
715
                    {
716
                        call(context, arg);
3,519✔
717
                        if (!m_running)
3,511✔
718
                            GOTO_HALT();
×
719
                        DISPATCH();
3,511✔
720
                    }
3,209✔
721

722
                    TARGET(CAPTURE)
723
                    {
724
                        if (!context.saved_scope)
3,209✔
725
                            context.saved_scope = ClosureScope();
632✔
726

727
                        const Value* ptr = findNearestVariable(arg, context);
3,209✔
728
                        if (!ptr)
3,209✔
729
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
730
                        else
731
                        {
732
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,209✔
733
                            uint16_t id = context.capture_rename_id.value_or(arg);
3,209✔
734
                            context.saved_scope.value().push_back(id, *ptr);
3,209✔
735
                            context.capture_rename_id.reset();
3,209✔
736
                        }
737

738
                        DISPATCH();
3,209✔
739
                    }
13✔
740

741
                    TARGET(RENAME_NEXT_CAPTURE)
742
                    {
743
                        context.capture_rename_id = arg;
13✔
744
                        DISPATCH();
13✔
745
                    }
2,122✔
746

747
                    TARGET(BUILTIN)
748
                    {
749
                        push(Builtins::builtins[arg].second, context);
2,122✔
750
                        DISPATCH();
2,122✔
751
                    }
2✔
752

753
                    TARGET(DEL)
754
                    {
755
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
2✔
756
                        {
757
                            if (var->valueType() == ValueType::User)
1✔
758
                                var->usertypeRef().del();
1✔
759
                            *var = Value();
1✔
760
                            DISPATCH();
1✔
761
                        }
762

763
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
764
                    }
632✔
765

766
                    TARGET(MAKE_CLOSURE)
767
                    {
768
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
632✔
769
                        context.saved_scope.reset();
632✔
770
                        DISPATCH();
632✔
771
                    }
6✔
772

773
                    TARGET(GET_FIELD)
774
                    {
775
                        Value* var = popAndResolveAsPtr(context);
6✔
776
                        push(getField(var, arg, context), context);
6✔
777
                        DISPATCH();
6✔
778
                    }
1✔
779

780
                    TARGET(PLUGIN)
781
                    {
782
                        loadPlugin(arg, context);
1✔
783
                        DISPATCH();
1✔
784
                    }
1,058✔
785

786
                    TARGET(LIST)
787
                    {
788
                        {
789
                            Value l = createList(arg, context);
1,058✔
790
                            push(std::move(l), context);
1,058✔
791
                        }
1,058✔
792
                        DISPATCH();
1,058✔
793
                    }
30✔
794

795
                    TARGET(APPEND)
796
                    {
797
                        {
798
                            Value* list = popAndResolveAsPtr(context);
30✔
799
                            if (list->valueType() != ValueType::List)
30✔
800
                            {
801
                                std::vector<Value> args = { *list };
1✔
802
                                for (uint16_t i = 0; i < arg; ++i)
2✔
803
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
804
                                throw types::TypeCheckingError(
2✔
805
                                    "append",
1✔
806
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
807
                                    args);
808
                            }
1✔
809

810
                            const auto size = static_cast<uint16_t>(list->constList().size());
29✔
811

812
                            Value obj { *list };
29✔
813
                            obj.list().reserve(size + arg);
29✔
814

815
                            for (uint16_t i = 0; i < arg; ++i)
58✔
816
                                obj.push_back(*popAndResolveAsPtr(context));
29✔
817
                            push(std::move(obj), context);
29✔
818
                        }
29✔
819
                        DISPATCH();
29✔
820
                    }
15✔
821

822
                    TARGET(CONCAT)
823
                    {
824
                        {
825
                            Value* list = popAndResolveAsPtr(context);
15✔
826
                            Value obj { *list };
15✔
827

828
                            for (uint16_t i = 0; i < arg; ++i)
30✔
829
                            {
830
                                Value* next = popAndResolveAsPtr(context);
17✔
831

832
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
17✔
833
                                    throw types::TypeCheckingError(
4✔
834
                                        "concat",
2✔
835
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
836
                                        { *list, *next });
2✔
837

838
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
15✔
839
                            }
15✔
840
                            push(std::move(obj), context);
13✔
841
                        }
15✔
842
                        DISPATCH();
13✔
843
                    }
1✔
844

845
                    TARGET(APPEND_IN_PLACE)
846
                    {
847
                        Value* list = popAndResolveAsPtr(context);
1✔
848
                        listAppendInPlace(list, arg, context);
1✔
849
                        DISPATCH();
1✔
850
                    }
570✔
851

852
                    TARGET(CONCAT_IN_PLACE)
853
                    {
854
                        Value* list = popAndResolveAsPtr(context);
570✔
855

856
                        for (uint16_t i = 0; i < arg; ++i)
1,175✔
857
                        {
858
                            Value* next = popAndResolveAsPtr(context);
607✔
859

860
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
607✔
861
                                throw types::TypeCheckingError(
4✔
862
                                    "concat!",
2✔
863
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
864
                                    { *list, *next });
2✔
865

866
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
605✔
867
                        }
605✔
868
                        DISPATCH();
568✔
869
                    }
6✔
870

871
                    TARGET(POP_LIST)
872
                    {
873
                        {
874
                            Value list = *popAndResolveAsPtr(context);
6✔
875
                            Value number = *popAndResolveAsPtr(context);
6✔
876

877
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
6✔
878
                                throw types::TypeCheckingError(
2✔
879
                                    "pop",
1✔
880
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
881
                                    { list, number });
1✔
882

883
                            long idx = static_cast<long>(number.number());
5✔
884
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
5✔
885
                            if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
5✔
886
                                throwVMError(
2✔
887
                                    ErrorKind::Index,
888
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
889

890
                            list.list().erase(list.list().begin() + idx);
3✔
891
                            push(list, context);
3✔
892
                        }
6✔
893
                        DISPATCH();
3✔
894
                    }
232✔
895

896
                    TARGET(POP_LIST_IN_PLACE)
897
                    {
898
                        {
899
                            Value* list = popAndResolveAsPtr(context);
232✔
900
                            Value number = *popAndResolveAsPtr(context);
232✔
901

902
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
232✔
903
                                throw types::TypeCheckingError(
2✔
904
                                    "pop!",
1✔
905
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
906
                                    { *list, number });
1✔
907

908
                            long idx = static_cast<long>(number.number());
231✔
909
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
231✔
910
                            if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
231✔
911
                                throwVMError(
2✔
912
                                    ErrorKind::Index,
913
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
914

915
                            list->list().erase(list->list().begin() + idx);
229✔
916
                        }
232✔
917
                        DISPATCH();
229✔
918
                    }
510✔
919

920
                    TARGET(SET_AT_INDEX)
921
                    {
922
                        {
923
                            Value* list = popAndResolveAsPtr(context);
510✔
924
                            Value number = *popAndResolveAsPtr(context);
510✔
925
                            Value new_value = *popAndResolveAsPtr(context);
510✔
926

927
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
510✔
928
                                throw types::TypeCheckingError(
2✔
929
                                    "@=",
1✔
930
                                    { { types::Contract {
3✔
931
                                          { types::Typedef("list", ValueType::List),
3✔
932
                                            types::Typedef("index", ValueType::Number),
1✔
933
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
934
                                      { types::Contract {
1✔
935
                                          { types::Typedef("string", ValueType::String),
3✔
936
                                            types::Typedef("index", ValueType::Number),
1✔
937
                                            types::Typedef("char", ValueType::String) } } } },
1✔
938
                                    { *list, number, new_value });
1✔
939

940
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
509✔
941
                            long idx = static_cast<long>(number.number());
509✔
942
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
509✔
943
                            if (std::cmp_greater_equal(idx, size) || idx < 0)
509✔
944
                                throwVMError(
2✔
945
                                    ErrorKind::Index,
946
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
947

948
                            if (list->valueType() == ValueType::List)
507✔
949
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
505✔
950
                            else
951
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
952
                        }
510✔
953
                        DISPATCH();
507✔
954
                    }
12✔
955

956
                    TARGET(SET_AT_2_INDEX)
957
                    {
958
                        {
959
                            Value* list = popAndResolveAsPtr(context);
12✔
960
                            Value x = *popAndResolveAsPtr(context);
12✔
961
                            Value y = *popAndResolveAsPtr(context);
12✔
962
                            Value new_value = *popAndResolveAsPtr(context);
12✔
963

964
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
12✔
965
                                throw types::TypeCheckingError(
2✔
966
                                    "@@=",
1✔
967
                                    { { types::Contract {
2✔
968
                                        { types::Typedef("list", ValueType::List),
4✔
969
                                          types::Typedef("x", ValueType::Number),
1✔
970
                                          types::Typedef("y", ValueType::Number),
1✔
971
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
972
                                    { *list, x, y, new_value });
1✔
973

974
                            long idx_y = static_cast<long>(x.number());
11✔
975
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
11✔
976
                            if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
11✔
977
                                throwVMError(
2✔
978
                                    ErrorKind::Index,
979
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
980

981
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
13✔
982
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
8✔
983
                                throw types::TypeCheckingError(
2✔
984
                                    "@@=",
1✔
985
                                    { { types::Contract {
3✔
986
                                          { types::Typedef("list", ValueType::List),
4✔
987
                                            types::Typedef("x", ValueType::Number),
1✔
988
                                            types::Typedef("y", ValueType::Number),
1✔
989
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
990
                                      { types::Contract {
1✔
991
                                          { types::Typedef("string", ValueType::String),
4✔
992
                                            types::Typedef("x", ValueType::Number),
1✔
993
                                            types::Typedef("y", ValueType::Number),
1✔
994
                                            types::Typedef("char", ValueType::String) } } } },
1✔
995
                                    { *list, x, y, new_value });
1✔
996

997
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
998
                            const std::size_t size =
8✔
999
                                is_list
16✔
1000
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
1001
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1002

1003
                            long idx_x = static_cast<long>(y.number());
8✔
1004
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
8✔
1005
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
8✔
1006
                                throwVMError(
2✔
1007
                                    ErrorKind::Index,
1008
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1009

1010
                            if (is_list)
6✔
1011
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1012
                            else
1013
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1014
                        }
12✔
1015
                        DISPATCH();
6✔
1016
                    }
4,689✔
1017

1018
                    TARGET(POP)
1019
                    {
1020
                        pop(context);
4,689✔
1021
                        DISPATCH();
4,689✔
1022
                    }
23,905✔
1023

1024
                    TARGET(SHORTCIRCUIT_AND)
1025
                    {
1026
                        if (!*peekAndResolveAsPtr(context))
23,905✔
1027
                            jump(arg, context);
822✔
1028
                        else
1029
                            pop(context);
23,083✔
1030
                        DISPATCH();
23,905✔
1031
                    }
852✔
1032

1033
                    TARGET(SHORTCIRCUIT_OR)
1034
                    {
1035
                        if (!!*peekAndResolveAsPtr(context))
852✔
1036
                            jump(arg, context);
219✔
1037
                        else
1038
                            pop(context);
633✔
1039
                        DISPATCH();
852✔
1040
                    }
3,128✔
1041

1042
                    TARGET(CREATE_SCOPE)
1043
                    {
1044
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
3,128✔
1045
                        DISPATCH();
3,128✔
1046
                    }
33,125✔
1047

1048
                    TARGET(RESET_SCOPE_JUMP)
1049
                    {
1050
                        context.locals.back().reset();
33,125✔
1051
                        jump(arg, context);
33,125✔
1052
                        DISPATCH();
33,125✔
1053
                    }
3,127✔
1054

1055
                    TARGET(POP_SCOPE)
1056
                    {
1057
                        context.locals.pop_back();
3,127✔
1058
                        DISPATCH();
3,127✔
1059
                    }
×
1060

1061
                    TARGET(GET_CURRENT_PAGE_ADDR)
1062
                    {
1063
                        context.last_symbol = arg;
×
1064
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1065
                        DISPATCH();
×
1066
                    }
184✔
1067

1068
                    TARGET(APPLY)
1069
                    {
1070
                        {
1071
                            const Value args_list = *popAndResolveAsPtr(context),
184✔
1072
                                        func = *popAndResolveAsPtr(context);
184✔
1073
                            if (args_list.valueType() != ValueType::List || !func.isFunction())
184✔
1074
                            {
1075
                                throw types::TypeCheckingError(
4✔
1076
                                    "apply",
2✔
1077
                                    { {
8✔
1078
                                        types::Contract {
2✔
1079
                                            { types::Typedef("func", ValueType::PageAddr),
4✔
1080
                                              types::Typedef("args", ValueType::List) } },
2✔
1081
                                        types::Contract {
2✔
1082
                                            { types::Typedef("func", ValueType::Closure),
4✔
1083
                                              types::Typedef("args", ValueType::List) } },
2✔
1084
                                        types::Contract {
2✔
1085
                                            { types::Typedef("func", ValueType::CProc),
4✔
1086
                                              types::Typedef("args", ValueType::List) } },
2✔
1087
                                    } },
1088
                                    { func, args_list });
2✔
NEW
1089
                            }
×
1090

1091
                            for (const Value& a : args_list.constList() | std::ranges::views::reverse)
485✔
1092
                                push(a, context);
303✔
1093
                            push(func, context);
182✔
1094

1095
                            call(context, static_cast<uint16_t>(args_list.constList().size()));
182✔
1096
                        }
184✔
1097
                        DISPATCH();
145✔
1098
                    }
21✔
1099

1100
#pragma endregion
1101

1102
#pragma region "Operators"
1103

1104
                    TARGET(BREAKPOINT)
1105
                    {
1106
                        {
1107
                            bool breakpoint_active = true;
21✔
1108
                            if (arg == 1)
21✔
1109
                                breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym;
19✔
1110

1111
                            if (m_state.m_features & FeatureVMDebugger && breakpoint_active)
21✔
1112
                            {
1113
                                initDebugger(context);
9✔
1114
                                m_debugger->run(*this, context, /* from_breakpoint= */ true);
9✔
1115
                                m_debugger->resetContextToSavedState(context);
9✔
1116

1117
                                if (m_debugger->shouldQuitVM())
9✔
1118
                                    GOTO_HALT();
1✔
1119
                            }
8✔
1120
                        }
1121
                        DISPATCH();
20✔
1122
                    }
28,246✔
1123

1124
                    TARGET(ADD)
1125
                    {
1126
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,246✔
1127

1128
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
28,246✔
1129
                            push(Value(a->number() + b->number()), context);
19,584✔
1130
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
8,662✔
1131
                            push(Value(a->string() + b->string()), context);
8,661✔
1132
                        else
1133
                            throw types::TypeCheckingError(
2✔
1134
                                "+",
1✔
1135
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1136
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1137
                                { *a, *b });
1✔
1138
                        DISPATCH();
28,245✔
1139
                    }
382✔
1140

1141
                    TARGET(SUB)
1142
                    {
1143
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
382✔
1144

1145
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
382✔
1146
                            throw types::TypeCheckingError(
2✔
1147
                                "-",
1✔
1148
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1149
                                { *a, *b });
1✔
1150
                        push(Value(a->number() - b->number()), context);
381✔
1151
                        DISPATCH();
381✔
1152
                    }
828✔
1153

1154
                    TARGET(MUL)
1155
                    {
1156
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
828✔
1157

1158
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
828✔
1159
                            throw types::TypeCheckingError(
2✔
1160
                                "*",
1✔
1161
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1162
                                { *a, *b });
1✔
1163
                        push(Value(a->number() * b->number()), context);
827✔
1164
                        DISPATCH();
827✔
1165
                    }
144✔
1166

1167
                    TARGET(DIV)
1168
                    {
1169
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
144✔
1170

1171
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
144✔
1172
                            throw types::TypeCheckingError(
2✔
1173
                                "/",
1✔
1174
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1175
                                { *a, *b });
1✔
1176
                        auto d = b->number();
143✔
1177
                        if (d == 0)
143✔
1178
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1179

1180
                        push(Value(a->number() / d), context);
142✔
1181
                        DISPATCH();
142✔
1182
                    }
209✔
1183

1184
                    TARGET(GT)
1185
                    {
1186
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
209✔
1187
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
209✔
1188
                        DISPATCH();
209✔
1189
                    }
21,011✔
1190

1191
                    TARGET(LT)
1192
                    {
1193
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
21,011✔
1194
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
21,011✔
1195
                        DISPATCH();
21,011✔
1196
                    }
7,297✔
1197

1198
                    TARGET(LE)
1199
                    {
1200
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,297✔
1201
                        push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
7,297✔
1202
                        DISPATCH();
7,297✔
1203
                    }
5,935✔
1204

1205
                    TARGET(GE)
1206
                    {
1207
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,935✔
1208
                        push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,935✔
1209
                        DISPATCH();
5,935✔
1210
                    }
1,249✔
1211

1212
                    TARGET(NEQ)
1213
                    {
1214
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,249✔
1215
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1,249✔
1216
                        DISPATCH();
1,249✔
1217
                    }
18,394✔
1218

1219
                    TARGET(EQ)
1220
                    {
1221
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
18,394✔
1222
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
18,394✔
1223
                        DISPATCH();
18,394✔
1224
                    }
3,995✔
1225

1226
                    TARGET(LEN)
1227
                    {
1228
                        const Value* a = popAndResolveAsPtr(context);
3,995✔
1229

1230
                        if (a->valueType() == ValueType::List)
3,995✔
1231
                            push(Value(static_cast<int>(a->constList().size())), context);
1,580✔
1232
                        else if (a->valueType() == ValueType::String)
2,415✔
1233
                            push(Value(static_cast<int>(a->string().size())), context);
2,409✔
1234
                        else if (a->valueType() == ValueType::Dict)
6✔
1235
                            push(Value(static_cast<int>(a->dict().size())), context);
5✔
1236
                        else
1237
                            throw types::TypeCheckingError(
2✔
1238
                                "len",
1✔
1239
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
3✔
1240
                                    types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1241
                                    types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1242
                                { *a });
1✔
1243
                        DISPATCH();
3,994✔
1244
                    }
631✔
1245

1246
                    TARGET(IS_EMPTY)
1247
                    {
1248
                        const Value* a = popAndResolveAsPtr(context);
631✔
1249

1250
                        if (a->valueType() == ValueType::List)
631✔
1251
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
126✔
1252
                        else if (a->valueType() == ValueType::String)
505✔
1253
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
500✔
1254
                        else if (a->valueType() == ValueType::Dict)
5✔
1255
                            push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context);
4✔
1256
                        else if (a->valueType() == ValueType::Nil)
1✔
1257
                            push(Builtins::trueSym, context);
×
1258
                        else
1259
                            throw types::TypeCheckingError(
2✔
1260
                                "empty?",
1✔
1261
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
4✔
1262
                                    types::Contract { { types::Typedef("value", ValueType::Nil) } },
1✔
1263
                                    types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1264
                                    types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1265
                                { *a });
1✔
1266
                        DISPATCH();
630✔
1267
                    }
337✔
1268

1269
                    TARGET(TAIL)
1270
                    {
1271
                        Value* const a = popAndResolveAsPtr(context);
337✔
1272
                        push(helper::tail(a), context);
337✔
1273
                        DISPATCH();
336✔
1274
                    }
1,130✔
1275

1276
                    TARGET(HEAD)
1277
                    {
1278
                        Value* const a = popAndResolveAsPtr(context);
1,130✔
1279
                        push(helper::head(a), context);
1,130✔
1280
                        DISPATCH();
1,129✔
1281
                    }
2,394✔
1282

1283
                    TARGET(IS_NIL)
1284
                    {
1285
                        const Value* a = popAndResolveAsPtr(context);
2,394✔
1286
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,394✔
1287
                        DISPATCH();
2,394✔
1288
                    }
17✔
1289

1290
                    TARGET(TO_NUM)
1291
                    {
1292
                        const Value* a = popAndResolveAsPtr(context);
17✔
1293

1294
                        if (a->valueType() != ValueType::String)
17✔
1295
                            throw types::TypeCheckingError(
2✔
1296
                                "toNumber",
1✔
1297
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1298
                                { *a });
1✔
1299

1300
                        double val;
1301
                        if (Utils::isDouble(a->string(), &val))
16✔
1302
                            push(Value(val), context);
12✔
1303
                        else
1304
                            push(Builtins::nil, context);
4✔
1305
                        DISPATCH();
16✔
1306
                    }
161✔
1307

1308
                    TARGET(TO_STR)
1309
                    {
1310
                        const Value* a = popAndResolveAsPtr(context);
161✔
1311
                        push(Value(a->toString(*this)), context);
161✔
1312
                        DISPATCH();
161✔
1313
                    }
189✔
1314

1315
                    TARGET(AT)
1316
                    {
1317
                        Value& b = *popAndResolveAsPtr(context);
189✔
1318
                        Value& a = *popAndResolveAsPtr(context);
189✔
1319
                        push(helper::at(a, b, *this), context);
189✔
1320
                        DISPATCH();
187✔
1321
                    }
78✔
1322

1323
                    TARGET(AT_AT)
1324
                    {
1325
                        {
1326
                            const Value* x = popAndResolveAsPtr(context);
78✔
1327
                            const Value* y = popAndResolveAsPtr(context);
78✔
1328
                            Value& list = *popAndResolveAsPtr(context);
78✔
1329

1330
                            push(helper::atAt(x, y, list), context);
78✔
1331
                        }
1332
                        DISPATCH();
73✔
1333
                    }
16,408✔
1334

1335
                    TARGET(MOD)
1336
                    {
1337
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
16,408✔
1338
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
16,408✔
1339
                            throw types::TypeCheckingError(
2✔
1340
                                "mod",
1✔
1341
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1342
                                { *a, *b });
1✔
1343
                        push(Value(std::fmod(a->number(), b->number())), context);
16,407✔
1344
                        DISPATCH();
16,407✔
1345
                    }
30✔
1346

1347
                    TARGET(TYPE)
1348
                    {
1349
                        const Value* a = popAndResolveAsPtr(context);
30✔
1350
                        push(Value(std::to_string(a->valueType())), context);
30✔
1351
                        DISPATCH();
30✔
1352
                    }
3✔
1353

1354
                    TARGET(HAS_FIELD)
1355
                    {
1356
                        {
1357
                            Value* const field = popAndResolveAsPtr(context);
3✔
1358
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1359
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1360
                                throw types::TypeCheckingError(
2✔
1361
                                    "hasField",
1✔
1362
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1363
                                    { *closure, *field });
1✔
1364

1365
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1366
                            if (it == m_state.m_symbols.end())
2✔
1367
                            {
1368
                                push(Builtins::falseSym, context);
1✔
1369
                                DISPATCH();
1✔
1370
                            }
1371

1372
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1373
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1374
                        }
1375
                        DISPATCH();
1✔
1376
                    }
3,711✔
1377

1378
                    TARGET(NOT)
1379
                    {
1380
                        const Value* a = popAndResolveAsPtr(context);
3,711✔
1381
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,711✔
1382
                        DISPATCH();
3,711✔
1383
                    }
8,490✔
1384

1385
#pragma endregion
1386

1387
#pragma region "Super Instructions"
1388
                    TARGET(LOAD_CONST_LOAD_CONST)
1389
                    {
1390
                        UNPACK_ARGS();
8,490✔
1391
                        push(loadConstAsPtr(primary_arg), context);
8,490✔
1392
                        push(loadConstAsPtr(secondary_arg), context);
8,490✔
1393
                        context.inst_exec_counter++;
8,490✔
1394
                        DISPATCH();
8,490✔
1395
                    }
10,038✔
1396

1397
                    TARGET(LOAD_CONST_STORE)
1398
                    {
1399
                        UNPACK_ARGS();
10,038✔
1400
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
10,038✔
1401
                        DISPATCH();
10,038✔
1402
                    }
976✔
1403

1404
                    TARGET(LOAD_CONST_SET_VAL)
1405
                    {
1406
                        UNPACK_ARGS();
976✔
1407
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
976✔
1408
                        DISPATCH();
975✔
1409
                    }
25✔
1410

1411
                    TARGET(STORE_FROM)
1412
                    {
1413
                        UNPACK_ARGS();
25✔
1414
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
25✔
1415
                        DISPATCH();
24✔
1416
                    }
1,223✔
1417

1418
                    TARGET(STORE_FROM_INDEX)
1419
                    {
1420
                        UNPACK_ARGS();
1,223✔
1421
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,223✔
1422
                        DISPATCH();
1,223✔
1423
                    }
627✔
1424

1425
                    TARGET(SET_VAL_FROM)
1426
                    {
1427
                        UNPACK_ARGS();
627✔
1428
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
627✔
1429
                        DISPATCH();
627✔
1430
                    }
603✔
1431

1432
                    TARGET(SET_VAL_FROM_INDEX)
1433
                    {
1434
                        UNPACK_ARGS();
603✔
1435
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
603✔
1436
                        DISPATCH();
603✔
1437
                    }
68✔
1438

1439
                    TARGET(INCREMENT)
1440
                    {
1441
                        UNPACK_ARGS();
68✔
1442
                        {
1443
                            Value* var = loadSymbol(primary_arg, context);
68✔
1444

1445
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1446
                            if (var->valueType() == ValueType::Reference)
68✔
1447
                                var = var->reference();
×
1448

1449
                            if (var->valueType() == ValueType::Number)
68✔
1450
                                push(Value(var->number() + secondary_arg), context);
67✔
1451
                            else
1452
                                throw types::TypeCheckingError(
2✔
1453
                                    "+",
1✔
1454
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1455
                                    { *var, Value(secondary_arg) });
1✔
1456
                        }
1457
                        DISPATCH();
67✔
1458
                    }
88,028✔
1459

1460
                    TARGET(INCREMENT_BY_INDEX)
1461
                    {
1462
                        UNPACK_ARGS();
88,028✔
1463
                        {
1464
                            Value* var = loadSymbolFromIndex(primary_arg, context);
88,028✔
1465

1466
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1467
                            if (var->valueType() == ValueType::Reference)
88,028✔
1468
                                var = var->reference();
×
1469

1470
                            if (var->valueType() == ValueType::Number)
88,028✔
1471
                                push(Value(var->number() + secondary_arg), context);
88,027✔
1472
                            else
1473
                                throw types::TypeCheckingError(
2✔
1474
                                    "+",
1✔
1475
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1476
                                    { *var, Value(secondary_arg) });
1✔
1477
                        }
1478
                        DISPATCH();
88,027✔
1479
                    }
33,354✔
1480

1481
                    TARGET(INCREMENT_STORE)
1482
                    {
1483
                        UNPACK_ARGS();
33,354✔
1484
                        {
1485
                            Value* var = loadSymbol(primary_arg, context);
33,354✔
1486

1487
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1488
                            if (var->valueType() == ValueType::Reference)
33,354✔
1489
                                var = var->reference();
×
1490

1491
                            if (var->valueType() == ValueType::Number)
33,354✔
1492
                            {
1493
                                auto val = Value(var->number() + secondary_arg);
33,353✔
1494
                                setVal(primary_arg, &val, context);
33,353✔
1495
                            }
33,353✔
1496
                            else
1497
                                throw types::TypeCheckingError(
2✔
1498
                                    "+",
1✔
1499
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1500
                                    { *var, Value(secondary_arg) });
1✔
1501
                        }
1502
                        DISPATCH();
33,353✔
1503
                    }
1,854✔
1504

1505
                    TARGET(DECREMENT)
1506
                    {
1507
                        UNPACK_ARGS();
1,854✔
1508
                        {
1509
                            Value* var = loadSymbol(primary_arg, context);
1,854✔
1510

1511
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1512
                            if (var->valueType() == ValueType::Reference)
1,854✔
1513
                                var = var->reference();
×
1514

1515
                            if (var->valueType() == ValueType::Number)
1,854✔
1516
                                push(Value(var->number() - secondary_arg), context);
1,853✔
1517
                            else
1518
                                throw types::TypeCheckingError(
2✔
1519
                                    "-",
1✔
1520
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1521
                                    { *var, Value(secondary_arg) });
1✔
1522
                        }
1523
                        DISPATCH();
1,853✔
1524
                    }
194,414✔
1525

1526
                    TARGET(DECREMENT_BY_INDEX)
1527
                    {
1528
                        UNPACK_ARGS();
194,414✔
1529
                        {
1530
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,414✔
1531

1532
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1533
                            if (var->valueType() == ValueType::Reference)
194,414✔
1534
                                var = var->reference();
×
1535

1536
                            if (var->valueType() == ValueType::Number)
194,414✔
1537
                                push(Value(var->number() - secondary_arg), context);
194,413✔
1538
                            else
1539
                                throw types::TypeCheckingError(
2✔
1540
                                    "-",
1✔
1541
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1542
                                    { *var, Value(secondary_arg) });
1✔
1543
                        }
1544
                        DISPATCH();
194,413✔
1545
                    }
866✔
1546

1547
                    TARGET(DECREMENT_STORE)
1548
                    {
1549
                        UNPACK_ARGS();
866✔
1550
                        {
1551
                            Value* var = loadSymbol(primary_arg, context);
866✔
1552

1553
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1554
                            if (var->valueType() == ValueType::Reference)
866✔
1555
                                var = var->reference();
×
1556

1557
                            if (var->valueType() == ValueType::Number)
866✔
1558
                            {
1559
                                auto val = Value(var->number() - secondary_arg);
865✔
1560
                                setVal(primary_arg, &val, context);
865✔
1561
                            }
865✔
1562
                            else
1563
                                throw types::TypeCheckingError(
2✔
1564
                                    "-",
1✔
1565
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1566
                                    { *var, Value(secondary_arg) });
1✔
1567
                        }
1568
                        DISPATCH();
865✔
1569
                    }
1✔
1570

1571
                    TARGET(STORE_TAIL)
1572
                    {
1573
                        UNPACK_ARGS();
1✔
1574
                        {
1575
                            Value* list = loadSymbol(primary_arg, context);
1✔
1576
                            Value tail = helper::tail(list);
1✔
1577
                            store(secondary_arg, &tail, context);
1✔
1578
                        }
1✔
1579
                        DISPATCH();
1✔
1580
                    }
8✔
1581

1582
                    TARGET(STORE_TAIL_BY_INDEX)
1583
                    {
1584
                        UNPACK_ARGS();
8✔
1585
                        {
1586
                            Value* list = loadSymbolFromIndex(primary_arg, context);
8✔
1587
                            Value tail = helper::tail(list);
8✔
1588
                            store(secondary_arg, &tail, context);
8✔
1589
                        }
8✔
1590
                        DISPATCH();
8✔
1591
                    }
4✔
1592

1593
                    TARGET(STORE_HEAD)
1594
                    {
1595
                        UNPACK_ARGS();
4✔
1596
                        {
1597
                            Value* list = loadSymbol(primary_arg, context);
4✔
1598
                            Value head = helper::head(list);
4✔
1599
                            store(secondary_arg, &head, context);
4✔
1600
                        }
4✔
1601
                        DISPATCH();
4✔
1602
                    }
38✔
1603

1604
                    TARGET(STORE_HEAD_BY_INDEX)
1605
                    {
1606
                        UNPACK_ARGS();
38✔
1607
                        {
1608
                            Value* list = loadSymbolFromIndex(primary_arg, context);
38✔
1609
                            Value head = helper::head(list);
38✔
1610
                            store(secondary_arg, &head, context);
38✔
1611
                        }
38✔
1612
                        DISPATCH();
38✔
1613
                    }
1,015✔
1614

1615
                    TARGET(STORE_LIST)
1616
                    {
1617
                        UNPACK_ARGS();
1,015✔
1618
                        {
1619
                            Value l = createList(primary_arg, context);
1,015✔
1620
                            store(secondary_arg, &l, context);
1,015✔
1621
                        }
1,015✔
1622
                        DISPATCH();
1,015✔
1623
                    }
3✔
1624

1625
                    TARGET(SET_VAL_TAIL)
1626
                    {
1627
                        UNPACK_ARGS();
3✔
1628
                        {
1629
                            Value* list = loadSymbol(primary_arg, context);
3✔
1630
                            Value tail = helper::tail(list);
3✔
1631
                            setVal(secondary_arg, &tail, context);
3✔
1632
                        }
3✔
1633
                        DISPATCH();
3✔
1634
                    }
1✔
1635

1636
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1637
                    {
1638
                        UNPACK_ARGS();
1✔
1639
                        {
1640
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1641
                            Value tail = helper::tail(list);
1✔
1642
                            setVal(secondary_arg, &tail, context);
1✔
1643
                        }
1✔
1644
                        DISPATCH();
1✔
1645
                    }
1✔
1646

1647
                    TARGET(SET_VAL_HEAD)
1648
                    {
1649
                        UNPACK_ARGS();
1✔
1650
                        {
1651
                            Value* list = loadSymbol(primary_arg, context);
1✔
1652
                            Value head = helper::head(list);
1✔
1653
                            setVal(secondary_arg, &head, context);
1✔
1654
                        }
1✔
1655
                        DISPATCH();
1✔
1656
                    }
1✔
1657

1658
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1659
                    {
1660
                        UNPACK_ARGS();
1✔
1661
                        {
1662
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1663
                            Value head = helper::head(list);
1✔
1664
                            setVal(secondary_arg, &head, context);
1✔
1665
                        }
1✔
1666
                        DISPATCH();
1✔
1667
                    }
1,715✔
1668

1669
                    TARGET(CALL_BUILTIN)
1670
                    {
1671
                        UNPACK_ARGS();
1,715✔
1672
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1673
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
1,715✔
1674
                        if (!m_running)
1,650✔
1675
                            GOTO_HALT();
×
1676
                        DISPATCH();
1,650✔
1677
                    }
11,709✔
1678

1679
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1680
                    {
1681
                        UNPACK_ARGS();
11,709✔
1682
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1683
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,709✔
1684
                        if (!m_running)
11,708✔
1685
                            GOTO_HALT();
×
1686
                        DISPATCH();
11,708✔
1687
                    }
877✔
1688

1689
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1690
                    {
1691
                        UNPACK_ARGS();
877✔
1692
                        const Value* sym = popAndResolveAsPtr(context);
877✔
1693
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
877✔
1694
                            jump(secondary_arg, context);
124✔
1695
                        DISPATCH();
877✔
1696
                    }
21,988✔
1697

1698
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1699
                    {
1700
                        UNPACK_ARGS();
21,988✔
1701
                        const Value* sym = popAndResolveAsPtr(context);
21,988✔
1702
                        if (*sym < *loadConstAsPtr(primary_arg))
21,988✔
1703
                            jump(secondary_arg, context);
10,960✔
1704
                        DISPATCH();
21,988✔
1705
                    }
6,919✔
1706

1707
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1708
                    {
1709
                        UNPACK_ARGS();
6,919✔
1710
                        const Value* sym = popAndResolveAsPtr(context);
6,919✔
1711
                        if (!(*sym < *loadSymbol(primary_arg, context)))
6,919✔
1712
                            jump(secondary_arg, context);
670✔
1713
                        DISPATCH();
6,919✔
1714
                    }
172,508✔
1715

1716
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1717
                    {
1718
                        UNPACK_ARGS();
172,508✔
1719
                        const Value* sym = popAndResolveAsPtr(context);
172,508✔
1720
                        const Value* cst = loadConstAsPtr(primary_arg);
172,508✔
1721
                        if (*cst < *sym)
172,508✔
1722
                            jump(secondary_arg, context);
86,590✔
1723
                        DISPATCH();
172,508✔
1724
                    }
187✔
1725

1726
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1727
                    {
1728
                        UNPACK_ARGS();
187✔
1729
                        const Value* sym = popAndResolveAsPtr(context);
187✔
1730
                        const Value* cst = loadConstAsPtr(primary_arg);
187✔
1731
                        if (!(*cst < *sym))
187✔
1732
                            jump(secondary_arg, context);
42✔
1733
                        DISPATCH();
187✔
1734
                    }
6✔
1735

1736
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1737
                    {
1738
                        UNPACK_ARGS();
6✔
1739
                        const Value* sym = popAndResolveAsPtr(context);
6✔
1740
                        const Value* rhs = loadSymbol(primary_arg, context);
6✔
1741
                        if (!(*rhs < *sym))
6✔
1742
                            jump(secondary_arg, context);
1✔
1743
                        DISPATCH();
6✔
1744
                    }
1,099✔
1745

1746
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1747
                    {
1748
                        UNPACK_ARGS();
1,099✔
1749
                        const Value* sym = popAndResolveAsPtr(context);
1,099✔
1750
                        if (*sym == *loadConstAsPtr(primary_arg))
1,099✔
1751
                            jump(secondary_arg, context);
41✔
1752
                        DISPATCH();
1,099✔
1753
                    }
87,351✔
1754

1755
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1756
                    {
1757
                        UNPACK_ARGS();
87,351✔
1758
                        const Value* sym = popAndResolveAsPtr(context);
87,351✔
1759
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
87,351✔
1760
                            jump(secondary_arg, context);
548✔
1761
                        DISPATCH();
87,351✔
1762
                    }
11✔
1763

1764
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1765
                    {
1766
                        UNPACK_ARGS();
11✔
1767
                        const Value* sym = popAndResolveAsPtr(context);
11✔
1768
                        if (*sym != *loadConstAsPtr(primary_arg))
11✔
1769
                            jump(secondary_arg, context);
2✔
1770
                        DISPATCH();
11✔
1771
                    }
30✔
1772

1773
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1774
                    {
1775
                        UNPACK_ARGS();
30✔
1776
                        const Value* sym = popAndResolveAsPtr(context);
30✔
1777
                        if (*sym == *loadSymbol(primary_arg, context))
30✔
1778
                            jump(secondary_arg, context);
10✔
1779
                        DISPATCH();
30✔
1780
                    }
28,127✔
1781

1782
                    TARGET(CALL_SYMBOL)
1783
                    {
1784
                        UNPACK_ARGS();
28,127✔
1785
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
28,127✔
1786
                        if (!m_running)
28,125✔
1787
                            GOTO_HALT();
×
1788
                        DISPATCH();
28,125✔
1789
                    }
109,875✔
1790

1791
                    TARGET(CALL_CURRENT_PAGE)
1792
                    {
1793
                        UNPACK_ARGS();
109,875✔
1794
                        context.last_symbol = primary_arg;
109,875✔
1795
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,875✔
1796
                        if (!m_running)
109,874✔
1797
                            GOTO_HALT();
×
1798
                        DISPATCH();
109,874✔
1799
                    }
3,393✔
1800

1801
                    TARGET(GET_FIELD_FROM_SYMBOL)
1802
                    {
1803
                        UNPACK_ARGS();
3,393✔
1804
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
3,393✔
1805
                        DISPATCH();
3,393✔
1806
                    }
843✔
1807

1808
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1809
                    {
1810
                        UNPACK_ARGS();
843✔
1811
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
843✔
1812
                        DISPATCH();
841✔
1813
                    }
16,138✔
1814

1815
                    TARGET(AT_SYM_SYM)
1816
                    {
1817
                        UNPACK_ARGS();
16,138✔
1818
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
16,138✔
1819
                        DISPATCH();
16,138✔
1820
                    }
49✔
1821

1822
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1823
                    {
1824
                        UNPACK_ARGS();
49✔
1825
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
49✔
1826
                        DISPATCH();
49✔
1827
                    }
1,048✔
1828

1829
                    TARGET(AT_SYM_INDEX_CONST)
1830
                    {
1831
                        UNPACK_ARGS();
1,048✔
1832
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
1,048✔
1833
                        DISPATCH();
1,045✔
1834
                    }
2✔
1835

1836
                    TARGET(CHECK_TYPE_OF)
1837
                    {
1838
                        UNPACK_ARGS();
2✔
1839
                        const Value* sym = loadSymbol(primary_arg, context);
2✔
1840
                        const Value* cst = loadConstAsPtr(secondary_arg);
2✔
1841
                        push(
2✔
1842
                            cst->valueType() == ValueType::String &&
4✔
1843
                                    std::to_string(sym->valueType()) == cst->string()
2✔
1844
                                ? Builtins::trueSym
1845
                                : Builtins::falseSym,
1846
                            context);
2✔
1847
                        DISPATCH();
2✔
1848
                    }
130✔
1849

1850
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1851
                    {
1852
                        UNPACK_ARGS();
130✔
1853
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
130✔
1854
                        const Value* cst = loadConstAsPtr(secondary_arg);
130✔
1855
                        push(
130✔
1856
                            cst->valueType() == ValueType::String &&
260✔
1857
                                    std::to_string(sym->valueType()) == cst->string()
130✔
1858
                                ? Builtins::trueSym
1859
                                : Builtins::falseSym,
1860
                            context);
130✔
1861
                        DISPATCH();
130✔
1862
                    }
3,486✔
1863

1864
                    TARGET(APPEND_IN_PLACE_SYM)
1865
                    {
1866
                        UNPACK_ARGS();
3,486✔
1867
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
3,486✔
1868
                        DISPATCH();
3,486✔
1869
                    }
14✔
1870

1871
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1872
                    {
1873
                        UNPACK_ARGS();
14✔
1874
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
14✔
1875
                        DISPATCH();
13✔
1876
                    }
123✔
1877

1878
                    TARGET(STORE_LEN)
1879
                    {
1880
                        UNPACK_ARGS();
123✔
1881
                        {
1882
                            Value* a = loadSymbolFromIndex(primary_arg, context);
123✔
1883
                            Value len;
123✔
1884
                            if (a->valueType() == ValueType::List)
123✔
1885
                                len = Value(static_cast<int>(a->constList().size()));
43✔
1886
                            else if (a->valueType() == ValueType::String)
80✔
1887
                                len = Value(static_cast<int>(a->string().size()));
79✔
1888
                            else
1889
                                throw types::TypeCheckingError(
2✔
1890
                                    "len",
1✔
1891
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1892
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1893
                                    { *a });
1✔
1894
                            store(secondary_arg, &len, context);
122✔
1895
                        }
123✔
1896
                        DISPATCH();
122✔
1897
                    }
9,247✔
1898

1899
                    TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
1900
                    {
1901
                        UNPACK_ARGS();
9,247✔
1902
                        {
1903
                            const Value* sym = loadSymbol(primary_arg, context);
9,247✔
1904
                            Value size;
9,247✔
1905

1906
                            if (sym->valueType() == ValueType::List)
9,247✔
1907
                                size = Value(static_cast<int>(sym->constList().size()));
3,576✔
1908
                            else if (sym->valueType() == ValueType::String)
5,671✔
1909
                                size = Value(static_cast<int>(sym->string().size()));
5,670✔
1910
                            else
1911
                                throw types::TypeCheckingError(
2✔
1912
                                    "len",
1✔
1913
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1914
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1915
                                    { *sym });
1✔
1916

1917
                            if (!(*popAndResolveAsPtr(context) < size))
9,246✔
1918
                                jump(secondary_arg, context);
1,213✔
1919
                        }
9,247✔
1920
                        DISPATCH();
9,246✔
1921
                    }
521✔
1922

1923
                    TARGET(MUL_BY)
1924
                    {
1925
                        UNPACK_ARGS();
521✔
1926
                        {
1927
                            Value* var = loadSymbol(primary_arg, context);
521✔
1928
                            const int other = static_cast<int>(secondary_arg) - 2048;
521✔
1929

1930
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1931
                            if (var->valueType() == ValueType::Reference)
521✔
1932
                                var = var->reference();
×
1933

1934
                            if (var->valueType() == ValueType::Number)
521✔
1935
                                push(Value(var->number() * other), context);
520✔
1936
                            else
1937
                                throw types::TypeCheckingError(
2✔
1938
                                    "*",
1✔
1939
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1940
                                    { *var, Value(other) });
1✔
1941
                        }
1942
                        DISPATCH();
520✔
1943
                    }
36✔
1944

1945
                    TARGET(MUL_BY_INDEX)
1946
                    {
1947
                        UNPACK_ARGS();
36✔
1948
                        {
1949
                            Value* var = loadSymbolFromIndex(primary_arg, context);
36✔
1950
                            const int other = static_cast<int>(secondary_arg) - 2048;
36✔
1951

1952
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1953
                            if (var->valueType() == ValueType::Reference)
36✔
1954
                                var = var->reference();
×
1955

1956
                            if (var->valueType() == ValueType::Number)
36✔
1957
                                push(Value(var->number() * other), context);
35✔
1958
                            else
1959
                                throw types::TypeCheckingError(
2✔
1960
                                    "*",
1✔
1961
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1962
                                    { *var, Value(other) });
1✔
1963
                        }
1964
                        DISPATCH();
35✔
1965
                    }
2✔
1966

1967
                    TARGET(MUL_SET_VAL)
1968
                    {
1969
                        UNPACK_ARGS();
2✔
1970
                        {
1971
                            Value* var = loadSymbol(primary_arg, context);
2✔
1972
                            const int other = static_cast<int>(secondary_arg) - 2048;
2✔
1973

1974
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1975
                            if (var->valueType() == ValueType::Reference)
2✔
1976
                                var = var->reference();
×
1977

1978
                            if (var->valueType() == ValueType::Number)
2✔
1979
                            {
1980
                                auto val = Value(var->number() * other);
1✔
1981
                                setVal(primary_arg, &val, context);
1✔
1982
                            }
1✔
1983
                            else
1984
                                throw types::TypeCheckingError(
2✔
1985
                                    "*",
1✔
1986
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1987
                                    { *var, Value(other) });
1✔
1988
                        }
1989
                        DISPATCH();
1✔
1990
                    }
1,110✔
1991

1992
                    TARGET(FUSED_MATH)
1993
                    {
1994
                        const auto op1 = static_cast<Instruction>(padding),
1,110✔
1995
                                   op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
1,110✔
1996
                                   op3 = static_cast<Instruction>(arg & 0x00ff);
1,110✔
1997
                        const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
1,110✔
1998

1999
                        const Value* d = popAndResolveAsPtr(context);
1,110✔
2000
                        const Value* c = popAndResolveAsPtr(context);
1,110✔
2001
                        const Value* b = popAndResolveAsPtr(context);
1,110✔
2002

2003
                        if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
1,110✔
2004
                            throw types::TypeCheckingError(
2✔
2005
                                helper::mathInstToStr(op1),
1✔
2006
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2007
                                { *c, *d });
1✔
2008

2009
                        double temp = helper::doMath(c->number(), d->number(), op1);
1,109✔
2010
                        if (b->valueType() != ValueType::Number)
1,109✔
2011
                            throw types::TypeCheckingError(
4✔
2012
                                helper::mathInstToStr(op2),
2✔
2013
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
2✔
2014
                                { *b, Value(temp) });
2✔
2015
                        temp = helper::doMath(b->number(), temp, op2);
1,107✔
2016

2017
                        if (arg_count == 2)
1,106✔
2018
                            push(Value(temp), context);
1,069✔
2019
                        else if (arg_count == 3)
37✔
2020
                        {
2021
                            const Value* a = popAndResolveAsPtr(context);
37✔
2022
                            if (a->valueType() != ValueType::Number)
37✔
2023
                                throw types::TypeCheckingError(
2✔
2024
                                    helper::mathInstToStr(op3),
1✔
2025
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2026
                                    { *a, Value(temp) });
1✔
2027

2028
                            temp = helper::doMath(a->number(), temp, op3);
36✔
2029
                            push(Value(temp), context);
36✔
2030
                        }
36✔
2031
                        else
2032
                            throw Error(
×
2033
                                fmt::format(
×
2034
                                    "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
×
2035
                                    arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
×
2036
                        DISPATCH();
1,105✔
2037
                    }
2038
#pragma endregion
2039
                }
107✔
2040
#if ARK_USE_COMPUTED_GOTOS
2041
            dispatch_end:
2042
                do
107✔
2043
                {
2044
                } while (false);
107✔
2045
#endif
2046
            }
2047
        }
289✔
2048
        catch (const Error& e)
2049
        {
2050
            if (fail_with_exception)
136✔
2051
            {
2052
                std::stringstream stream;
135✔
2053
                backtrace(context, stream, /* colorize= */ false);
135✔
2054
                // It's important we have an Ark::Error here, as the constructor for NestedError
2055
                // does more than just aggregate error messages, hence the code duplication.
2056
                throw NestedError(e, stream.str(), *this);
135✔
2057
            }
135✔
2058
            else
2059
                showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context);
1✔
2060
        }
243✔
2061
        catch (const std::exception& e)
2062
        {
2063
            if (fail_with_exception)
46✔
2064
            {
2065
                std::stringstream stream;
46✔
2066
                backtrace(context, stream, /* colorize= */ false);
46✔
2067
                throw NestedError(e, stream.str());
46✔
2068
            }
46✔
2069
            else
2070
                showBacktraceWithException(e, context);
×
2071
        }
182✔
2072
        catch (...)
2073
        {
2074
            if (fail_with_exception)
×
2075
                throw;
×
2076

2077
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2078
            throw;
2079
#endif
2080
            fmt::println("Unknown error");
×
2081
            backtrace(context);
×
2082
            m_exit_code = 1;
×
2083
        }
227✔
2084

2085
        return m_exit_code;
108✔
2086
    }
363✔
2087

2088
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,056✔
2089
    {
2,056✔
2090
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,202✔
2091
        {
2092
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,146✔
2093
                return id;
2,050✔
2094
        }
2,096,146✔
2095
        return MaxValue16Bits;
6✔
2096
    }
2,056✔
2097

2098
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, ExecutionContext& context)
7✔
2099
    {
7✔
2100
        std::vector<std::string> arg_names;
7✔
2101
        arg_names.reserve(expected_arg_count + 1);
7✔
2102
        if (expected_arg_count > 0)
7✔
2103
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
6✔
2104

2105
        std::size_t index = 0;
7✔
2106
        while (m_state.inst(context.pp, index) == STORE ||
14✔
2107
               m_state.inst(context.pp, index) == STORE_REF)
7✔
2108
        {
2109
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
2110
            arg_names.push_back(m_state.m_symbols[id]);
×
2111
            index += 4;
×
2112
        }
×
2113
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2114
        if (arg_names.size() == 1 && index == 0)
7✔
2115
        {
2116
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
2117
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
2118
                arg_names.emplace_back(1, static_cast<char>('a' + i));
2✔
2119
        }
2✔
2120

2121
        std::vector<std::string> arg_vals;
7✔
2122
        arg_vals.reserve(passed_arg_count + 1);
7✔
2123
        if (passed_arg_count > 0)
7✔
2124
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
6✔
2125

2126
        for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.sp; ++i)
20✔
2127
            // -1 on the stack because we always point to the next available slot
2128
            arg_vals.push_back(context.stack[context.sp - i - 1].toString(*this));
13✔
2129

2130
        // set ip/pp to the callee location so that the error can pinpoint the line
2131
        // where the bad call happened
2132
        if (context.sp >= 2 + passed_arg_count)
7✔
2133
        {
2134
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
7✔
2135
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
7✔
2136
            context.sp -= 2;
7✔
2137
            returnFromFuncCall(context);
7✔
2138
        }
7✔
2139

2140
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
14✔
2141
            ? m_state.m_symbols[context.last_symbol]
7✔
2142
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2143

2144
        throwVMError(
7✔
2145
            ErrorKind::Arity,
2146
            fmt::format(
14✔
2147
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
7✔
2148
                function_name,
2149
                fmt::join(arg_vals, " "),
7✔
2150
                passed_arg_count,
2151
                passed_arg_count > 1 ? "s" : "",
7✔
2152
                expected_arg_count,
2153
                function_name,
2154
                fmt::join(arg_names, " ")));
7✔
2155
    }
14✔
2156

2157
    void VM::initDebugger(ExecutionContext& context)
10✔
2158
    {
10✔
2159
        if (!m_debugger)
10✔
2160
            m_debugger = std::make_unique<Debugger>(context, m_state.m_libenv, m_state.m_symbols, m_state.m_constants);
×
2161
        else
2162
            m_debugger->saveState(context);
10✔
2163
    }
10✔
2164

2165
    void VM::showBacktraceWithException(const std::exception& e, ExecutionContext& context)
1✔
2166
    {
1✔
2167
        std::string text = e.what();
1✔
2168
        if (!text.empty() && text.back() != '\n')
1✔
2169
            text += '\n';
×
2170
        fmt::println("{}", text);
1✔
2171

2172
        // If code being run from the debugger crashed, ignore it and don't trigger a debugger inside the VM inside the debugger inside the VM
2173
        const bool error_from_debugger = m_debugger && m_debugger->isRunning();
1✔
2174
        if (m_state.m_features & FeatureVMDebugger && !error_from_debugger)
1✔
2175
            initDebugger(context);
1✔
2176

2177
        const std::size_t saved_ip = context.ip;
1✔
2178
        const std::size_t saved_pp = context.pp;
1✔
2179
        const uint16_t saved_sp = context.sp;
1✔
2180

2181
        backtrace(context);
1✔
2182

2183
        fmt::println(
1✔
2184
            "At IP: {}, PP: {}, SP: {}",
1✔
2185
            // dividing by 4 because the instructions are actually on 4 bytes
2186
            fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)),
1✔
2187
            fmt::styled(saved_pp, fmt::fg(fmt::color::green)),
1✔
2188
            fmt::styled(saved_sp, fmt::fg(fmt::color::yellow)));
1✔
2189

2190
        if (m_debugger && !error_from_debugger)
1✔
2191
        {
2192
            m_debugger->resetContextToSavedState(context);
1✔
2193
            m_debugger->run(*this, context, /* from_breakpoint= */ false);
1✔
2194
        }
1✔
2195

2196
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2197
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2198
        m_exit_code = 0;
2199
#else
2200
        m_exit_code = 1;
1✔
2201
#endif
2202
    }
1✔
2203

2204
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,256✔
2205
    {
2,256✔
2206
        std::optional<InstLoc> match = std::nullopt;
2,256✔
2207

2208
        for (const auto location : m_state.m_inst_locations)
11,168✔
2209
        {
2210
            if (location.page_pointer == pp && !match)
8,912✔
2211
                match = location;
2,256✔
2212

2213
            // select the best match: we want to find the location that's nearest our instruction pointer,
2214
            // but not equal to it as the IP will always be pointing to the next instruction,
2215
            // not yet executed. Thus, the erroneous instruction is the previous one.
2216
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
8,912✔
2217
                match = location;
2,453✔
2218

2219
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2220
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
8,912✔
2221
                break;
2,084✔
2222
        }
8,912✔
2223

2224
        return match;
2,256✔
2225
    }
2226

2227
    std::string VM::debugShowSource() const
×
2228
    {
×
2229
        const auto& context = m_execution_contexts.front();
×
2230
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2231
        if (maybe_source_loc)
×
2232
        {
2233
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2234
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2235
        }
×
2236
        return "No source location found";
×
2237
    }
×
2238

2239
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
182✔
2240
    {
182✔
2241
        constexpr std::size_t max_consecutive_traces = 7;
182✔
2242

2243
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
182✔
2244
        if (maybe_location)
182✔
2245
        {
2246
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
182✔
2247

2248
            if (Utils::fileExists(filename))
182✔
2249
                Diagnostics::makeContext(
360✔
2250
                    Diagnostics::ErrorLocation {
360✔
2251
                        .filename = filename,
180✔
2252
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
180✔
2253
                        .end = std::nullopt,
180✔
2254
                        .maybe_content = std::nullopt },
180✔
2255
                    os,
180✔
2256
                    /* maybe_context= */ std::nullopt,
180✔
2257
                    /* colorize= */ colorize);
180✔
2258
            fmt::println(os, "");
182✔
2259
        }
182✔
2260

2261
        if (context.fc > 1)
182✔
2262
        {
2263
            // display call stack trace
2264
            const ScopeView old_scope = context.locals.back();
9✔
2265

2266
            std::string previous_trace;
9✔
2267
            std::size_t displayed_traces = 0;
9✔
2268
            std::size_t consecutive_similar_traces = 0;
9✔
2269

2270
            while (context.fc != 0 && context.pp != 0)
2,065✔
2271
            {
2272
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,056✔
2273
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
2,056✔
2274

2275
                const uint16_t id = findNearestVariableIdWithValue(
2,056✔
2276
                    Value(static_cast<PageAddr_t>(context.pp)),
2,056✔
2277
                    context);
2,056✔
2278
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,056✔
2279

2280
                if (func_name + loc_as_text != previous_trace)
2,056✔
2281
                {
2282
                    fmt::println(
20✔
2283
                        os,
10✔
2284
                        "[{:4}] In function `{}'{}",
10✔
2285
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
10✔
2286
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
10✔
2287
                        loc_as_text);
2288
                    previous_trace = func_name + loc_as_text;
10✔
2289
                    ++displayed_traces;
10✔
2290
                    consecutive_similar_traces = 0;
10✔
2291
                }
10✔
2292
                else if (consecutive_similar_traces == 0)
2,046✔
2293
                {
2294
                    fmt::println(os, "       ...");
1✔
2295
                    ++consecutive_similar_traces;
1✔
2296
                }
1✔
2297

2298
                const Value* ip;
2,056✔
2299
                do
6,261✔
2300
                {
2301
                    ip = popAndResolveAsPtr(context);
6,261✔
2302
                } while (ip->valueType() != ValueType::InstPtr);
6,261✔
2303

2304
                context.ip = ip->pageAddr();
2,056✔
2305
                context.pp = pop(context)->pageAddr();
2,056✔
2306
                returnFromFuncCall(context);
2,056✔
2307

2308
                if (displayed_traces > max_consecutive_traces)
2,056✔
2309
                {
2310
                    fmt::println(os, "       ...");
×
2311
                    break;
×
2312
                }
2313
            }
2,056✔
2314

2315
            if (context.pp == 0)
9✔
2316
            {
2317
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
9✔
2318
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
9✔
2319
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
9✔
2320
            }
9✔
2321

2322
            // display variables values in the current scope
2323
            fmt::println(os, "\nCurrent scope variables values:");
9✔
2324
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
10✔
2325
            {
2326
                fmt::println(
2✔
2327
                    os,
1✔
2328
                    "{} = {}",
1✔
2329
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2330
                    old_scope.atPos(i).second.toString(*this));
1✔
2331
            }
1✔
2332
        }
9✔
2333
    }
182✔
2334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc