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

ArkScript-lang / Ark / 28841712321

07 Jul 2026 04:32AM UTC coverage: 94.381% (+0.03%) from 94.349%
28841712321

push

github

SuperFola
chore(ci): ignore leaks when copying a ClosureScope

10313 of 10927 relevant lines covered (94.38%)

991593.76 hits per line

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

92.31
/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/ranges.h>
8
#include <fmt/ostream.h>
9

10
#include <Ark/Utils/Files.hpp>
11
#include <Ark/Utils/Utils.hpp>
12
#include <Ark/Error/Diagnostics.hpp>
13
#include <Ark/TypeChecker.hpp>
14
#include <Ark/VM/ModuleMapping.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 :
1,092✔
23
        m_state(state), m_exit_code(0), m_running(false)
364✔
24
    {
364✔
25
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
364✔
26
    }
364✔
27

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

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

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

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

48
        context.locals.clear();
358✔
49
        context.locals.reserve(64);
358✔
50
        context.locals.emplace_back(context.scopes_storage.data(), 0);
358✔
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)
1,119✔
55
        {
56
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
731✔
57
            if (it != m_state.m_symbols.end())
731✔
58
                context.locals[0].pushBack(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
30✔
59
        }
731✔
60
    }
358✔
61

62
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context, const bool push_with_env)
5,875✔
63
    {
5,875✔
64
        if (closure->valueType() != ValueType::Closure)
5,875✔
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)
11,748✔
84
        {
85
            if (push_with_env)
5,873✔
86
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
3,262✔
87
            else
88
                return *field;
2,611✔
89
        }
90
        else
91
        {
92
            if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
1✔
93
                throwVMError(
1✔
94
                    ErrorKind::Scope,
95
                    fmt::format(
2✔
96
                        "`{0}' isn't in the closure environment: {1}",
1✔
97
                        m_state.m_symbols[id],
1✔
98
                        closure->refClosure().toString(*this)));
1✔
99
            throwVMError(
×
100
                ErrorKind::Scope,
101
                fmt::format(
×
102
                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
×
103
                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
104
                    m_state.m_symbols[id],
×
105
                    closure->refClosure().toString(*this)));
×
106
        }
107
    }
5,875✔
108

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

115
        for (std::size_t i = 0; i < count; ++i)
12,264✔
116
            l.push_back(*popAndResolveAsPtr(context));
6,351✔
117

118
        return l;
5,913✔
119
    }
5,913✔
120

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

134
        for (std::size_t i = 0; i < count; ++i)
44,334✔
135
            list->push_back(*popAndResolveAsPtr(context));
22,169✔
136
    }
22,166✔
137

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

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

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

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

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

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

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

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

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

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

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

213
        m_shared_lib_objects.emplace_back(lib);
1✔
214

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

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

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

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

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

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

253
        ExecutionContext* ctx = nullptr;
17✔
254

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

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

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

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

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

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

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

302
        return ctx;
17✔
303
    }
17✔
304

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

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

328
    Future* VM::createFuture(std::vector<Value>& args)
17✔
329
    {
17✔
330
        const std::lock_guard lock(m_mutex_futures);
17✔
331

332
        ExecutionContext* ctx = createAndGetContext();
17✔
333
        // so that we have access to the presumed symbol id of the function we are calling
334
        // assuming that the callee is always the global context
335
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
17✔
336

337
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
17✔
338
        return m_futures.back().get();
17✔
339
    }
17✔
340

341
    void VM::deleteFuture(Future* f)
1✔
342
    {
1✔
343
        const std::lock_guard lock(m_mutex_futures);
1✔
344

345
        std::erase_if(
1✔
346
            m_futures,
1✔
347
            [f](const std::unique_ptr<Future>& future) {
3✔
348
                return future.get() == f;
2✔
349
            });
350
    }
1✔
351

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

371
                    ++i;
×
372
                }
×
373
            }
×
374

375
            return true;
×
376
        }
×
377
        catch (const std::system_error&)
378
        {
379
            return false;
×
380
        }
×
381
    }
×
382

383
    void VM::usePromptFileForDebugger(const std::string& path, std::ostream& os)
7✔
384
    {
7✔
385
        m_debugger = std::make_unique<Debugger>(m_state.m_libenv, path, os, m_state.m_symbols, m_state.m_constants);
7✔
386
    }
7✔
387

388
    void VM::throwVMError(ErrorKind kind, const std::string& message)
40✔
389
    {
40✔
390
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
40✔
391
    }
40✔
392

393
    int VM::run(const bool fail_with_exception)
358✔
394
    {
358✔
395
        init();
358✔
396
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
358✔
397
        return m_exit_code;
358✔
398
    }
399

400
    int VM::safeRun(ExecutionContext& context, const std::size_t untilFrameCount, const bool fail_with_exception)
389✔
401
    {
389✔
402
        m_running = true;
389✔
403

404
        try
405
        {
406
            if (m_state.m_features & FeatureVMDebugger)
389✔
407
                unsafeRun<true>(context, untilFrameCount);
20✔
408
            else
409
                unsafeRun<false>(context, untilFrameCount);
369✔
410
        }
389✔
411
        catch (const Error& e)
412
        {
413
            if (fail_with_exception)
165✔
414
            {
415
                std::stringstream stream;
164✔
416
                backtrace(context, stream, /* colorize= */ false);
164✔
417
                // It's important we have an Ark::Error here, as the constructor for NestedError
418
                // does more than just aggregate error messages, hence the code duplication.
419
                throw NestedError(e, stream.str(), *this);
164✔
420
            }
164✔
421
            showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context);
1✔
422
        }
289✔
423
        catch (const std::exception& e)
424
        {
425
            if (fail_with_exception)
100✔
426
            {
427
                std::stringstream stream;
100✔
428
                backtrace(context, stream, /* colorize= */ false);
100✔
429
                throw NestedError(e, stream.str());
100✔
430
            }
100✔
431
            showBacktraceWithException(e, context);
×
432
        }
265✔
433
        catch (...)
434
        {
435
            if (fail_with_exception)
×
436
                throw;
×
437

438
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
439
            throw;
440
#endif
441
            fmt::println("Unknown error");
×
442
            backtrace(context);
×
443
            m_exit_code = 1;
×
444
        }
364✔
445

446
        return m_exit_code;
125✔
447
    }
529✔
448

449
    template <bool WithDebugger>
450
    void VM::unsafeRun(ExecutionContext& context, const std::size_t untilFrameCount)
531✔
451
    {
531✔
452
#if ARK_USE_COMPUTED_GOTOS
453
#    define TARGET(op) TARGET_##op:
454
#    define DISPATCH_GOTO()            \
455
        _Pragma("GCC diagnostic push") \
456
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
457
        _Pragma("GCC diagnostic pop")
458
#    define GOTO_HALT() goto dispatch_end
459
#else
460
#    define TARGET(op) case op:
461
#    define DISPATCH_GOTO() goto dispatch_opcode
462
#    define GOTO_HALT() break
463
#endif
464

465
#define FETCH_NEXT_INSTRUCTION()                                                                          \
466
    do                                                                                                    \
467
    {                                                                                                     \
468
        inst = m_state.inst(context.pp, context.ip);                                                      \
469
        padding = m_state.inst(context.pp, context.ip + 1);                                               \
470
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) +                     \
471
                                    m_state.inst(context.pp, context.ip + 3));                            \
472
        context.ip += 4;                                                                                  \
473
        context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize;               \
474
        if constexpr (WithDebugger)                                                                       \
475
        {                                                                                                 \
476
            if (!m_debugger) initDebugger(context);                                                       \
477
            m_debugger->registerInstruction(static_cast<uint32_t>((inst << 24) | (padding << 16) | arg)); \
478
        }                                                                                                 \
479
        if (context.inst_exec_counter < 2 && context.sp >= VMStackSize)                                   \
480
            stackOverflowError(context);                                                                  \
481
    } while (false)
482
#define DISPATCH()            \
483
    FETCH_NEXT_INSTRUCTION(); \
484
    DISPATCH_GOTO();
485
#define UNPACK_ARGS()                                                                 \
486
    do                                                                                \
487
    {                                                                                 \
488
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
489
        primary_arg = arg & 0x0fff;                                                   \
490
    } while (false)
491

492
#if ARK_USE_COMPUTED_GOTOS
493
#    pragma GCC diagnostic push
494
#    pragma GCC diagnostic ignored "-Wpedantic"
495
            constexpr std::array opcode_targets = {
531✔
496
                // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
497
                &&TARGET_NOP,
498
                &&TARGET_LOAD_FAST,
499
                &&TARGET_LOAD_FAST_BY_INDEX,
500
                &&TARGET_LOAD_SYMBOL,
501
                &&TARGET_LOAD_CONST,
502
                &&TARGET_POP_JUMP_IF_TRUE,
503
                &&TARGET_STORE,
504
                &&TARGET_STORE_REF,
505
                &&TARGET_SET_VAL,
506
                &&TARGET_POP_JUMP_IF_FALSE,
507
                &&TARGET_JUMP,
508
                &&TARGET_RET,
509
                &&TARGET_HALT,
510
                &&TARGET_PUSH_RETURN_ADDRESS,
511
                &&TARGET_CALL,
512
                &&TARGET_TAIL_CALL_SELF,
513
                &&TARGET_CAPTURE,
514
                &&TARGET_RENAME_NEXT_CAPTURE,
515
                &&TARGET_BUILTIN,
516
                &&TARGET_DEL,
517
                &&TARGET_MAKE_CLOSURE,
518
                &&TARGET_GET_FIELD,
519
                &&TARGET_GET_FIELD_AS_CLOSURE,
520
                &&TARGET_PLUGIN,
521
                &&TARGET_LIST,
522
                &&TARGET_APPEND,
523
                &&TARGET_CONCAT,
524
                &&TARGET_APPEND_IN_PLACE,
525
                &&TARGET_CONCAT_IN_PLACE,
526
                &&TARGET_POP_LIST,
527
                &&TARGET_POP_LIST_IN_PLACE,
528
                &&TARGET_SET_AT_INDEX,
529
                &&TARGET_SET_AT_2_INDEX,
530
                &&TARGET_POP,
531
                &&TARGET_SHORTCIRCUIT_AND,
532
                &&TARGET_SHORTCIRCUIT_OR,
533
                &&TARGET_CREATE_SCOPE,
534
                &&TARGET_RESET_SCOPE_JUMP,
535
                &&TARGET_POP_SCOPE,
536
                &&TARGET_APPLY,
537
                &&TARGET_BREAKPOINT,
538
                &&TARGET_ADD,
539
                &&TARGET_SUB,
540
                &&TARGET_MUL,
541
                &&TARGET_DIV,
542
                &&TARGET_GT,
543
                &&TARGET_LT,
544
                &&TARGET_LE,
545
                &&TARGET_GE,
546
                &&TARGET_NEQ,
547
                &&TARGET_EQ,
548
                &&TARGET_LEN,
549
                &&TARGET_IS_EMPTY,
550
                &&TARGET_TAIL,
551
                &&TARGET_HEAD,
552
                &&TARGET_IS_NIL,
553
                &&TARGET_TO_NUM,
554
                &&TARGET_TO_STR,
555
                &&TARGET_AT,
556
                &&TARGET_AT_AT,
557
                &&TARGET_MOD,
558
                &&TARGET_TYPE,
559
                &&TARGET_HAS_FIELD,
560
                &&TARGET_NOT,
561
                &&TARGET_LOAD_CONST_LOAD_CONST,
562
                &&TARGET_LOAD_CONST_STORE,
563
                &&TARGET_LOAD_CONST_SET_VAL,
564
                &&TARGET_STORE_FROM,
565
                &&TARGET_STORE_FROM_INDEX,
566
                &&TARGET_SET_VAL_FROM,
567
                &&TARGET_SET_VAL_FROM_INDEX,
568
                &&TARGET_INCREMENT,
569
                &&TARGET_INCREMENT_BY_INDEX,
570
                &&TARGET_INCREMENT_STORE,
571
                &&TARGET_DECREMENT,
572
                &&TARGET_DECREMENT_BY_INDEX,
573
                &&TARGET_DECREMENT_STORE,
574
                &&TARGET_STORE_TAIL,
575
                &&TARGET_STORE_TAIL_BY_INDEX,
576
                &&TARGET_STORE_HEAD,
577
                &&TARGET_STORE_HEAD_BY_INDEX,
578
                &&TARGET_STORE_LIST,
579
                &&TARGET_SET_VAL_TAIL,
580
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
581
                &&TARGET_SET_VAL_HEAD,
582
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
583
                &&TARGET_CALL_BUILTIN,
584
                &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
585
                &&TARGET_LT_CONST_JUMP_IF_FALSE,
586
                &&TARGET_LT_CONST_JUMP_IF_TRUE,
587
                &&TARGET_LT_SYM_JUMP_IF_FALSE,
588
                &&TARGET_GT_CONST_JUMP_IF_TRUE,
589
                &&TARGET_GT_CONST_JUMP_IF_FALSE,
590
                &&TARGET_GT_SYM_JUMP_IF_FALSE,
591
                &&TARGET_EQ_CONST_JUMP_IF_TRUE,
592
                &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
593
                &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
594
                &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
595
                &&TARGET_CALL_SYMBOL,
596
                &&TARGET_CALL_SYMBOL_BY_INDEX,
597
                &&TARGET_CALL_CURRENT_PAGE,
598
                &&TARGET_GET_FIELD_FROM_SYMBOL,
599
                &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
600
                &&TARGET_AT_SYM_SYM,
601
                &&TARGET_AT_SYM_INDEX_SYM_INDEX,
602
                &&TARGET_AT_SYM_INDEX_CONST,
603
                &&TARGET_CHECK_TYPE_OF,
604
                &&TARGET_CHECK_TYPE_OF_BY_INDEX,
605
                &&TARGET_APPEND_IN_PLACE_SYM,
606
                &&TARGET_APPEND_IN_PLACE_SYM_INDEX,
607
                &&TARGET_STORE_LEN,
608
                &&TARGET_LT_LEN_SYM_JUMP_IF_FALSE,
609
                &&TARGET_MUL_BY,
610
                &&TARGET_MUL_BY_INDEX,
611
                &&TARGET_MUL_SET_VAL,
612
                &&TARGET_FUSED_MATH
613
            };
614

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

619
        uint8_t inst = 0;
531✔
620
        uint8_t padding = 0;
531✔
621
        uint16_t arg = 0;
531✔
622
        uint16_t primary_arg = 0;
531✔
623
        uint16_t secondary_arg = 0;
531✔
624

625
        DISPATCH();
531✔
626
        // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
627
        {
628
#if !ARK_USE_COMPUTED_GOTOS
629
        dispatch_opcode:
630
            switch (inst)
631
#endif
632
            {
×
633
#pragma region "Instructions"
634
                TARGET(NOP)
635
                {
636
                    DISPATCH();
×
637
                }
7,322,248✔
638

639
                TARGET(LOAD_FAST)
640
                {
641
                    push(loadSymbol(arg, context), context);
7,322,248✔
642
                    DISPATCH();
7,322,248✔
643
                }
375,831✔
644

645
                TARGET(LOAD_FAST_BY_INDEX)
646
                {
647
                    push(loadSymbolFromIndex(arg, context), context);
375,831✔
648
                    DISPATCH();
375,831✔
649
                }
5,384✔
650

651
                TARGET(LOAD_SYMBOL)
652
                {
653
                    // force resolving the reference
654
                    push(*loadSymbol(arg, context), context);
5,384✔
655
                    DISPATCH();
5,384✔
656
                }
665,753✔
657

658
                TARGET(LOAD_CONST)
659
                {
660
                    push(loadConstAsPtr(arg), context);
665,753✔
661
                    DISPATCH();
665,753✔
662
                }
45,821✔
663

664
                TARGET(POP_JUMP_IF_TRUE)
665
                {
666
                    if (!!*popAndResolveAsPtr(context))
45,821✔
667
                        jump(arg, context);
13,734✔
668
                    DISPATCH();
45,821✔
669
                }
429,401✔
670

671
                TARGET(STORE)
672
                {
673
                    store(arg, popAndResolveAsPtr(context), context);
429,401✔
674
                    DISPATCH();
429,401✔
675
                }
3,126✔
676

677
                TARGET(STORE_REF)
678
                {
679
                    // Not resolving a potential ref is on purpose!
680
                    // This instruction is only used by functions when storing arguments
681
                    const Value* tmp = pop(context);
3,126✔
682
                    store(arg, tmp, context);
3,126✔
683
                    DISPATCH();
3,126✔
684
                }
552,171✔
685

686
                TARGET(SET_VAL)
687
                {
688
                    setVal(arg, popAndResolveAsPtr(context), context);
552,171✔
689
                    DISPATCH();
552,171✔
690
                }
1,035,696✔
691

692
                TARGET(POP_JUMP_IF_FALSE)
693
                {
694
                    if (!*popAndResolveAsPtr(context))
1,035,696✔
695
                        jump(arg, context);
4,812✔
696
                    DISPATCH();
1,035,696✔
697
                }
621,636✔
698

699
                TARGET(JUMP)
700
                {
701
                    jump(arg, context);
621,636✔
702
                    DISPATCH();
621,636✔
703
                }
164,895✔
704

705
                TARGET(RET)
706
                {
707
                    {
708
                        Value ts = *popAndResolveAsPtr(context);
164,895✔
709
                        Value ts1 = *popAndResolveAsPtr(context);
164,895✔
710

711
                        if (ts1.valueType() == ValueType::InstPtr)
164,895✔
712
                        {
713
                            context.ip = ts1.pageAddr();
161,305✔
714
                            // we always push PP then IP, thus the next value
715
                            // MUST be the page pointer
716
                            context.pp = pop(context)->pageAddr();
161,305✔
717

718
                            returnFromFuncCall(context);
161,305✔
719
                            if (ts.valueType() == ValueType::Garbage)
161,305✔
720
                                push(Builtins::nil, context);
×
721
                            else
722
                                push(std::move(ts), context);
161,305✔
723
                        }
161,305✔
724
                        else if (ts1.valueType() == ValueType::Garbage)
3,590✔
725
                        {
726
                            const Value* ip = pop(context);
3,465✔
727
                            assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
3,465✔
728
                            context.ip = ip->pageAddr();
3,465✔
729
                            context.pp = pop(context)->pageAddr();
3,465✔
730

731
                            returnFromFuncCall(context);
3,465✔
732
                            push(std::move(ts), context);
3,465✔
733
                        }
3,465✔
734
                        else if (ts.valueType() == ValueType::InstPtr)
125✔
735
                        {
736
                            context.ip = ts.pageAddr();
125✔
737
                            context.pp = ts1.pageAddr();
125✔
738
                            returnFromFuncCall(context);
125✔
739
                            push(Builtins::nil, context);
125✔
740
                        }
125✔
741
                        else
742
                            throw Error(
×
743
                                fmt::format(
×
744
                                    "Unhandled case when returning from function call. TS=({}){}, TS1=({}){}",
×
745
                                    std::to_string(ts.valueType()),
×
746
                                    ts.toString(*this),
×
747
                                    std::to_string(ts1.valueType()),
×
748
                                    ts1.toString(*this)));
×
749

750
                        if (context.fc <= untilFrameCount)
164,895✔
751
                            GOTO_HALT();
18✔
752
                    }
164,989✔
753

754
                    DISPATCH();
164,877✔
755
                }
105✔
756

757
                TARGET(HALT)
758
                {
759
                    m_running = false;
105✔
760
                    GOTO_HALT();
105✔
761
                }
175,998✔
762

763
                TARGET(PUSH_RETURN_ADDRESS)
764
                {
765
                    push(Value(static_cast<PageAddr_t>(context.pp)), context);
175,998✔
766
                    // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
767
                    push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
175,998✔
768
                    context.inst_exec_counter++;
175,998✔
769
                    DISPATCH();
175,998✔
770
                }
3,451✔
771

772
                TARGET(CALL)
773
                {
774
                    call(context, arg);
3,451✔
775
                    if (!m_running)
3,451✔
776
                        GOTO_HALT();
×
777
                    DISPATCH();
3,451✔
778
                }
87,986✔
779

780
                TARGET(TAIL_CALL_SELF)
781
                {
782
                    jump(0, context);
87,986✔
783
                    context.locals.back().reset();
87,986✔
784
                    DISPATCH();
87,986✔
785
                }
3,360✔
786

787
                TARGET(CAPTURE)
788
                {
789
                    if (!context.saved_scope)
3,360✔
790
                        context.saved_scope = ClosureScope();
693✔
791

792
                    const Value* ptr = findNearestVariable(arg, context);
3,360✔
793
                    if (!ptr)
3,360✔
794
                        throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
795
                    else
796
                    {
797
                        ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,360✔
798
                        uint16_t id = context.capture_rename_id.value_or(arg);
3,360✔
799
                        context.saved_scope.value().push_back(id, *ptr);
3,360✔
800
                        context.capture_rename_id.reset();
3,360✔
801
                    }
802

803
                    DISPATCH();
3,360✔
804
                }
13✔
805

806
                TARGET(RENAME_NEXT_CAPTURE)
807
                {
808
                    context.capture_rename_id = arg;
13✔
809
                    DISPATCH();
13✔
810
                }
7,789✔
811

812
                TARGET(BUILTIN)
813
                {
814
                    push(Builtins::builtins[arg].second, context);
7,789✔
815
                    DISPATCH();
7,789✔
816
                }
2✔
817

818
                TARGET(DEL)
819
                {
820
                    if (Value* var = findNearestVariable(arg, context); var != nullptr)
2✔
821
                    {
822
                        if (var->valueType() == ValueType::User)
1✔
823
                            var->usertypeRef().del();
1✔
824
                        *var = Value();
1✔
825
                        DISPATCH();
1✔
826
                    }
827

828
                    throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
829
                }
693✔
830

831
                TARGET(MAKE_CLOSURE)
832
                {
833
                    push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
693✔
834
                    context.saved_scope.reset();
693✔
835
                    DISPATCH();
693✔
836
                }
6✔
837

838
                TARGET(GET_FIELD)
839
                {
840
                    Value* var = popAndResolveAsPtr(context);
6✔
841
                    push(getField(var, arg, context), context);
6✔
842
                    DISPATCH();
6✔
843
                }
3,262✔
844

845
                TARGET(GET_FIELD_AS_CLOSURE)
846
                {
847
                    Value* var = popAndResolveAsPtr(context);
3,262✔
848
                    push(getField(var, arg, context, /* push_with_env= */ true), context);
3,262✔
849
                    DISPATCH();
3,262✔
850
                }
1✔
851

852
                TARGET(PLUGIN)
853
                {
854
                    loadPlugin(arg, context);
1✔
855
                    DISPATCH();
1✔
856
                }
1,568✔
857

858
                TARGET(LIST)
859
                {
860
                    push(createList(arg, context), context);
1,568✔
861
                    DISPATCH();
1,568✔
862
                }
2,030✔
863

864
                TARGET(APPEND)
865
                {
866
                    {
867
                        Value* list = popAndResolveAsPtr(context);
2,030✔
868
                        if (list->valueType() != ValueType::List)
2,030✔
869
                        {
870
                            std::vector<Value> args = { *list };
1✔
871
                            for (uint16_t i = 0; i < arg; ++i)
2✔
872
                                args.push_back(*popAndResolveAsPtr(context));
1✔
873
                            throw types::TypeCheckingError(
2✔
874
                                "append",
1✔
875
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
876
                                args);
877
                        }
1✔
878

879
                        const auto size = static_cast<uint16_t>(list->constList().size());
2,029✔
880

881
                        Value obj { *list };
2,029✔
882
                        obj.list().reserve(size + arg);
2,029✔
883

884
                        for (uint16_t i = 0; i < arg; ++i)
4,058✔
885
                            obj.push_back(*popAndResolveAsPtr(context));
2,029✔
886
                        push(std::move(obj), context);
2,029✔
887
                    }
2,029✔
888
                    DISPATCH();
2,029✔
889
                }
37✔
890

891
                TARGET(CONCAT)
892
                {
893
                    {
894
                        Value* list = popAndResolveAsPtr(context);
37✔
895
                        Value obj { *list };
37✔
896

897
                        for (uint16_t i = 0; i < arg; ++i)
74✔
898
                        {
899
                            Value* next = popAndResolveAsPtr(context);
39✔
900

901
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
39✔
902
                                throw types::TypeCheckingError(
4✔
903
                                    "concat",
2✔
904
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
905
                                    { *list, *next });
2✔
906

907
                            std::ranges::copy(next->list(), std::back_inserter(obj.list()));
37✔
908
                        }
37✔
909
                        push(std::move(obj), context);
35✔
910
                    }
37✔
911
                    DISPATCH();
35✔
912
                }
1✔
913

914
                TARGET(APPEND_IN_PLACE)
915
                {
916
                    Value* list = popAndResolveAsPtr(context);
1✔
917
                    listAppendInPlace(list, arg, context);
1✔
918
                    DISPATCH();
1✔
919
                }
599✔
920

921
                TARGET(CONCAT_IN_PLACE)
922
                {
923
                    Value* list = popAndResolveAsPtr(context);
599✔
924

925
                    for (uint16_t i = 0; i < arg; ++i)
1,233✔
926
                    {
927
                        Value* next = popAndResolveAsPtr(context);
636✔
928

929
                        if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
636✔
930
                            throw types::TypeCheckingError(
4✔
931
                                "concat!",
2✔
932
                                { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
933
                                { *list, *next });
2✔
934

935
                        std::ranges::copy(next->list(), std::back_inserter(list->list()));
634✔
936
                    }
634✔
937
                    DISPATCH();
597✔
938
                }
1,087✔
939

940
                TARGET(POP_LIST)
941
                {
942
                    {
943
                        Value list = *popAndResolveAsPtr(context);
1,087✔
944
                        Value number = *popAndResolveAsPtr(context);
1,087✔
945

946
                        if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
1,087✔
947
                            throw types::TypeCheckingError(
2✔
948
                                "pop",
1✔
949
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
950
                                { list, number });
1✔
951

952
                        long idx = static_cast<long>(number.number());
1,086✔
953
                        idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
1,086✔
954
                        if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
1,086✔
955
                            throwVMError(
2✔
956
                                ErrorKind::Index,
957
                                fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
958

959
                        list.list().erase(list.list().begin() + idx);
1,084✔
960
                        push(list, context);
1,084✔
961
                    }
1,087✔
962
                    DISPATCH();
1,084✔
963
                }
355✔
964

965
                TARGET(POP_LIST_IN_PLACE)
966
                {
967
                    {
968
                        Value* list = popAndResolveAsPtr(context);
355✔
969
                        Value number = *popAndResolveAsPtr(context);
355✔
970

971
                        if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
355✔
972
                            throw types::TypeCheckingError(
2✔
973
                                "pop!",
1✔
974
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
975
                                { *list, number });
1✔
976

977
                        long idx = static_cast<long>(number.number());
354✔
978
                        idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
354✔
979
                        if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
354✔
980
                            throwVMError(
2✔
981
                                ErrorKind::Index,
982
                                fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
983

984
                        // Save the value we're removing to push it later.
985
                        // We need to save the value and push later because we're using a pointer to 'list', and pushing before erasing
986
                        // would overwrite values from the stack.
987
                        if (arg)
352✔
988
                            number = list->list()[static_cast<std::size_t>(idx)];
298✔
989
                        list->list().erase(list->list().begin() + idx);
352✔
990
                        if (arg)
352✔
991
                            push(number, context);
298✔
992
                    }
355✔
993
                    DISPATCH();
352✔
994
                }
509,214✔
995

996
                TARGET(SET_AT_INDEX)
997
                {
998
                    {
999
                        Value* list = popAndResolveAsPtr(context);
509,214✔
1000
                        Value number = *popAndResolveAsPtr(context);
509,214✔
1001
                        Value new_value = *popAndResolveAsPtr(context);
509,214✔
1002

1003
                        if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
509,214✔
1004
                            throw types::TypeCheckingError(
2✔
1005
                                "@=",
1✔
1006
                                { { types::Contract {
3✔
1007
                                      { types::Typedef("list", ValueType::List),
3✔
1008
                                        types::Typedef("index", ValueType::Number),
1✔
1009
                                        types::Typedef("new_value", ValueType::Any) } } },
1✔
1010
                                  { types::Contract {
1✔
1011
                                      { types::Typedef("string", ValueType::String),
3✔
1012
                                        types::Typedef("index", ValueType::Number),
1✔
1013
                                        types::Typedef("char", ValueType::String) } } } },
1✔
1014
                                { *list, number, new_value });
1✔
1015

1016
                        const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
509,213✔
1017
                        long idx = static_cast<long>(number.number());
509,213✔
1018
                        idx = idx < 0 ? static_cast<long>(size) + idx : idx;
509,213✔
1019
                        if (std::cmp_greater_equal(idx, size) || idx < 0)
509,213✔
1020
                            throwVMError(
2✔
1021
                                ErrorKind::Index,
1022
                                fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
1023

1024
                        if (list->valueType() == ValueType::List)
509,211✔
1025
                        {
1026
                            list->list()[static_cast<std::size_t>(idx)] = new_value;
509,207✔
1027
                            if (arg)
509,207✔
1028
                                push(new_value, context);
24✔
1029
                        }
509,207✔
1030
                        else
1031
                        {
1032
                            list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
4✔
1033
                            if (arg)
4✔
1034
                                push(Value(std::string(1, new_value.string()[0])), context);
2✔
1035
                        }
1036
                    }
509,214✔
1037
                    DISPATCH();
509,211✔
1038
                }
1,117✔
1039

1040
                TARGET(SET_AT_2_INDEX)
1041
                {
1042
                    {
1043
                        Value* list = popAndResolveAsPtr(context);
1,117✔
1044
                        Value x = *popAndResolveAsPtr(context);
1,117✔
1045
                        Value y = *popAndResolveAsPtr(context);
1,117✔
1046
                        Value new_value = *popAndResolveAsPtr(context);
1,117✔
1047

1048
                        if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
1,117✔
1049
                            throw types::TypeCheckingError(
2✔
1050
                                "@@=",
1✔
1051
                                { { types::Contract {
2✔
1052
                                    { types::Typedef("list", ValueType::List),
4✔
1053
                                      types::Typedef("x", ValueType::Number),
1✔
1054
                                      types::Typedef("y", ValueType::Number),
1✔
1055
                                      types::Typedef("new_value", ValueType::Any) } } } },
1✔
1056
                                { *list, x, y, new_value });
1✔
1057

1058
                        long idx_y = static_cast<long>(x.number());
1,116✔
1059
                        idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
1,116✔
1060
                        if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
1,116✔
1061
                            throwVMError(
2✔
1062
                                ErrorKind::Index,
1063
                                fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
1064

1065
                        if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
1,124✔
1066
                            (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
1,113✔
1067
                            throw types::TypeCheckingError(
2✔
1068
                                "@@=",
1✔
1069
                                { { types::Contract {
3✔
1070
                                      { types::Typedef("list", ValueType::List),
4✔
1071
                                        types::Typedef("x", ValueType::Number),
1✔
1072
                                        types::Typedef("y", ValueType::Number),
1✔
1073
                                        types::Typedef("new_value", ValueType::Any) } } },
1✔
1074
                                  { types::Contract {
1✔
1075
                                      { types::Typedef("string", ValueType::String),
4✔
1076
                                        types::Typedef("x", ValueType::Number),
1✔
1077
                                        types::Typedef("y", ValueType::Number),
1✔
1078
                                        types::Typedef("char", ValueType::String) } } } },
1✔
1079
                                { *list, x, y, new_value });
1✔
1080

1081
                        const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
1,113✔
1082
                        const std::size_t size =
1,113✔
1083
                            is_list
2,226✔
1084
                            ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
1,108✔
1085
                            : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
5✔
1086

1087
                        long idx_x = static_cast<long>(y.number());
1,113✔
1088
                        idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1,113✔
1089
                        if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
1,113✔
1090
                            throwVMError(
2✔
1091
                                ErrorKind::Index,
1092
                                fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1093

1094
                        if (is_list)
1,111✔
1095
                        {
1096
                            list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
1,106✔
1097
                            if (arg)
1,106✔
1098
                                push(new_value, context);
2✔
1099
                        }
1,106✔
1100
                        else
1101
                        {
1102
                            list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
5✔
1103
                            if (arg)
5✔
1104
                                push(Value(std::string(1, new_value.string()[0])), context);
3✔
1105
                        }
1106
                    }
1,117✔
1107
                    DISPATCH();
1,111✔
1108
                }
12,117✔
1109

1110
                TARGET(POP)
1111
                {
1112
                    pop(context);
12,117✔
1113
                    DISPATCH();
12,117✔
1114
                }
534,707✔
1115

1116
                TARGET(SHORTCIRCUIT_AND)
1117
                {
1118
                    if (!*peekAndResolveAsPtr(context))
534,707✔
1119
                        jump(arg, context);
4,799✔
1120
                    else
1121
                        pop(context);
529,908✔
1122
                    DISPATCH();
534,707✔
1123
                }
3,192✔
1124

1125
                TARGET(SHORTCIRCUIT_OR)
1126
                {
1127
                    if (!!*peekAndResolveAsPtr(context))
3,192✔
1128
                        jump(arg, context);
838✔
1129
                    else
1130
                        pop(context);
2,354✔
1131
                    DISPATCH();
3,192✔
1132
                }
10,262✔
1133

1134
                TARGET(CREATE_SCOPE)
1135
                {
1136
                    context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
10,262✔
1137
                    DISPATCH();
10,262✔
1138
                }
1,069,659✔
1139

1140
                TARGET(RESET_SCOPE_JUMP)
1141
                {
1142
                    context.locals.back().reset();
1,069,659✔
1143
                    jump(arg, context);
1,069,659✔
1144
                    DISPATCH();
1,069,659✔
1145
                }
10,261✔
1146

1147
                TARGET(POP_SCOPE)
1148
                {
1149
                    context.locals.pop_back();
10,261✔
1150
                    DISPATCH();
10,261✔
1151
                }
203✔
1152

1153
                TARGET(APPLY)
1154
                {
1155
                    {
1156
                        const Value args_list = *popAndResolveAsPtr(context),
203✔
1157
                                    func = *popAndResolveAsPtr(context);
203✔
1158
                        if (args_list.valueType() != ValueType::List || !func.isFunction())
203✔
1159
                        {
1160
                            throw types::TypeCheckingError(
4✔
1161
                                "apply",
2✔
1162
                                { {
8✔
1163
                                    types::Contract {
2✔
1164
                                        { types::Typedef("func", ValueType::PageAddr),
4✔
1165
                                          types::Typedef("args", ValueType::List) } },
2✔
1166
                                    types::Contract {
2✔
1167
                                        { types::Typedef("func", ValueType::Closure),
4✔
1168
                                          types::Typedef("args", ValueType::List) } },
2✔
1169
                                    types::Contract {
2✔
1170
                                        { types::Typedef("func", ValueType::CProc),
4✔
1171
                                          types::Typedef("args", ValueType::List) } },
2✔
1172
                                } },
1173
                                { func, args_list });
2✔
1174
                        }
×
1175

1176
                        push(func, context);
201✔
1177
                        for (const Value& a : args_list.constList())
542✔
1178
                            push(a, context);
341✔
1179

1180
                        call(context, static_cast<uint16_t>(args_list.constList().size()));
201✔
1181
                    }
203✔
1182
                    DISPATCH();
155✔
1183
                }
25✔
1184

1185
#pragma endregion
1186

1187
#pragma region "Operators"
1188

1189
                TARGET(BREAKPOINT)
1190
                {
1191
                    {
1192
                        bool breakpoint_active = true;
25✔
1193
                        if (arg == 1)
25✔
1194
                            breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym;
19✔
1195

1196
                        if (m_state.m_features & FeatureVMDebugger && breakpoint_active)
25✔
1197
                        {
1198
                            initDebugger(context);
13✔
1199
                            m_debugger->run(*this, context, /* from_breakpoint= */ true);
13✔
1200
                            m_debugger->resetContextToSavedState(context);
13✔
1201

1202
                            if (m_debugger->shouldQuitVM())
13✔
1203
                                GOTO_HALT();
1✔
1204
                        }
12✔
1205
                    }
1206
                    DISPATCH();
24✔
1207
                }
48,953✔
1208

1209
                TARGET(ADD)
1210
                {
1211
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
48,953✔
1212

1213
                    if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
48,953✔
1214
                        push(Value(a->number() + b->number()), context);
37,542✔
1215
                    else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
11,411✔
1216
                        push(Value(a->string() + b->string()), context);
11,410✔
1217
                    else
1218
                        throw types::TypeCheckingError(
2✔
1219
                            "+",
1✔
1220
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1221
                                types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1222
                            { *a, *b });
1✔
1223
                    DISPATCH();
48,952✔
1224
                }
511,321✔
1225

1226
                TARGET(SUB)
1227
                {
1228
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
511,321✔
1229

1230
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
511,321✔
1231
                        throw types::TypeCheckingError(
2✔
1232
                            "-",
1✔
1233
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1234
                            { *a, *b });
1✔
1235
                    push(Value(a->number() - b->number()), context);
511,320✔
1236
                    DISPATCH();
511,320✔
1237
                }
510,295✔
1238

1239
                TARGET(MUL)
1240
                {
1241
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
510,295✔
1242

1243
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
510,295✔
1244
                        throw types::TypeCheckingError(
2✔
1245
                            "*",
1✔
1246
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1247
                            { *a, *b });
1✔
1248
                    push(Value(a->number() * b->number()), context);
510,294✔
1249
                    DISPATCH();
510,294✔
1250
                }
3,762✔
1251

1252
                TARGET(DIV)
1253
                {
1254
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
3,762✔
1255

1256
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
3,762✔
1257
                        throw types::TypeCheckingError(
2✔
1258
                            "/",
1✔
1259
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1260
                            { *a, *b });
1✔
1261
                    auto d = b->number();
3,761✔
1262
                    if (d == 0)
3,761✔
1263
                        throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1264

1265
                    push(Value(a->number() / d), context);
3,760✔
1266
                    DISPATCH();
3,760✔
1267
                }
1,760✔
1268

1269
                TARGET(GT)
1270
                {
1271
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,760✔
1272
                    push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
1,760✔
1273
                    DISPATCH();
1,760✔
1274
                }
26,317✔
1275

1276
                TARGET(LT)
1277
                {
1278
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
26,317✔
1279
                    push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
26,317✔
1280
                    DISPATCH();
26,317✔
1281
                }
513,151✔
1282

1283
                TARGET(LE)
1284
                {
1285
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
513,151✔
1286
                    push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
513,151✔
1287
                    DISPATCH();
513,151✔
1288
                }
7,910✔
1289

1290
                TARGET(GE)
1291
                {
1292
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,910✔
1293
                    push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
7,910✔
1294
                    DISPATCH();
7,910✔
1295
                }
504,155✔
1296

1297
                TARGET(NEQ)
1298
                {
1299
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
504,155✔
1300
                    push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
504,155✔
1301
                    DISPATCH();
504,155✔
1302
                }
29,550✔
1303

1304
                TARGET(EQ)
1305
                {
1306
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
29,550✔
1307
                    push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
29,550✔
1308
                    DISPATCH();
29,550✔
1309
                }
7,535✔
1310

1311
                TARGET(LEN)
1312
                {
1313
                    const Value* a = popAndResolveAsPtr(context);
7,535✔
1314

1315
                    if (a->valueType() == ValueType::List)
7,535✔
1316
                        push(Value(static_cast<int>(a->constList().size())), context);
4,230✔
1317
                    else if (a->valueType() == ValueType::String)
3,305✔
1318
                        push(Value(static_cast<int>(a->string().size())), context);
3,279✔
1319
                    else if (a->valueType() == ValueType::Dict)
26✔
1320
                        push(Value(static_cast<int>(a->dict().size())), context);
25✔
1321
                    else
1322
                        throw types::TypeCheckingError(
2✔
1323
                            "len",
1✔
1324
                            { { types::Contract { { types::Typedef("value", ValueType::List) } },
3✔
1325
                                types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1326
                                types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1327
                            { *a });
1✔
1328
                    DISPATCH();
7,534✔
1329
                }
686✔
1330

1331
                TARGET(IS_EMPTY)
1332
                {
1333
                    const Value* a = popAndResolveAsPtr(context);
686✔
1334

1335
                    if (a->valueType() == ValueType::List)
686✔
1336
                        push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
150✔
1337
                    else if (a->valueType() == ValueType::String)
536✔
1338
                        push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
527✔
1339
                    else if (a->valueType() == ValueType::Dict)
9✔
1340
                        push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context);
5✔
1341
                    else if (a->valueType() == ValueType::Nil)
4✔
1342
                        push(Builtins::trueSym, context);
3✔
1343
                    else
1344
                        throw types::TypeCheckingError(
2✔
1345
                            "empty?",
1✔
1346
                            { { types::Contract { { types::Typedef("value", ValueType::List) } },
4✔
1347
                                types::Contract { { types::Typedef("value", ValueType::Nil) } },
1✔
1348
                                types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1349
                                types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1350
                            { *a });
1✔
1351
                    DISPATCH();
685✔
1352
                }
404✔
1353

1354
                TARGET(TAIL)
1355
                {
1356
                    Value* const a = popAndResolveAsPtr(context);
404✔
1357
                    push(helper::tail(a), context);
404✔
1358
                    DISPATCH();
404✔
1359
                }
1,441✔
1360

1361
                TARGET(HEAD)
1362
                {
1363
                    Value* const a = popAndResolveAsPtr(context);
1,441✔
1364
                    push(helper::head(a), context);
1,441✔
1365
                    DISPATCH();
1,441✔
1366
                }
2,549✔
1367

1368
                TARGET(IS_NIL)
1369
                {
1370
                    const Value* a = popAndResolveAsPtr(context);
2,549✔
1371
                    push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,549✔
1372
                    DISPATCH();
2,549✔
1373
                }
1,100✔
1374

1375
                TARGET(TO_NUM)
1376
                {
1377
                    const Value* a = popAndResolveAsPtr(context);
1,100✔
1378

1379
                    if (a->valueType() != ValueType::String)
1,100✔
1380
                        throw types::TypeCheckingError(
2✔
1381
                            "toNumber",
1✔
1382
                            { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1383
                            { *a });
1✔
1384

1385
                    double val;
1386
                    if (Utils::isDouble(a->string(), &val))
1,099✔
1387
                        push(Value(val), context);
1,095✔
1388
                    else
1389
                        push(Builtins::nil, context);
4✔
1390
                    DISPATCH();
1,099✔
1391
                }
1,899✔
1392

1393
                TARGET(TO_STR)
1394
                {
1395
                    const Value* a = popAndResolveAsPtr(context);
1,899✔
1396
                    push(Value(a->toString(*this)), context);
1,899✔
1397
                    DISPATCH();
1,899✔
1398
                }
511,926✔
1399

1400
                TARGET(AT)
1401
                {
1402
                    Value& b = *popAndResolveAsPtr(context);
511,926✔
1403
                    Value& a = *popAndResolveAsPtr(context);
511,926✔
1404
                    push(helper::at(a, b, *this), context);
511,926✔
1405
                    DISPATCH();
511,926✔
1406
                }
2,625✔
1407

1408
                TARGET(AT_AT)
1409
                {
1410
                    {
1411
                        const Value* x = popAndResolveAsPtr(context);
2,625✔
1412
                        const Value* y = popAndResolveAsPtr(context);
2,625✔
1413
                        Value& list = *popAndResolveAsPtr(context);
2,625✔
1414

1415
                        push(helper::atAt(x, y, list), context);
2,625✔
1416
                    }
1417
                    DISPATCH();
2,625✔
1418
                }
1,033,715✔
1419

1420
                TARGET(MOD)
1421
                {
1422
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,033,715✔
1423
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,033,715✔
1424
                        throw types::TypeCheckingError(
2✔
1425
                            "mod",
1✔
1426
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1427
                            { *a, *b });
1✔
1428
                    push(Value(std::fmod(a->number(), b->number())), context);
1,033,714✔
1429
                    DISPATCH();
1,033,714✔
1430
                }
32✔
1431

1432
                TARGET(TYPE)
1433
                {
1434
                    const Value* a = popAndResolveAsPtr(context);
32✔
1435
                    push(Value(std::to_string(a->valueType())), context);
32✔
1436
                    DISPATCH();
32✔
1437
                }
3✔
1438

1439
                TARGET(HAS_FIELD)
1440
                {
1441
                    {
1442
                        Value* const field = popAndResolveAsPtr(context);
3✔
1443
                        Value* const closure = popAndResolveAsPtr(context);
3✔
1444
                        if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1445
                            throw types::TypeCheckingError(
2✔
1446
                                "hasField",
1✔
1447
                                { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1448
                                { *closure, *field });
1✔
1449

1450
                        auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1451
                        if (it == m_state.m_symbols.end())
2✔
1452
                        {
1453
                            push(Builtins::falseSym, context);
1✔
1454
                            DISPATCH();
1✔
1455
                        }
1456

1457
                        auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1458
                        push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1459
                    }
1460
                    DISPATCH();
1✔
1461
                }
3,850✔
1462

1463
                TARGET(NOT)
1464
                {
1465
                    const Value* a = popAndResolveAsPtr(context);
3,850✔
1466
                    push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,850✔
1467
                    DISPATCH();
3,850✔
1468
                }
11,331✔
1469

1470
#pragma endregion
1471

1472
#pragma region "Super Instructions"
1473
                TARGET(LOAD_CONST_LOAD_CONST)
1474
                {
1475
                    UNPACK_ARGS();
11,331✔
1476
                    push(loadConstAsPtr(primary_arg), context);
11,331✔
1477
                    push(loadConstAsPtr(secondary_arg), context);
11,331✔
1478
                    context.inst_exec_counter++;
11,331✔
1479
                    DISPATCH();
11,331✔
1480
                }
22,098✔
1481

1482
                TARGET(LOAD_CONST_STORE)
1483
                {
1484
                    UNPACK_ARGS();
22,098✔
1485
                    store(secondary_arg, loadConstAsPtr(primary_arg), context);
22,098✔
1486
                    DISPATCH();
22,098✔
1487
                }
1,444✔
1488

1489
                TARGET(LOAD_CONST_SET_VAL)
1490
                {
1491
                    UNPACK_ARGS();
1,444✔
1492
                    setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
1,444✔
1493
                    DISPATCH();
1,444✔
1494
                }
47✔
1495

1496
                TARGET(STORE_FROM)
1497
                {
1498
                    UNPACK_ARGS();
47✔
1499
                    store(secondary_arg, loadSymbol(primary_arg, context), context);
47✔
1500
                    DISPATCH();
47✔
1501
                }
844✔
1502

1503
                TARGET(STORE_FROM_INDEX)
1504
                {
1505
                    UNPACK_ARGS();
844✔
1506
                    store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
844✔
1507
                    DISPATCH();
844✔
1508
                }
678✔
1509

1510
                TARGET(SET_VAL_FROM)
1511
                {
1512
                    UNPACK_ARGS();
678✔
1513
                    setVal(secondary_arg, loadSymbol(primary_arg, context), context);
678✔
1514
                    DISPATCH();
678✔
1515
                }
905✔
1516

1517
                TARGET(SET_VAL_FROM_INDEX)
1518
                {
1519
                    UNPACK_ARGS();
905✔
1520
                    setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
905✔
1521
                    DISPATCH();
905✔
1522
                }
167✔
1523

1524
                TARGET(INCREMENT)
1525
                {
1526
                    UNPACK_ARGS();
167✔
1527
                    {
1528
                        Value* var = loadSymbol(primary_arg, context);
167✔
1529

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

1534
                        if (var->valueType() == ValueType::Number)
167✔
1535
                            push(Value(var->number() + secondary_arg), context);
166✔
1536
                        else
1537
                            throw types::TypeCheckingError(
2✔
1538
                                "+",
1✔
1539
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1540
                                { *var, Value(secondary_arg) });
1✔
1541
                    }
1542
                    DISPATCH();
166✔
1543
                }
91,649✔
1544

1545
                TARGET(INCREMENT_BY_INDEX)
1546
                {
1547
                    UNPACK_ARGS();
91,649✔
1548
                    {
1549
                        Value* var = loadSymbolFromIndex(primary_arg, context);
91,649✔
1550

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

1555
                        if (var->valueType() == ValueType::Number)
91,649✔
1556
                            push(Value(var->number() + secondary_arg), context);
91,648✔
1557
                        else
1558
                            throw types::TypeCheckingError(
2✔
1559
                                "+",
1✔
1560
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1561
                                { *var, Value(secondary_arg) });
1✔
1562
                    }
1563
                    DISPATCH();
91,648✔
1564
                }
557,848✔
1565

1566
                TARGET(INCREMENT_STORE)
1567
                {
1568
                    UNPACK_ARGS();
557,848✔
1569
                    {
1570
                        Value* var = loadSymbol(primary_arg, context);
557,848✔
1571

1572
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1573
                        if (var->valueType() == ValueType::Reference)
557,848✔
1574
                            var = var->reference();
×
1575

1576
                        if (var->valueType() == ValueType::Number)
557,848✔
1577
                        {
1578
                            auto val = Value(var->number() + secondary_arg);
557,847✔
1579
                            setVal(primary_arg, &val, context);
557,847✔
1580
                        }
557,847✔
1581
                        else
1582
                            throw types::TypeCheckingError(
2✔
1583
                                "+",
1✔
1584
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1585
                                { *var, Value(secondary_arg) });
1✔
1586
                    }
1587
                    DISPATCH();
557,847✔
1588
                }
515,382✔
1589

1590
                TARGET(DECREMENT)
1591
                {
1592
                    UNPACK_ARGS();
515,382✔
1593
                    {
1594
                        Value* var = loadSymbol(primary_arg, context);
515,382✔
1595

1596
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1597
                        if (var->valueType() == ValueType::Reference)
515,382✔
1598
                            var = var->reference();
×
1599

1600
                        if (var->valueType() == ValueType::Number)
515,382✔
1601
                            push(Value(var->number() - secondary_arg), context);
515,381✔
1602
                        else
1603
                            throw types::TypeCheckingError(
2✔
1604
                                "-",
1✔
1605
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1606
                                { *var, Value(secondary_arg) });
1✔
1607
                    }
1608
                    DISPATCH();
515,381✔
1609
                }
195,034✔
1610

1611
                TARGET(DECREMENT_BY_INDEX)
1612
                {
1613
                    UNPACK_ARGS();
195,034✔
1614
                    {
1615
                        Value* var = loadSymbolFromIndex(primary_arg, context);
195,034✔
1616

1617
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1618
                        if (var->valueType() == ValueType::Reference)
195,034✔
1619
                            var = var->reference();
×
1620

1621
                        if (var->valueType() == ValueType::Number)
195,034✔
1622
                            push(Value(var->number() - secondary_arg), context);
195,033✔
1623
                        else
1624
                            throw types::TypeCheckingError(
2✔
1625
                                "-",
1✔
1626
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1627
                                { *var, Value(secondary_arg) });
1✔
1628
                    }
1629
                    DISPATCH();
195,033✔
1630
                }
512,542✔
1631

1632
                TARGET(DECREMENT_STORE)
1633
                {
1634
                    UNPACK_ARGS();
512,542✔
1635
                    {
1636
                        Value* var = loadSymbol(primary_arg, context);
512,542✔
1637

1638
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1639
                        if (var->valueType() == ValueType::Reference)
512,542✔
1640
                            var = var->reference();
×
1641

1642
                        if (var->valueType() == ValueType::Number)
512,542✔
1643
                        {
1644
                            auto val = Value(var->number() - secondary_arg);
512,541✔
1645
                            setVal(primary_arg, &val, context);
512,541✔
1646
                        }
512,541✔
1647
                        else
1648
                            throw types::TypeCheckingError(
2✔
1649
                                "-",
1✔
1650
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1651
                                { *var, Value(secondary_arg) });
1✔
1652
                    }
1653
                    DISPATCH();
512,541✔
1654
                }
1✔
1655

1656
                TARGET(STORE_TAIL)
1657
                {
1658
                    UNPACK_ARGS();
1✔
1659
                    {
1660
                        Value* list = loadSymbol(primary_arg, context);
1✔
1661
                        Value tail = helper::tail(list);
1✔
1662
                        store(secondary_arg, &tail, context);
1✔
1663
                    }
1✔
1664
                    DISPATCH();
1✔
1665
                }
8✔
1666

1667
                TARGET(STORE_TAIL_BY_INDEX)
1668
                {
1669
                    UNPACK_ARGS();
8✔
1670
                    {
1671
                        Value* list = loadSymbolFromIndex(primary_arg, context);
8✔
1672
                        Value tail = helper::tail(list);
8✔
1673
                        store(secondary_arg, &tail, context);
8✔
1674
                    }
8✔
1675
                    DISPATCH();
8✔
1676
                }
4✔
1677

1678
                TARGET(STORE_HEAD)
1679
                {
1680
                    UNPACK_ARGS();
4✔
1681
                    {
1682
                        Value* list = loadSymbol(primary_arg, context);
4✔
1683
                        Value head = helper::head(list);
4✔
1684
                        store(secondary_arg, &head, context);
4✔
1685
                    }
4✔
1686
                    DISPATCH();
4✔
1687
                }
38✔
1688

1689
                TARGET(STORE_HEAD_BY_INDEX)
1690
                {
1691
                    UNPACK_ARGS();
38✔
1692
                    {
1693
                        Value* list = loadSymbolFromIndex(primary_arg, context);
38✔
1694
                        Value head = helper::head(list);
38✔
1695
                        store(secondary_arg, &head, context);
38✔
1696
                    }
38✔
1697
                    DISPATCH();
38✔
1698
                }
4,345✔
1699

1700
                TARGET(STORE_LIST)
1701
                {
1702
                    UNPACK_ARGS();
4,345✔
1703
                    {
1704
                        Value l = createList(primary_arg, context);
4,345✔
1705
                        store(secondary_arg, &l, context);
4,345✔
1706
                    }
4,345✔
1707
                    DISPATCH();
4,345✔
1708
                }
3✔
1709

1710
                TARGET(SET_VAL_TAIL)
1711
                {
1712
                    UNPACK_ARGS();
3✔
1713
                    {
1714
                        Value* list = loadSymbol(primary_arg, context);
3✔
1715
                        Value tail = helper::tail(list);
3✔
1716
                        setVal(secondary_arg, &tail, context);
3✔
1717
                    }
3✔
1718
                    DISPATCH();
3✔
1719
                }
1✔
1720

1721
                TARGET(SET_VAL_TAIL_BY_INDEX)
1722
                {
1723
                    UNPACK_ARGS();
1✔
1724
                    {
1725
                        Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1726
                        Value tail = helper::tail(list);
1✔
1727
                        setVal(secondary_arg, &tail, context);
1✔
1728
                    }
1✔
1729
                    DISPATCH();
1✔
1730
                }
1✔
1731

1732
                TARGET(SET_VAL_HEAD)
1733
                {
1734
                    UNPACK_ARGS();
1✔
1735
                    {
1736
                        Value* list = loadSymbol(primary_arg, context);
1✔
1737
                        Value head = helper::head(list);
1✔
1738
                        setVal(secondary_arg, &head, context);
1✔
1739
                    }
1✔
1740
                    DISPATCH();
1✔
1741
                }
1✔
1742

1743
                TARGET(SET_VAL_HEAD_BY_INDEX)
1744
                {
1745
                    UNPACK_ARGS();
1✔
1746
                    {
1747
                        Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1748
                        Value head = helper::head(list);
1✔
1749
                        setVal(secondary_arg, &head, context);
1✔
1750
                    }
1✔
1751
                    DISPATCH();
1✔
1752
                }
8,582✔
1753

1754
                TARGET(CALL_BUILTIN)
1755
                {
1756
                    UNPACK_ARGS();
8,582✔
1757
                    // no stack size check because we do not push IP/PP since we are just calling a builtin
1758
                    callBuiltin(
17,164✔
1759
                        context,
8,582✔
1760
                        Builtins::builtins[primary_arg].second,
8,582✔
1761
                        secondary_arg,
8,582✔
1762
                        /* remove_return_address= */ true,
1763
                        /* remove_builtin= */ false);
1764
                    if (!m_running)
8,582✔
1765
                        GOTO_HALT();
×
1766
                    DISPATCH();
8,582✔
1767
                }
19,732✔
1768

1769
                TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1770
                {
1771
                    UNPACK_ARGS();
19,732✔
1772
                    // no stack size check because we do not push IP/PP since we are just calling a builtin
1773
                    callBuiltin(
39,464✔
1774
                        context,
19,732✔
1775
                        Builtins::builtins[primary_arg].second,
19,732✔
1776
                        secondary_arg,
19,732✔
1777
                        // we didn't have a PUSH_RETURN_ADDRESS instruction before,
1778
                        // so do not attempt to remove (pp,ip) from the stack: they're not there!
1779
                        /* remove_return_address= */ false,
1780
                        /* remove_builtin= */ false);
1781
                    if (!m_running)
19,732✔
1782
                        GOTO_HALT();
×
1783
                    DISPATCH();
19,732✔
1784
                }
887✔
1785

1786
                TARGET(LT_CONST_JUMP_IF_FALSE)
1787
                {
1788
                    UNPACK_ARGS();
887✔
1789
                    const Value* sym = popAndResolveAsPtr(context);
887✔
1790
                    if (!(*sym < *loadConstAsPtr(primary_arg)))
887✔
1791
                        jump(secondary_arg, context);
125✔
1792
                    DISPATCH();
887✔
1793
                }
22,973✔
1794

1795
                TARGET(LT_CONST_JUMP_IF_TRUE)
1796
                {
1797
                    UNPACK_ARGS();
22,973✔
1798
                    const Value* sym = popAndResolveAsPtr(context);
22,973✔
1799
                    if (*sym < *loadConstAsPtr(primary_arg))
22,973✔
1800
                        jump(secondary_arg, context);
11,961✔
1801
                    DISPATCH();
22,973✔
1802
                }
10,130✔
1803

1804
                TARGET(LT_SYM_JUMP_IF_FALSE)
1805
                {
1806
                    UNPACK_ARGS();
10,130✔
1807
                    const Value* sym = popAndResolveAsPtr(context);
10,130✔
1808
                    if (!(*sym < *loadSymbol(primary_arg, context)))
10,130✔
1809
                        jump(secondary_arg, context);
575✔
1810
                    DISPATCH();
10,130✔
1811
                }
172,669✔
1812

1813
                TARGET(GT_CONST_JUMP_IF_TRUE)
1814
                {
1815
                    UNPACK_ARGS();
172,669✔
1816
                    const Value* sym = popAndResolveAsPtr(context);
172,669✔
1817
                    const Value* cst = loadConstAsPtr(primary_arg);
172,669✔
1818
                    if (*cst < *sym)
172,669✔
1819
                        jump(secondary_arg, context);
86,693✔
1820
                    DISPATCH();
172,669✔
1821
                }
4,117✔
1822

1823
                TARGET(GT_CONST_JUMP_IF_FALSE)
1824
                {
1825
                    UNPACK_ARGS();
4,117✔
1826
                    const Value* sym = popAndResolveAsPtr(context);
4,117✔
1827
                    const Value* cst = loadConstAsPtr(primary_arg);
4,117✔
1828
                    if (!(*cst < *sym))
4,117✔
1829
                        jump(secondary_arg, context);
1,050✔
1830
                    DISPATCH();
4,117✔
1831
                }
82✔
1832

1833
                TARGET(GT_SYM_JUMP_IF_FALSE)
1834
                {
1835
                    UNPACK_ARGS();
82✔
1836
                    const Value* sym = popAndResolveAsPtr(context);
82✔
1837
                    const Value* rhs = loadSymbol(primary_arg, context);
82✔
1838
                    if (!(*rhs < *sym))
82✔
1839
                        jump(secondary_arg, context);
16✔
1840
                    DISPATCH();
82✔
1841
                }
502,112✔
1842

1843
                TARGET(EQ_CONST_JUMP_IF_TRUE)
1844
                {
1845
                    UNPACK_ARGS();
502,112✔
1846
                    const Value* sym = popAndResolveAsPtr(context);
502,112✔
1847
                    if (*sym == *loadConstAsPtr(primary_arg))
502,112✔
1848
                        jump(secondary_arg, context);
12,185✔
1849
                    DISPATCH();
502,112✔
1850
                }
89,217✔
1851

1852
                TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1853
                {
1854
                    UNPACK_ARGS();
89,217✔
1855
                    const Value* sym = popAndResolveAsPtr(context);
89,217✔
1856
                    if (*sym == *loadSymbolFromIndex(primary_arg, context))
89,217✔
1857
                        jump(secondary_arg, context);
588✔
1858
                    DISPATCH();
89,217✔
1859
                }
11✔
1860

1861
                TARGET(NEQ_CONST_JUMP_IF_TRUE)
1862
                {
1863
                    UNPACK_ARGS();
11✔
1864
                    const Value* sym = popAndResolveAsPtr(context);
11✔
1865
                    if (*sym != *loadConstAsPtr(primary_arg))
11✔
1866
                        jump(secondary_arg, context);
2✔
1867
                    DISPATCH();
11✔
1868
                }
30✔
1869

1870
                TARGET(NEQ_SYM_JUMP_IF_FALSE)
1871
                {
1872
                    UNPACK_ARGS();
30✔
1873
                    const Value* sym = popAndResolveAsPtr(context);
30✔
1874
                    if (*sym == *loadSymbol(primary_arg, context))
30✔
1875
                        jump(secondary_arg, context);
10✔
1876
                    DISPATCH();
30✔
1877
                }
52,474✔
1878

1879
                TARGET(CALL_SYMBOL)
1880
                {
1881
                    UNPACK_ARGS();
52,474✔
1882
                    call(context, secondary_arg, /* function_ptr= */ loadSymbol(primary_arg, context));
52,474✔
1883
                    if (!m_running)
52,474✔
1884
                        GOTO_HALT();
×
1885
                    DISPATCH();
52,474✔
1886
                }
1,194✔
1887

1888
                TARGET(CALL_SYMBOL_BY_INDEX)
1889
                {
1890
                    UNPACK_ARGS();
1,194✔
1891
                    call(context, secondary_arg, /* function_ptr= */ loadSymbolFromIndex(primary_arg, context));
1,194✔
1892
                    if (!m_running)
1,194✔
1893
                        GOTO_HALT();
×
1894
                    DISPATCH();
1,194✔
1895
                }
109,874✔
1896

1897
                TARGET(CALL_CURRENT_PAGE)
1898
                {
1899
                    UNPACK_ARGS();
109,874✔
1900
                    context.last_symbol = primary_arg;
109,874✔
1901
                    call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,874✔
1902
                    if (!m_running)
109,874✔
1903
                        GOTO_HALT();
×
1904
                    DISPATCH();
109,874✔
1905
                }
2,285✔
1906

1907
                TARGET(GET_FIELD_FROM_SYMBOL)
1908
                {
1909
                    UNPACK_ARGS();
2,285✔
1910
                    push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
2,285✔
1911
                    DISPATCH();
2,285✔
1912
                }
320✔
1913

1914
                TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1915
                {
1916
                    UNPACK_ARGS();
320✔
1917
                    push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
320✔
1918
                    DISPATCH();
320✔
1919
                }
543,313✔
1920

1921
                TARGET(AT_SYM_SYM)
1922
                {
1923
                    UNPACK_ARGS();
543,313✔
1924
                    push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
543,313✔
1925
                    DISPATCH();
543,313✔
1926
                }
52✔
1927

1928
                TARGET(AT_SYM_INDEX_SYM_INDEX)
1929
                {
1930
                    UNPACK_ARGS();
52✔
1931
                    push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
52✔
1932
                    DISPATCH();
52✔
1933
                }
6,428✔
1934

1935
                TARGET(AT_SYM_INDEX_CONST)
1936
                {
1937
                    UNPACK_ARGS();
6,428✔
1938
                    push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
6,428✔
1939
                    DISPATCH();
6,428✔
1940
                }
2✔
1941

1942
                TARGET(CHECK_TYPE_OF)
1943
                {
1944
                    UNPACK_ARGS();
2✔
1945
                    const Value* sym = loadSymbol(primary_arg, context);
2✔
1946
                    const Value* cst = loadConstAsPtr(secondary_arg);
2✔
1947
                    push(
2✔
1948
                        cst->valueType() == ValueType::String &&
4✔
1949
                                std::to_string(sym->valueType()) == cst->string()
2✔
1950
                            ? Builtins::trueSym
1951
                            : Builtins::falseSym,
1952
                        context);
2✔
1953
                    DISPATCH();
2✔
1954
                }
160✔
1955

1956
                TARGET(CHECK_TYPE_OF_BY_INDEX)
1957
                {
1958
                    UNPACK_ARGS();
160✔
1959
                    const Value* sym = loadSymbolFromIndex(primary_arg, context);
160✔
1960
                    const Value* cst = loadConstAsPtr(secondary_arg);
160✔
1961
                    push(
160✔
1962
                        cst->valueType() == ValueType::String &&
320✔
1963
                                std::to_string(sym->valueType()) == cst->string()
160✔
1964
                            ? Builtins::trueSym
1965
                            : Builtins::falseSym,
1966
                        context);
160✔
1967
                    DISPATCH();
160✔
1968
                }
22,147✔
1969

1970
                TARGET(APPEND_IN_PLACE_SYM)
1971
                {
1972
                    UNPACK_ARGS();
22,147✔
1973
                    listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
22,147✔
1974
                    DISPATCH();
22,147✔
1975
                }
17✔
1976

1977
                TARGET(APPEND_IN_PLACE_SYM_INDEX)
1978
                {
1979
                    UNPACK_ARGS();
17✔
1980
                    listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
17✔
1981
                    DISPATCH();
17✔
1982
                }
190✔
1983

1984
                TARGET(STORE_LEN)
1985
                {
1986
                    UNPACK_ARGS();
190✔
1987
                    {
1988
                        Value* a = loadSymbolFromIndex(primary_arg, context);
190✔
1989
                        Value len;
190✔
1990
                        if (a->valueType() == ValueType::List)
190✔
1991
                            len = Value(static_cast<int>(a->constList().size()));
74✔
1992
                        else if (a->valueType() == ValueType::String)
116✔
1993
                            len = Value(static_cast<int>(a->string().size()));
115✔
1994
                        else
1995
                            throw types::TypeCheckingError(
2✔
1996
                                "len",
1✔
1997
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1998
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1999
                                { *a });
1✔
2000
                        store(secondary_arg, &len, context);
189✔
2001
                    }
190✔
2002
                    DISPATCH();
189✔
2003
                }
28,979✔
2004

2005
                TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
2006
                {
2007
                    UNPACK_ARGS();
28,979✔
2008
                    {
2009
                        const Value* sym = loadSymbol(primary_arg, context);
28,979✔
2010
                        Value size;
28,979✔
2011

2012
                        if (sym->valueType() == ValueType::List)
28,979✔
2013
                            size = Value(static_cast<int>(sym->constList().size()));
22,978✔
2014
                        else if (sym->valueType() == ValueType::String)
6,001✔
2015
                            size = Value(static_cast<int>(sym->string().size()));
6,000✔
2016
                        else
2017
                            throw types::TypeCheckingError(
2✔
2018
                                "len",
1✔
2019
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2020
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2021
                                { *sym });
1✔
2022

2023
                        if (!(*popAndResolveAsPtr(context) < size))
28,978✔
2024
                            jump(secondary_arg, context);
3,673✔
2025
                    }
28,979✔
2026
                    DISPATCH();
28,978✔
2027
                }
521✔
2028

2029
                TARGET(MUL_BY)
2030
                {
2031
                    UNPACK_ARGS();
521✔
2032
                    {
2033
                        Value* var = loadSymbol(primary_arg, context);
521✔
2034
                        const int other = static_cast<int>(secondary_arg) - 2048;
521✔
2035

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

2040
                        if (var->valueType() == ValueType::Number)
521✔
2041
                            push(Value(var->number() * other), context);
520✔
2042
                        else
2043
                            throw types::TypeCheckingError(
2✔
2044
                                "*",
1✔
2045
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2046
                                { *var, Value(other) });
1✔
2047
                    }
2048
                    DISPATCH();
520✔
2049
                }
56✔
2050

2051
                TARGET(MUL_BY_INDEX)
2052
                {
2053
                    UNPACK_ARGS();
56✔
2054
                    {
2055
                        Value* var = loadSymbolFromIndex(primary_arg, context);
56✔
2056
                        const int other = static_cast<int>(secondary_arg) - 2048;
56✔
2057

2058
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
2059
                        if (var->valueType() == ValueType::Reference)
56✔
2060
                            var = var->reference();
×
2061

2062
                        if (var->valueType() == ValueType::Number)
56✔
2063
                            push(Value(var->number() * other), context);
55✔
2064
                        else
2065
                            throw types::TypeCheckingError(
2✔
2066
                                "*",
1✔
2067
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2068
                                { *var, Value(other) });
1✔
2069
                    }
2070
                    DISPATCH();
55✔
2071
                }
2✔
2072

2073
                TARGET(MUL_SET_VAL)
2074
                {
2075
                    UNPACK_ARGS();
2✔
2076
                    {
2077
                        Value* var = loadSymbol(primary_arg, context);
2✔
2078
                        const int other = static_cast<int>(secondary_arg) - 2048;
2✔
2079

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

2084
                        if (var->valueType() == ValueType::Number)
2✔
2085
                        {
2086
                            auto val = Value(var->number() * other);
1✔
2087
                            setVal(primary_arg, &val, context);
1✔
2088
                        }
1✔
2089
                        else
2090
                            throw types::TypeCheckingError(
2✔
2091
                                "*",
1✔
2092
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2093
                                { *var, Value(other) });
1✔
2094
                    }
2095
                    DISPATCH();
1✔
2096
                }
509,821✔
2097

2098
                TARGET(FUSED_MATH)
2099
                {
2100
                    const auto op1 = static_cast<Instruction>(padding),
509,821✔
2101
                               op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
509,821✔
2102
                               op3 = static_cast<Instruction>(arg & 0x00ff);
509,821✔
2103
                    const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
509,821✔
2104

2105
                    const Value* d = popAndResolveAsPtr(context);
509,821✔
2106
                    const Value* c = popAndResolveAsPtr(context);
509,821✔
2107
                    const Value* b = popAndResolveAsPtr(context);
509,821✔
2108

2109
                    if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
509,821✔
2110
                        throw types::TypeCheckingError(
2✔
2111
                            helper::mathInstToStr(op1),
1✔
2112
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2113
                            { *c, *d });
1✔
2114

2115
                    double temp = helper::doMath(c->number(), d->number(), op1);
509,820✔
2116
                    if (b->valueType() != ValueType::Number)
509,820✔
2117
                        throw types::TypeCheckingError(
4✔
2118
                            helper::mathInstToStr(op2),
2✔
2119
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
2✔
2120
                            { *b, Value(temp) });
2✔
2121
                    temp = helper::doMath(b->number(), temp, op2);
509,818✔
2122

2123
                    if (arg_count == 2)
509,818✔
2124
                        push(Value(temp), context);
509,781✔
2125
                    else if (arg_count == 3)
37✔
2126
                    {
2127
                        const Value* a = popAndResolveAsPtr(context);
37✔
2128
                        if (a->valueType() != ValueType::Number)
37✔
2129
                            throw types::TypeCheckingError(
2✔
2130
                                helper::mathInstToStr(op3),
1✔
2131
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2132
                                { *a, Value(temp) });
1✔
2133

2134
                        temp = helper::doMath(a->number(), temp, op3);
36✔
2135
                        push(Value(temp), context);
36✔
2136
                    }
36✔
2137
                    else
2138
                        throw Error(
×
2139
                            fmt::format(
×
2140
                                "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
×
2141
                                arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
×
2142
                    DISPATCH();
509,817✔
2143
                }
2144
#pragma endregion
2145
            }
124✔
2146
#if ARK_USE_COMPUTED_GOTOS
2147
        dispatch_end:
2148
            do
124✔
2149
            {
2150
            } while (false);
124✔
2151
#endif
2152
        }
2153
    }
218✔
2154

2155
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,054✔
2156
    {
2,054✔
2157
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,196✔
2158
        {
2159
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,142✔
2160
                return id;
2,048✔
2161
        }
2,096,142✔
2162
        return MaxValue16Bits;
6✔
2163
    }
2,054✔
2164

2165
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, ExecutionContext& context, const bool skip_function)
7✔
2166
    {
7✔
2167
        std::vector<std::string> arg_names;
7✔
2168
        arg_names.reserve(expected_arg_count + 1);
7✔
2169

2170
        std::size_t index = 0;
7✔
2171
        while (m_state.inst(context.pp, index) == STORE ||
14✔
2172
               m_state.inst(context.pp, index) == STORE_REF)
7✔
2173
        {
2174
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
2175
            arg_names.insert(arg_names.begin(), m_state.m_symbols[id]);
×
2176
            index += 4;
×
2177
        }
×
2178
        // we have no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2179
        if (arg_names.empty() && index == 0)
7✔
2180
        {
2181
            for (std::size_t i = 0; i < expected_arg_count; ++i)
5✔
2182
                arg_names.emplace_back(1, static_cast<char>('a' + i));
2✔
2183
        }
3✔
2184
        if (expected_arg_count > 0)
7✔
2185
            arg_names.insert(arg_names.begin(), "");  // for formatting, so that we have a space between the function and the args
6✔
2186

2187
        std::vector<std::string> arg_vals;
7✔
2188
        arg_vals.reserve(passed_arg_count + 1);
7✔
2189

2190
        for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.sp; ++i)
20✔
2191
            // -1 on the stack because we always point to the next available slot
2192
            arg_vals.push_back(context.stack[context.sp - passed_arg_count + i].toString(*this));
13✔
2193
        if (passed_arg_count > 0)
7✔
2194
            arg_vals.insert(arg_vals.begin(), "");  // for formatting, so that we have a space between the function and the args
6✔
2195

2196
        // set ip/pp to the callee location so that the error can pinpoint the line
2197
        // where the bad call happened
2198
        if (context.sp >= 2 + passed_arg_count)
7✔
2199
        {
2200
            // -2/-3 instead of -1/-2 to skip over the function pushed on the stack
2201
            context.ip = context.stack[context.sp - 1 - (skip_function ? 1 : 0) - passed_arg_count].pageAddr();
7✔
2202
            context.pp = context.stack[context.sp - 2 - (skip_function ? 1 : 0) - passed_arg_count].pageAddr();
7✔
2203
            context.sp -= 2;
7✔
2204
            returnFromFuncCall(context);
7✔
2205
        }
7✔
2206

2207
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
14✔
2208
            ? m_state.m_symbols[context.last_symbol]
7✔
2209
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2210

2211
        throwVMError(
7✔
2212
            ErrorKind::Arity,
2213
            fmt::format(
14✔
2214
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
7✔
2215
                function_name,
2216
                fmt::join(arg_vals, " "),
7✔
2217
                passed_arg_count,
2218
                passed_arg_count > 1 ? "s" : "",
7✔
2219
                expected_arg_count,
2220
                function_name,
2221
                fmt::join(arg_names, " ")));
7✔
2222
    }
14✔
2223

2224
    void VM::initDebugger(ExecutionContext& context)
14✔
2225
    {
14✔
2226
        if (!m_debugger)
14✔
2227
            m_debugger = std::make_unique<Debugger>(context, m_state.m_libenv, m_state.m_symbols, m_state.m_constants);
×
2228
        else
2229
            m_debugger->saveState(context);
14✔
2230
    }
14✔
2231

2232
    void VM::showBacktraceWithException(const std::exception& e, ExecutionContext& context)
1✔
2233
    {
1✔
2234
        std::string text = e.what();
1✔
2235
        if (!text.empty() && text.back() != '\n')
1✔
2236
            text += '\n';
×
2237
        fmt::println(std::cerr, "{}", text);
1✔
2238

2239
        // 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
2240
        const bool error_from_debugger = m_debugger && m_debugger->isRunning();
1✔
2241
        if (m_state.m_features & FeatureVMDebugger && !error_from_debugger)
1✔
2242
            initDebugger(context);
1✔
2243

2244
        const std::size_t saved_ip = context.ip;
1✔
2245
        const std::size_t saved_pp = context.pp;
1✔
2246
        const uint16_t saved_sp = context.sp;
1✔
2247

2248
        backtrace(context);
1✔
2249

2250
        fmt::println(
1✔
2251
            std::cerr,
2252
            "At IP: {}, PP: {}, SP: {}",
1✔
2253
            // dividing by 4 because the instructions are actually on 4 bytes
2254
            fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)),
1✔
2255
            fmt::styled(saved_pp, fmt::fg(fmt::color::green)),
1✔
2256
            fmt::styled(saved_sp, fmt::fg(fmt::color::yellow)));
1✔
2257

2258
        if (m_debugger && !error_from_debugger)
1✔
2259
        {
2260
            m_debugger->resetContextToSavedState(context);
1✔
2261
            m_debugger->run(*this, context, /* from_breakpoint= */ false);
1✔
2262
        }
1✔
2263

2264
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2265
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2266
        m_exit_code = 0;
2267
#else
2268
        m_exit_code = 1;
1✔
2269
#endif
2270
    }
1✔
2271

2272
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,340✔
2273
    {
2,340✔
2274
        std::optional<InstLoc> match = std::nullopt;
2,340✔
2275

2276
        for (const auto location : m_state.m_inst_locations)
11,985✔
2277
        {
2278
            if (location.page_pointer == pp && !match)
9,645✔
2279
                match = location;
2,340✔
2280

2281
            // select the best match: we want to find the location that's nearest our instruction pointer,
2282
            // but not equal to it as the IP will always be pointing to the next instruction,
2283
            // not yet executed. Thus, the erroneous instruction is the previous one.
2284
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
9,645✔
2285
                match = location;
2,578✔
2286

2287
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2288
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
9,645✔
2289
                break;
2,086✔
2290
        }
9,645✔
2291

2292
        return match;
2,340✔
2293
    }
2294

2295
    std::string VM::debugShowSource() const
×
2296
    {
×
2297
        const auto& context = m_execution_contexts.front();
×
2298
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2299
        if (maybe_source_loc)
×
2300
        {
2301
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2302
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2303
        }
×
2304
        return "No source location found";
×
2305
    }
×
2306

2307
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
265✔
2308
    {
265✔
2309
        constexpr std::size_t max_consecutive_traces = 7;
265✔
2310

2311
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
265✔
2312
        if (maybe_location)
265✔
2313
        {
2314
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
265✔
2315

2316
            if (Utils::fileExists(filename))
265✔
2317
                Diagnostics::makeContext(
526✔
2318
                    Diagnostics::ErrorLocation {
526✔
2319
                        .filename = filename,
263✔
2320
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
263✔
2321
                        .end = std::nullopt,
263✔
2322
                        .maybe_content = std::nullopt },
263✔
2323
                    os,
263✔
2324
                    /* maybe_context= */ std::nullopt,
263✔
2325
                    /* colorize= */ colorize);
263✔
2326
            fmt::println(os, "");
265✔
2327
        }
265✔
2328

2329
        if (context.fc > 1)
265✔
2330
        {
2331
            // display call stack trace
2332
            const ScopeView old_scope = context.locals.back();
8✔
2333

2334
            std::string previous_trace;
8✔
2335
            std::size_t displayed_traces = 0;
8✔
2336
            std::size_t consecutive_similar_traces = 0;
8✔
2337

2338
            while (context.fc != 0 && context.pp != 0 && context.sp > 0)
2,062✔
2339
            {
2340
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,054✔
2341
                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,054✔
2342

2343
                const uint16_t id = findNearestVariableIdWithValue(
2,054✔
2344
                    Value(static_cast<PageAddr_t>(context.pp)),
2,054✔
2345
                    context);
2,054✔
2346
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,054✔
2347

2348
                if (func_name + loc_as_text != previous_trace)
2,054✔
2349
                {
2350
                    fmt::println(
16✔
2351
                        os,
8✔
2352
                        "[{:4}] In function `{}'{}",
8✔
2353
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
8✔
2354
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
8✔
2355
                        loc_as_text);
2356
                    previous_trace = func_name + loc_as_text;
8✔
2357
                    ++displayed_traces;
8✔
2358
                    consecutive_similar_traces = 0;
8✔
2359
                }
8✔
2360
                else if (consecutive_similar_traces == 0)
2,046✔
2361
                {
2362
                    fmt::println(os, "       ...");
1✔
2363
                    ++consecutive_similar_traces;
1✔
2364
                }
1✔
2365

2366
                const Value* ip;
2,054✔
2367
                do
2,061✔
2368
                {
2369
                    ip = popAndResolveAsPtr(context);
2,061✔
2370
                } while (ip->valueType() != ValueType::InstPtr);
2,061✔
2371

2372
                context.ip = ip->pageAddr();
2,054✔
2373
                context.pp = pop(context)->pageAddr();
2,054✔
2374
                returnFromFuncCall(context);
2,054✔
2375

2376
                if (displayed_traces > max_consecutive_traces)
2,054✔
2377
                {
2378
                    fmt::println(os, "       ...");
×
2379
                    break;
×
2380
                }
2381
            }
2,054✔
2382

2383
            if (context.pp == 0)
8✔
2384
            {
2385
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
8✔
2386
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
8✔
2387
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
8✔
2388
            }
8✔
2389

2390
            // display variables values in the current scope
2391
            fmt::println(os, "\nCurrent scope variables values:");
8✔
2392
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
9✔
2393
            {
2394
                fmt::println(
2✔
2395
                    os,
1✔
2396
                    "{} = {}",
1✔
2397
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2398
                    old_scope.atPos(i).second.toString(*this));
1✔
2399
            }
1✔
2400
        }
8✔
2401
    }
265✔
2402
}
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