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

ArkScript-lang / Ark / 29851449016

21 Jul 2026 05:05PM UTC coverage: 94.39% (+0.005%) from 94.385%
29851449016

push

github

SuperFola
fix(compiler): keep provenance information of IR blocks inside the IROptimiser

9 of 9 new or added lines in 1 file covered. (100.0%)

122 existing lines in 10 files now uncovered.

10700 of 11336 relevant lines covered (94.39%)

1164644.99 hits per line

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

92.33
/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,098✔
23
        m_state(state), m_exit_code(0), m_running(false)
366✔
24
    {
366✔
25
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
366✔
26
    }
366✔
27

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

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

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

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

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

62
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context, const bool push_with_env)
11,363✔
63
    {
11,363✔
64
        if (closure->valueType() != ValueType::Closure)
11,363✔
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)
22,724✔
84
        {
85
            if (push_with_env)
11,361✔
86
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
6,102✔
87
            else
88
                return *field;
5,259✔
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
    }
11,363✔
108

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

115
        for (std::size_t i = 0; i < count; ++i)
19,929✔
116
            l.push_back(*popAndResolveAsPtr(context));
11,614✔
117

118
        return l;
8,315✔
119
    }
8,315✔
120

121
    void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
23,385✔
122
    {
23,385✔
123
        if (list->valueType() != ValueType::List)
23,385✔
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)
46,776✔
135
            list->push_back(*popAndResolveAsPtr(context));
23,392✔
136
    }
23,385✔
137

138
    Value& VM::operator[](const std::string& name) noexcept
38✔
139
    {
38✔
140
        // find id of object
141
        const auto it = std::ranges::find(m_state.m_symbols, name);
38✔
142
        if (it == m_state.m_symbols.end())
38✔
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);
37✔
149
        if (std::cmp_less(dist, MaxValue16Bits))
37✔
150
        {
151
            ExecutionContext& context = *m_execution_contexts.front();
37✔
152

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

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

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

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

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

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

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

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

199
        if (!lib)
2✔
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);
2✔
214

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

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

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

231
            context.locals.back().insertFront(data);
2✔
232
        }
2✔
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
        }
2✔
241
    }
2✔
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()
33✔
250
    {
33✔
251
        const std::lock_guard lock(m_mutex);
33✔
252

253
        ExecutionContext* ctx = nullptr;
33✔
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)
33✔
261
        {
262
            const auto it = std::ranges::find_if(
56✔
263
                m_execution_contexts,
28✔
264
                [](const std::unique_ptr<ExecutionContext>& context) -> bool {
76✔
265
                    return !context->primary && context->isFree();
76✔
266
                });
267

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

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

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

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

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

302
        return ctx;
33✔
303
    }
33✔
304

305
    void VM::deleteContext(ExecutionContext* ec)
32✔
306
    {
32✔
307
        const std::lock_guard lock(m_mutex);
32✔
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)
32✔
311
        {
312
            const auto it =
2✔
313
                std::ranges::remove_if(
4✔
314
                    m_execution_contexts,
2✔
315
                    [ec](const std::unique_ptr<ExecutionContext>& ctx) {
14✔
316
                        return ctx.get() == ec;
12✔
317
                    })
318
                    .begin();
2✔
319
            m_execution_contexts.erase(it);
2✔
320
        }
2✔
321
        else
322
        {
323
            // mark the used context as ready to be used again
324
            ec->setActive(false);
30✔
325
        }
326
    }
32✔
327

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

332
        ExecutionContext* ctx = createAndGetContext();
33✔
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;
33✔
336

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

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

345
        std::erase_if(
2✔
346
            m_futures,
2✔
347
            [f](const std::unique_ptr<Future>& future) {
6✔
348
                return future.get() == f;
4✔
349
            });
350
    }
2✔
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)
360✔
394
    {
360✔
395
        init();
360✔
396
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
360✔
397
        return m_exit_code;
360✔
398
    }
399

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

404
#define WITH_TRY
405

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

443
#    ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
444
            throw;
445
#    endif
UNCOV
446
            fmt::println("Unknown error");
×
UNCOV
447
            backtrace(context);
×
UNCOV
448
            m_exit_code = 1;
×
449
        }
364✔
450
#endif
451
#undef WITH_TRY
452

453
        return m_exit_code;
143✔
454
    }
529✔
455

456
    template <bool WithDebugger>
457
    void VM::unsafeRun(ExecutionContext& context, const std::size_t untilFrameCount)
529✔
458
    {
529✔
459
#if ARK_USE_COMPUTED_GOTOS
460
#    define TARGET(op) TARGET_##op:
461
#    define DISPATCH_GOTO()            \
462
        _Pragma("GCC diagnostic push") \
463
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
464
        _Pragma("GCC diagnostic pop")
465
#    define GOTO_HALT() goto dispatch_end
466
#else
467
#    define TARGET(op) case op:
468
#    define DISPATCH_GOTO() goto dispatch_opcode
469
#    define GOTO_HALT() break
470
#endif
471

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

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

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

626
        uint8_t inst = 0;
529✔
627
        uint8_t padding = 0;
529✔
628
        uint16_t arg = 0;
529✔
629
        uint16_t primary_arg = 0;
529✔
630
        uint16_t secondary_arg = 0;
529✔
631

632
        DISPATCH();
529✔
633
        // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
634
        {
635
#if !ARK_USE_COMPUTED_GOTOS
636
        dispatch_opcode:
637
            switch (inst)
638
#endif
UNCOV
639
            {
×
640
#pragma region "Instructions"
641
                TARGET(NOP)
642
                {
UNCOV
643
                    DISPATCH();
×
644
                }
7,359,474✔
645

646
                TARGET(LOAD_FAST)
647
                {
648
                    push(loadSymbol(arg, context), context);
7,359,474✔
649
                    DISPATCH();
7,359,474✔
650
                }
387,241✔
651

652
                TARGET(LOAD_FAST_BY_INDEX)
653
                {
654
                    push(loadSymbolFromIndex(arg, context), context);
387,241✔
655
                    DISPATCH();
387,241✔
656
                }
7,732✔
657

658
                TARGET(LOAD_SYMBOL)
659
                {
660
                    // force resolving the reference
661
                    push(*loadSymbol(arg, context), context);
7,732✔
662
                    DISPATCH();
7,732✔
663
                }
677,048✔
664

665
                TARGET(LOAD_CONST)
666
                {
667
                    push(loadConstAsPtr(arg), context);
677,048✔
668
                    DISPATCH();
677,048✔
669
                }
54,790✔
670

671
                TARGET(POP_JUMP_IF_TRUE)
672
                {
673
                    if (!!*popAndResolveAsPtr(context))
54,790✔
674
                        jump(arg, context);
17,929✔
675
                    DISPATCH();
54,790✔
676
                }
438,179✔
677

678
                TARGET(STORE)
679
                {
680
                    store(arg, popAndResolveAsPtr(context), context);
438,179✔
681
                    DISPATCH();
438,179✔
682
                }
3,719✔
683

684
                TARGET(STORE_REF)
685
                {
686
                    // Not resolving a potential ref is on purpose!
687
                    // This instruction is only used by functions when storing arguments
688
                    const Value* tmp = pop(context);
3,719✔
689
                    store(arg, tmp, context);
3,719✔
690
                    DISPATCH();
3,719✔
691
                }
558,815✔
692

693
                TARGET(SET_VAL)
694
                {
695
                    setVal(arg, popAndResolveAsPtr(context), context);
558,815✔
696
                    DISPATCH();
558,815✔
697
                }
1,036,724✔
698

699
                TARGET(POP_JUMP_IF_FALSE)
700
                {
701
                    if (!*popAndResolveAsPtr(context))
1,036,724✔
702
                        jump(arg, context);
4,938✔
703
                    DISPATCH();
1,036,724✔
704
                }
627,093✔
705

706
                TARGET(JUMP)
707
                {
708
                    jump(arg, context);
627,093✔
709
                    DISPATCH();
627,093✔
710
                }
155,060✔
711

712
                TARGET(RET)
713
                {
714
                    {
715
                        Value ts = *popAndResolveAsPtr(context);
155,060✔
716
                        Value ts1 = *popAndResolveAsPtr(context);
155,060✔
717

718
                        if (ts1.valueType() == ValueType::InstPtr)
155,060✔
719
                        {
720
                            context.ip = ts1.pageAddr();
148,500✔
721
                            // we always push PP then IP, thus the next value
722
                            // MUST be the page pointer
723
                            context.pp = pop(context)->pageAddr();
148,500✔
724

725
                            returnFromFuncCall(context);
148,500✔
726
                            if (ts.valueType() == ValueType::Garbage)
148,500✔
UNCOV
727
                                push(Builtins::nil, context);
×
728
                            else
729
                                push(std::move(ts), context);
148,500✔
730
                        }
148,500✔
731
                        else if (ts1.valueType() == ValueType::Garbage)
6,560✔
732
                        {
733
                            const Value* ip = pop(context);
6,411✔
734
                            assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
6,411✔
735
                            context.ip = ip->pageAddr();
6,411✔
736
                            context.pp = pop(context)->pageAddr();
6,411✔
737

738
                            returnFromFuncCall(context);
6,411✔
739
                            push(std::move(ts), context);
6,411✔
740
                        }
6,411✔
741
                        else if (ts.valueType() == ValueType::InstPtr)
149✔
742
                        {
743
                            context.ip = ts.pageAddr();
149✔
744
                            context.pp = ts1.pageAddr();
149✔
745
                            returnFromFuncCall(context);
149✔
746
                            push(Builtins::nil, context);
149✔
747
                        }
149✔
748
                        else
UNCOV
749
                            throw Error(
×
UNCOV
750
                                fmt::format(
×
UNCOV
751
                                    "Unhandled case when returning from function call. TS=({}){}, TS1=({}){}",
×
UNCOV
752
                                    std::to_string(ts.valueType()),
×
UNCOV
753
                                    ts.toString(*this),
×
UNCOV
754
                                    std::to_string(ts1.valueType()),
×
UNCOV
755
                                    ts1.toString(*this)));
×
756

757
                        if (context.fc <= untilFrameCount)
155,060✔
758
                            GOTO_HALT();
34✔
759
                    }
155,154✔
760

761
                    DISPATCH();
155,026✔
762
                }
107✔
763

764
                TARGET(HALT)
765
                {
766
                    m_running = false;
107✔
767
                    GOTO_HALT();
107✔
768
                }
167,871✔
769

770
                TARGET(PUSH_RETURN_ADDRESS)
771
                {
772
                    push(Value(static_cast<PageAddr_t>(context.pp)), context);
167,871✔
773
                    // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
774
                    push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
167,871✔
775
                    context.inst_exec_counter++;
167,871✔
776
                    DISPATCH();
167,871✔
777
                }
6,379✔
778

779
                TARGET(CALL)
780
                {
781
                    call(context, arg);
6,379✔
782
                    if (!m_running)
6,379✔
UNCOV
783
                        GOTO_HALT();
×
784
                    DISPATCH();
6,379✔
785
                }
88,074✔
786

787
                TARGET(TAIL_CALL_SELF)
788
                {
789
                    jump(0, context);
88,074✔
790
                    context.locals.back().reset();
88,074✔
791
                    DISPATCH();
88,074✔
792
                }
4,070✔
793

794
                TARGET(CAPTURE)
795
                {
796
                    if (!context.saved_scope)
4,070✔
797
                        context.saved_scope = ClosureScope();
830✔
798

799
                    const Value* ptr = findNearestVariable(arg, context);
4,070✔
800
                    if (!ptr)
4,070✔
UNCOV
801
                        throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
802
                    else
803
                    {
804
                        ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
4,070✔
805
                        uint16_t id = context.capture_rename_id.value_or(arg);
4,070✔
806
                        context.saved_scope.value().push_back(id, *ptr);
4,070✔
807
                        context.capture_rename_id.reset();
4,070✔
808
                    }
809

810
                    DISPATCH();
4,070✔
811
                }
21✔
812

813
                TARGET(RENAME_NEXT_CAPTURE)
814
                {
815
                    context.capture_rename_id = arg;
21✔
816
                    DISPATCH();
21✔
817
                }
11,110✔
818

819
                TARGET(BUILTIN)
820
                {
821
                    push(Builtins::builtins[arg].second, context);
11,110✔
822
                    DISPATCH();
11,110✔
823
                }
3✔
824

825
                TARGET(DEL)
826
                {
827
                    if (Value* var = findNearestVariable(arg, context); var != nullptr)
3✔
828
                    {
829
                        if (var->valueType() == ValueType::User)
2✔
830
                            var->usertypeRef().del();
2✔
831
                        *var = Value();
2✔
832
                        DISPATCH();
2✔
833
                    }
834

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

838
                TARGET(MAKE_CLOSURE)
839
                {
840
                    push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
830✔
841
                    context.saved_scope.reset();
830✔
842
                    DISPATCH();
830✔
843
                }
12✔
844

845
                TARGET(GET_FIELD)
846
                {
847
                    Value* var = popAndResolveAsPtr(context);
12✔
848
                    push(getField(var, arg, context), context);
12✔
849
                    DISPATCH();
12✔
850
                }
6,102✔
851

852
                TARGET(GET_FIELD_AS_CLOSURE)
853
                {
854
                    Value* var = popAndResolveAsPtr(context);
6,102✔
855
                    push(getField(var, arg, context, /* push_with_env= */ true), context);
6,102✔
856
                    DISPATCH();
6,102✔
857
                }
2✔
858

859
                TARGET(PLUGIN)
860
                {
861
                    loadPlugin(arg, context);
2✔
862
                    DISPATCH();
2✔
863
                }
2,727✔
864

865
                TARGET(LIST)
866
                {
867
                    push(createList(arg, context), context);
2,727✔
868
                    DISPATCH();
2,727✔
869
                }
2,040✔
870

871
                TARGET(APPEND)
872
                {
873
                    {
874
                        Value* list = popAndResolveAsPtr(context);
2,040✔
875
                        if (list->valueType() != ValueType::List)
2,040✔
876
                        {
877
                            std::vector<Value> args = { *list };
1✔
878
                            for (uint16_t i = 0; i < arg; ++i)
2✔
879
                                args.push_back(*popAndResolveAsPtr(context));
1✔
880
                            throw types::TypeCheckingError(
2✔
881
                                "append",
1✔
882
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
883
                                args);
884
                        }
1✔
885

886
                        const auto size = static_cast<uint16_t>(list->constList().size());
2,039✔
887

888
                        Value obj { *list };
2,039✔
889
                        obj.list().reserve(size + arg);
2,039✔
890

891
                        for (uint16_t i = 0; i < arg; ++i)
4,078✔
892
                            obj.push_back(*popAndResolveAsPtr(context));
2,039✔
893
                        push(std::move(obj), context);
2,039✔
894
                    }
2,039✔
895
                    DISPATCH();
2,039✔
896
                }
62✔
897

898
                TARGET(CONCAT)
899
                {
900
                    {
901
                        Value* list = popAndResolveAsPtr(context);
62✔
902
                        Value obj { *list };
62✔
903

904
                        for (uint16_t i = 0; i < arg; ++i)
126✔
905
                        {
906
                            Value* next = popAndResolveAsPtr(context);
66✔
907

908
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
66✔
909
                                throw types::TypeCheckingError(
4✔
910
                                    "concat",
2✔
911
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
912
                                    { *list, *next });
2✔
913

914
                            std::ranges::copy(next->list(), std::back_inserter(obj.list()));
64✔
915
                        }
64✔
916
                        push(std::move(obj), context);
60✔
917
                    }
62✔
918
                    DISPATCH();
60✔
919
                }
2✔
920

921
                TARGET(APPEND_IN_PLACE)
922
                {
923
                    Value* list = popAndResolveAsPtr(context);
2✔
924
                    listAppendInPlace(list, arg, context);
2✔
925
                    DISPATCH();
2✔
926
                }
617✔
927

928
                TARGET(CONCAT_IN_PLACE)
929
                {
930
                    Value* list = popAndResolveAsPtr(context);
617✔
931

932
                    for (uint16_t i = 0; i < arg; ++i)
1,276✔
933
                    {
934
                        Value* next = popAndResolveAsPtr(context);
661✔
935

936
                        if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
661✔
937
                            throw types::TypeCheckingError(
4✔
938
                                "concat!",
2✔
939
                                { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
940
                                { *list, *next });
2✔
941

942
                        std::ranges::copy(next->list(), std::back_inserter(list->list()));
659✔
943
                    }
659✔
944
                    DISPATCH();
615✔
945
                }
1,090✔
946

947
                TARGET(POP_LIST)
948
                {
949
                    {
950
                        Value list = *popAndResolveAsPtr(context);
1,090✔
951
                        Value number = *popAndResolveAsPtr(context);
1,090✔
952

953
                        if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
1,090✔
954
                            throw types::TypeCheckingError(
2✔
955
                                "pop",
1✔
956
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
957
                                { list, number });
1✔
958

959
                        long idx = static_cast<long>(number.number());
1,089✔
960
                        idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
1,089✔
961
                        if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
1,089✔
962
                            throwVMError(
2✔
963
                                ErrorKind::Index,
964
                                fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
965

966
                        list.list().erase(list.list().begin() + idx);
1,087✔
967
                        push(list, context);
1,087✔
968
                    }
1,090✔
969
                    DISPATCH();
1,087✔
970
                }
710✔
971

972
                TARGET(POP_LIST_IN_PLACE)
973
                {
974
                    {
975
                        Value* list = popAndResolveAsPtr(context);
710✔
976
                        Value number = *popAndResolveAsPtr(context);
710✔
977

978
                        if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
710✔
979
                            throw types::TypeCheckingError(
2✔
980
                                "pop!",
1✔
981
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
982
                                { *list, number });
1✔
983

984
                        long idx = static_cast<long>(number.number());
709✔
985
                        idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
709✔
986
                        if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
709✔
987
                            throwVMError(
2✔
988
                                ErrorKind::Index,
989
                                fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
990

991
                        // Save the value we're removing to push it later.
992
                        // We need to save the value and push later because we're using a pointer to 'list', and pushing before erasing
993
                        // would overwrite values from the stack.
994
                        if (arg)
707✔
995
                            number = list->list()[static_cast<std::size_t>(idx)];
602✔
996
                        list->list().erase(list->list().begin() + idx);
707✔
997
                        if (arg)
707✔
998
                            push(number, context);
602✔
999
                    }
710✔
1000
                    DISPATCH();
707✔
1001
                }
509,443✔
1002

1003
                TARGET(SET_AT_INDEX)
1004
                {
1005
                    {
1006
                        Value* list = popAndResolveAsPtr(context);
509,443✔
1007
                        Value number = *popAndResolveAsPtr(context);
509,443✔
1008
                        Value new_value = *popAndResolveAsPtr(context);
509,443✔
1009

1010
                        if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
509,443✔
1011
                            throw types::TypeCheckingError(
2✔
1012
                                "@=",
1✔
1013
                                { { types::Contract {
3✔
1014
                                      { types::Typedef("list", ValueType::List),
3✔
1015
                                        types::Typedef("index", ValueType::Number),
1✔
1016
                                        types::Typedef("new_value", ValueType::Any) } } },
1✔
1017
                                  { types::Contract {
1✔
1018
                                      { types::Typedef("string", ValueType::String),
3✔
1019
                                        types::Typedef("index", ValueType::Number),
1✔
1020
                                        types::Typedef("char", ValueType::String) } } } },
1✔
1021
                                { *list, number, new_value });
1✔
1022

1023
                        const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
509,442✔
1024
                        long idx = static_cast<long>(number.number());
509,442✔
1025
                        idx = idx < 0 ? static_cast<long>(size) + idx : idx;
509,442✔
1026
                        if (std::cmp_greater_equal(idx, size) || idx < 0)
509,442✔
1027
                            throwVMError(
2✔
1028
                                ErrorKind::Index,
1029
                                fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
1030

1031
                        if (list->valueType() == ValueType::List)
509,440✔
1032
                        {
1033
                            list->list()[static_cast<std::size_t>(idx)] = new_value;
509,432✔
1034
                            if (arg)
509,432✔
1035
                                push(new_value, context);
48✔
1036
                        }
509,432✔
1037
                        else
1038
                        {
1039
                            list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
8✔
1040
                            if (arg)
8✔
1041
                                push(Value(std::string(1, new_value.string()[0])), context);
4✔
1042
                        }
1043
                    }
509,443✔
1044
                    DISPATCH();
509,440✔
1045
                }
2,228✔
1046

1047
                TARGET(SET_AT_2_INDEX)
1048
                {
1049
                    {
1050
                        Value* list = popAndResolveAsPtr(context);
2,228✔
1051
                        Value x = *popAndResolveAsPtr(context);
2,228✔
1052
                        Value y = *popAndResolveAsPtr(context);
2,228✔
1053
                        Value new_value = *popAndResolveAsPtr(context);
2,228✔
1054

1055
                        if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
2,228✔
1056
                            throw types::TypeCheckingError(
2✔
1057
                                "@@=",
1✔
1058
                                { { types::Contract {
2✔
1059
                                    { types::Typedef("list", ValueType::List),
4✔
1060
                                      types::Typedef("x", ValueType::Number),
1✔
1061
                                      types::Typedef("y", ValueType::Number),
1✔
1062
                                      types::Typedef("new_value", ValueType::Any) } } } },
1✔
1063
                                { *list, x, y, new_value });
1✔
1064

1065
                        long idx_y = static_cast<long>(x.number());
2,227✔
1066
                        idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
2,227✔
1067
                        if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
2,227✔
1068
                            throwVMError(
2✔
1069
                                ErrorKind::Index,
1070
                                fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
1071

1072
                        if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
2,245✔
1073
                            (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
2,224✔
1074
                            throw types::TypeCheckingError(
2✔
1075
                                "@@=",
1✔
1076
                                { { types::Contract {
3✔
1077
                                      { types::Typedef("list", ValueType::List),
4✔
1078
                                        types::Typedef("x", ValueType::Number),
1✔
1079
                                        types::Typedef("y", ValueType::Number),
1✔
1080
                                        types::Typedef("new_value", ValueType::Any) } } },
1✔
1081
                                  { types::Contract {
1✔
1082
                                      { types::Typedef("string", ValueType::String),
4✔
1083
                                        types::Typedef("x", ValueType::Number),
1✔
1084
                                        types::Typedef("y", ValueType::Number),
1✔
1085
                                        types::Typedef("char", ValueType::String) } } } },
1✔
1086
                                { *list, x, y, new_value });
1✔
1087

1088
                        const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
2,224✔
1089
                        const std::size_t size =
2,224✔
1090
                            is_list
4,448✔
1091
                            ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
2,214✔
1092
                            : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
10✔
1093

1094
                        long idx_x = static_cast<long>(y.number());
2,224✔
1095
                        idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
2,224✔
1096
                        if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
2,224✔
1097
                            throwVMError(
2✔
1098
                                ErrorKind::Index,
1099
                                fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1100

1101
                        if (is_list)
2,222✔
1102
                        {
1103
                            list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
2,212✔
1104
                            if (arg)
2,212✔
1105
                                push(new_value, context);
4✔
1106
                        }
2,212✔
1107
                        else
1108
                        {
1109
                            list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
10✔
1110
                            if (arg)
10✔
1111
                                push(Value(std::string(1, new_value.string()[0])), context);
6✔
1112
                        }
1113
                    }
2,228✔
1114
                    DISPATCH();
2,222✔
1115
                }
17,737✔
1116

1117
                TARGET(POP)
1118
                {
1119
                    pop(context);
17,737✔
1120
                    DISPATCH();
17,737✔
1121
                }
537,969✔
1122

1123
                TARGET(SHORTCIRCUIT_AND)
1124
                {
1125
                    if (!*peekAndResolveAsPtr(context))
537,969✔
1126
                        jump(arg, context);
5,315✔
1127
                    else
1128
                        pop(context);
532,654✔
1129
                    DISPATCH();
537,969✔
1130
                }
3,805✔
1131

1132
                TARGET(SHORTCIRCUIT_OR)
1133
                {
1134
                    if (!!*peekAndResolveAsPtr(context))
3,805✔
1135
                        jump(arg, context);
914✔
1136
                    else
1137
                        pop(context);
2,891✔
1138
                    DISPATCH();
3,805✔
1139
                }
11,570✔
1140

1141
                TARGET(CREATE_SCOPE)
1142
                {
1143
                    context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
11,570✔
1144
                    DISPATCH();
11,570✔
1145
                }
1,076,334✔
1146

1147
                TARGET(RESET_SCOPE_JUMP)
1148
                {
1149
                    context.locals.back().reset();
1,076,334✔
1150
                    jump(arg, context);
1,076,334✔
1151
                    DISPATCH();
1,076,334✔
1152
                }
11,569✔
1153

1154
                TARGET(POP_SCOPE)
1155
                {
1156
                    if (arg == 1)
11,569✔
1157
                    {
1158
                        if (Value* ts = peek(context); ts->valueType() == ValueType::Reference)
380✔
1159
                            *ts = *ts->reference();
142✔
1160
                    }
380✔
1161
                    context.locals.pop_back();
11,569✔
1162
                    DISPATCH();
11,569✔
1163
                }
358✔
1164

1165
                TARGET(APPLY)
1166
                {
1167
                    {
1168
                        const Value args_list = *popAndResolveAsPtr(context),
358✔
1169
                                    func = *popAndResolveAsPtr(context);
358✔
1170
                        if (args_list.valueType() != ValueType::List || !func.isFunction())
358✔
1171
                        {
1172
                            throw types::TypeCheckingError(
4✔
1173
                                "apply",
2✔
1174
                                { {
8✔
1175
                                    types::Contract {
2✔
1176
                                        { types::Typedef("func", ValueType::PageAddr),
4✔
1177
                                          types::Typedef("args", ValueType::List) } },
2✔
1178
                                    types::Contract {
2✔
1179
                                        { types::Typedef("func", ValueType::Closure),
4✔
1180
                                          types::Typedef("args", ValueType::List) } },
2✔
1181
                                    types::Contract {
2✔
1182
                                        { types::Typedef("func", ValueType::CProc),
4✔
1183
                                          types::Typedef("args", ValueType::List) } },
2✔
1184
                                } },
1185
                                { func, args_list });
2✔
UNCOV
1186
                        }
×
1187

1188
                        push(func, context);
356✔
1189
                        for (const Value& a : args_list.constList())
962✔
1190
                            push(a, context);
606✔
1191

1192
                        call(context, static_cast<uint16_t>(args_list.constList().size()));
356✔
1193
                    }
358✔
1194
                    DISPATCH();
310✔
1195
                }
25✔
1196

1197
#pragma endregion
1198

1199
#pragma region "Operators"
1200

1201
                TARGET(BREAKPOINT)
1202
                {
1203
                    {
1204
                        bool breakpoint_active = true;
25✔
1205
                        if (arg == 1)
25✔
1206
                            breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym;
19✔
1207

1208
                        if (m_state.m_features & FeatureVMDebugger && breakpoint_active)
25✔
1209
                        {
1210
                            initDebugger(context);
13✔
1211
                            m_debugger->run(*this, context, /* from_breakpoint= */ true);
13✔
1212
                            m_debugger->resetContextToSavedState(context);
13✔
1213

1214
                            if (m_debugger->shouldQuitVM())
13✔
1215
                                GOTO_HALT();
1✔
1216
                        }
12✔
1217
                    }
1218
                    DISPATCH();
24✔
1219
                }
55,921✔
1220

1221
                TARGET(ADD)
1222
                {
1223
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
55,921✔
1224

1225
                    if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
55,921✔
1226
                        push(Value(a->number() + b->number()), context);
43,386✔
1227
                    else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
12,535✔
1228
                        push(Value(a->string() + b->string()), context);
12,534✔
1229
                    else
1230
                        throw types::TypeCheckingError(
2✔
1231
                            "+",
1✔
1232
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1233
                                types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1234
                            { *a, *b });
1✔
1235
                    DISPATCH();
55,920✔
1236
                }
511,640✔
1237

1238
                TARGET(SUB)
1239
                {
1240
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
511,640✔
1241

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

1251
                TARGET(MUL)
1252
                {
1253
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
510,804✔
1254

1255
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
510,804✔
1256
                        throw types::TypeCheckingError(
2✔
1257
                            "*",
1✔
1258
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1259
                            { *a, *b });
1✔
1260
                    push(Value(a->number() * b->number()), context);
510,803✔
1261
                    DISPATCH();
510,803✔
1262
                }
4,305✔
1263

1264
                TARGET(DIV)
1265
                {
1266
                    Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
4,305✔
1267

1268
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
4,305✔
1269
                        throw types::TypeCheckingError(
2✔
1270
                            "/",
1✔
1271
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1272
                            { *a, *b });
1✔
1273
                    auto d = b->number();
4,304✔
1274
                    if (d == 0)
4,304✔
1275
                        throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1276

1277
                    push(Value(a->number() / d), context);
4,303✔
1278
                    DISPATCH();
4,303✔
1279
                }
2,040✔
1280

1281
                TARGET(GT)
1282
                {
1283
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,040✔
1284
                    push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
2,040✔
1285
                    DISPATCH();
2,040✔
1286
                }
28,207✔
1287

1288
                TARGET(LT)
1289
                {
1290
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,207✔
1291
                    push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
28,207✔
1292
                    DISPATCH();
28,207✔
1293
                }
516,577✔
1294

1295
                TARGET(LE)
1296
                {
1297
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
516,577✔
1298
                    push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
516,577✔
1299
                    DISPATCH();
516,577✔
1300
                }
8,594✔
1301

1302
                TARGET(GE)
1303
                {
1304
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
8,594✔
1305
                    push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
8,594✔
1306
                    DISPATCH();
8,594✔
1307
                }
505,479✔
1308

1309
                TARGET(NEQ)
1310
                {
1311
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
505,479✔
1312
                    push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
505,479✔
1313
                    DISPATCH();
505,479✔
1314
                }
33,044✔
1315

1316
                TARGET(EQ)
1317
                {
1318
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
33,044✔
1319
                    push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
33,044✔
1320
                    DISPATCH();
33,044✔
1321
                }
8,846✔
1322

1323
                TARGET(LEN)
1324
                {
1325
                    const Value* a = popAndResolveAsPtr(context);
8,846✔
1326

1327
                    if (a->valueType() == ValueType::List)
8,846✔
1328
                        push(Value(static_cast<int>(a->constList().size())), context);
5,171✔
1329
                    else if (a->valueType() == ValueType::String)
3,675✔
1330
                        push(Value(static_cast<int>(a->string().size())), context);
3,624✔
1331
                    else if (a->valueType() == ValueType::Dict)
51✔
1332
                        push(Value(static_cast<int>(a->dict().size())), context);
50✔
1333
                    else
1334
                        throw types::TypeCheckingError(
2✔
1335
                            "len",
1✔
1336
                            { { types::Contract { { types::Typedef("value", ValueType::List) } },
3✔
1337
                                types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1338
                                types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1339
                            { *a });
1✔
1340
                    DISPATCH();
8,845✔
1341
                }
839✔
1342

1343
                TARGET(IS_EMPTY)
1344
                {
1345
                    const Value* a = popAndResolveAsPtr(context);
839✔
1346

1347
                    if (a->valueType() == ValueType::List)
839✔
1348
                        push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
226✔
1349
                    else if (a->valueType() == ValueType::String)
613✔
1350
                        push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
596✔
1351
                    else if (a->valueType() == ValueType::Dict)
17✔
1352
                        push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context);
10✔
1353
                    else if (a->valueType() == ValueType::Nil)
7✔
1354
                        push(Builtins::trueSym, context);
6✔
1355
                    else
1356
                        throw types::TypeCheckingError(
2✔
1357
                            "empty?",
1✔
1358
                            { { types::Contract { { types::Typedef("value", ValueType::List) } },
4✔
1359
                                types::Contract { { types::Typedef("value", ValueType::Nil) } },
1✔
1360
                                types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1361
                                types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1362
                            { *a });
1✔
1363
                    DISPATCH();
838✔
1364
                }
415✔
1365

1366
                TARGET(TAIL)
1367
                {
1368
                    Value* const a = popAndResolveAsPtr(context);
415✔
1369
                    push(helper::tail(a), context);
415✔
1370
                    DISPATCH();
415✔
1371
                }
1,491✔
1372

1373
                TARGET(HEAD)
1374
                {
1375
                    Value* const a = popAndResolveAsPtr(context);
1,491✔
1376
                    push(helper::head(a), context);
1,491✔
1377
                    DISPATCH();
1,491✔
1378
                }
2,783✔
1379

1380
                TARGET(IS_NIL)
1381
                {
1382
                    const Value* a = popAndResolveAsPtr(context);
2,783✔
1383
                    push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,783✔
1384
                    DISPATCH();
2,783✔
1385
                }
1,108✔
1386

1387
                TARGET(TO_NUM)
1388
                {
1389
                    const Value* a = popAndResolveAsPtr(context);
1,108✔
1390

1391
                    if (a->valueType() != ValueType::String)
1,108✔
1392
                        throw types::TypeCheckingError(
2✔
1393
                            "toNumber",
1✔
1394
                            { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1395
                            { *a });
1✔
1396

1397
                    double val;
1398
                    if (Utils::isDouble(a->string(), &val))
1,107✔
1399
                        push(Value(val), context);
1,101✔
1400
                    else
1401
                        push(Builtins::nil, context);
6✔
1402
                    DISPATCH();
1,107✔
1403
                }
1,941✔
1404

1405
                TARGET(TO_STR)
1406
                {
1407
                    const Value* a = popAndResolveAsPtr(context);
1,941✔
1408
                    push(Value(a->toString(*this)), context);
1,941✔
1409
                    DISPATCH();
1,941✔
1410
                }
514,332✔
1411

1412
                TARGET(AT)
1413
                {
1414
                    Value& b = *popAndResolveAsPtr(context);
514,332✔
1415
                    Value& a = *popAndResolveAsPtr(context);
514,332✔
1416
                    push(helper::at(a, b, *this), context);
514,332✔
1417
                    DISPATCH();
514,332✔
1418
                }
5,250✔
1419

1420
                TARGET(AT_AT)
1421
                {
1422
                    {
1423
                        const Value* x = popAndResolveAsPtr(context);
5,250✔
1424
                        const Value* y = popAndResolveAsPtr(context);
5,250✔
1425
                        Value& list = *popAndResolveAsPtr(context);
5,250✔
1426

1427
                        push(helper::atAt(x, y, list), context);
5,250✔
1428
                    }
1429
                    DISPATCH();
5,250✔
1430
                }
1,034,767✔
1431

1432
                TARGET(MOD)
1433
                {
1434
                    const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,034,767✔
1435
                    if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,034,767✔
1436
                        throw types::TypeCheckingError(
2✔
1437
                            "mod",
1✔
1438
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1439
                            { *a, *b });
1✔
1440
                    push(Value(std::fmod(a->number(), b->number())), context);
1,034,766✔
1441
                    DISPATCH();
1,034,766✔
1442
                }
63✔
1443

1444
                TARGET(TYPE)
1445
                {
1446
                    const Value* a = popAndResolveAsPtr(context);
63✔
1447
                    push(Value(std::to_string(a->valueType())), context);
63✔
1448
                    DISPATCH();
63✔
1449
                }
5✔
1450

1451
                TARGET(HAS_FIELD)
1452
                {
1453
                    {
1454
                        Value* const field = popAndResolveAsPtr(context);
5✔
1455
                        Value* const closure = popAndResolveAsPtr(context);
5✔
1456
                        if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
5✔
1457
                            throw types::TypeCheckingError(
2✔
1458
                                "hasField",
1✔
1459
                                { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1460
                                { *closure, *field });
1✔
1461

1462
                        auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
4✔
1463
                        if (it == m_state.m_symbols.end())
4✔
1464
                        {
1465
                            push(Builtins::falseSym, context);
2✔
1466
                            DISPATCH();
2✔
1467
                        }
1468

1469
                        auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
2✔
1470
                        push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
2✔
1471
                    }
1472
                    DISPATCH();
2✔
1473
                }
4,329✔
1474

1475
                TARGET(NOT)
1476
                {
1477
                    const Value* a = popAndResolveAsPtr(context);
4,329✔
1478
                    push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
4,329✔
1479
                    DISPATCH();
4,329✔
1480
                }
13,801✔
1481

1482
#pragma endregion
1483

1484
#pragma region "Super Instructions"
1485
                TARGET(LOAD_CONST_LOAD_CONST)
1486
                {
1487
                    UNPACK_ARGS();
13,801✔
1488
                    push(loadConstAsPtr(primary_arg), context);
13,801✔
1489
                    push(loadConstAsPtr(secondary_arg), context);
13,801✔
1490
                    context.inst_exec_counter++;
13,801✔
1491
                    DISPATCH();
13,801✔
1492
                }
23,996✔
1493

1494
                TARGET(LOAD_CONST_STORE)
1495
                {
1496
                    UNPACK_ARGS();
23,996✔
1497
                    store(secondary_arg, loadConstAsPtr(primary_arg), context);
23,996✔
1498
                    DISPATCH();
23,996✔
1499
                }
2,803✔
1500

1501
                TARGET(LOAD_CONST_SET_VAL)
1502
                {
1503
                    UNPACK_ARGS();
2,803✔
1504
                    setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
2,803✔
1505
                    DISPATCH();
2,803✔
1506
                }
71✔
1507

1508
                TARGET(STORE_FROM)
1509
                {
1510
                    UNPACK_ARGS();
71✔
1511
                    store(secondary_arg, loadSymbol(primary_arg, context), context);
71✔
1512
                    DISPATCH();
71✔
1513
                }
942✔
1514

1515
                TARGET(STORE_FROM_INDEX)
1516
                {
1517
                    UNPACK_ARGS();
942✔
1518
                    store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
942✔
1519
                    DISPATCH();
942✔
1520
                }
815✔
1521

1522
                TARGET(SET_VAL_FROM)
1523
                {
1524
                    UNPACK_ARGS();
815✔
1525
                    setVal(secondary_arg, loadSymbol(primary_arg, context), context);
815✔
1526
                    DISPATCH();
815✔
1527
                }
1,800✔
1528

1529
                TARGET(SET_VAL_FROM_INDEX)
1530
                {
1531
                    UNPACK_ARGS();
1,800✔
1532
                    setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,800✔
1533
                    DISPATCH();
1,800✔
1534
                }
292✔
1535

1536
                TARGET(INCREMENT)
1537
                {
1538
                    UNPACK_ARGS();
292✔
1539
                    {
1540
                        Value* var = loadSymbol(primary_arg, context);
292✔
1541

1542
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1543
                        if (var->valueType() == ValueType::Reference)
292✔
UNCOV
1544
                            var = var->reference();
×
1545

1546
                        if (var->valueType() == ValueType::Number)
292✔
1547
                            push(Value(var->number() + secondary_arg), context);
291✔
1548
                        else
1549
                            throw types::TypeCheckingError(
2✔
1550
                                "+",
1✔
1551
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1552
                                { *var, Value(secondary_arg) });
1✔
1553
                    }
1554
                    DISPATCH();
291✔
1555
                }
91,744✔
1556

1557
                TARGET(INCREMENT_BY_INDEX)
1558
                {
1559
                    UNPACK_ARGS();
91,744✔
1560
                    {
1561
                        Value* var = loadSymbolFromIndex(primary_arg, context);
91,744✔
1562

1563
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1564
                        if (var->valueType() == ValueType::Reference)
91,744✔
UNCOV
1565
                            var = var->reference();
×
1566

1567
                        if (var->valueType() == ValueType::Number)
91,744✔
1568
                            push(Value(var->number() + secondary_arg), context);
91,743✔
1569
                        else
1570
                            throw types::TypeCheckingError(
2✔
1571
                                "+",
1✔
1572
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1573
                                { *var, Value(secondary_arg) });
1✔
1574
                    }
1575
                    DISPATCH();
91,743✔
1576
                }
566,051✔
1577

1578
                TARGET(INCREMENT_STORE)
1579
                {
1580
                    UNPACK_ARGS();
566,051✔
1581
                    {
1582
                        Value* var = loadSymbol(primary_arg, context);
566,051✔
1583

1584
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1585
                        if (var->valueType() == ValueType::Reference)
566,051✔
UNCOV
1586
                            var = var->reference();
×
1587

1588
                        if (var->valueType() == ValueType::Number)
566,051✔
1589
                        {
1590
                            auto val = Value(var->number() + secondary_arg);
566,050✔
1591
                            setVal(primary_arg, &val, context);
566,050✔
1592
                        }
566,050✔
1593
                        else
1594
                            throw types::TypeCheckingError(
2✔
1595
                                "+",
1✔
1596
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1597
                                { *var, Value(secondary_arg) });
1✔
1598
                    }
1599
                    DISPATCH();
566,050✔
1600
                }
520,478✔
1601

1602
                TARGET(DECREMENT)
1603
                {
1604
                    UNPACK_ARGS();
520,478✔
1605
                    {
1606
                        Value* var = loadSymbol(primary_arg, context);
520,478✔
1607

1608
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1609
                        if (var->valueType() == ValueType::Reference)
520,478✔
UNCOV
1610
                            var = var->reference();
×
1611

1612
                        if (var->valueType() == ValueType::Number)
520,478✔
1613
                            push(Value(var->number() - secondary_arg), context);
520,477✔
1614
                        else
1615
                            throw types::TypeCheckingError(
2✔
1616
                                "-",
1✔
1617
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1618
                                { *var, Value(secondary_arg) });
1✔
1619
                    }
1620
                    DISPATCH();
520,477✔
1621
                }
195,503✔
1622

1623
                TARGET(DECREMENT_BY_INDEX)
1624
                {
1625
                    UNPACK_ARGS();
195,503✔
1626
                    {
1627
                        Value* var = loadSymbolFromIndex(primary_arg, context);
195,503✔
1628

1629
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1630
                        if (var->valueType() == ValueType::Reference)
195,503✔
UNCOV
1631
                            var = var->reference();
×
1632

1633
                        if (var->valueType() == ValueType::Number)
195,503✔
1634
                            push(Value(var->number() - secondary_arg), context);
195,502✔
1635
                        else
1636
                            throw types::TypeCheckingError(
2✔
1637
                                "-",
1✔
1638
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1639
                                { *var, Value(secondary_arg) });
1✔
1640
                    }
1641
                    DISPATCH();
195,502✔
1642
                }
512,674✔
1643

1644
                TARGET(DECREMENT_STORE)
1645
                {
1646
                    UNPACK_ARGS();
512,674✔
1647
                    {
1648
                        Value* var = loadSymbol(primary_arg, context);
512,674✔
1649

1650
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
1651
                        if (var->valueType() == ValueType::Reference)
512,674✔
UNCOV
1652
                            var = var->reference();
×
1653

1654
                        if (var->valueType() == ValueType::Number)
512,674✔
1655
                        {
1656
                            auto val = Value(var->number() - secondary_arg);
512,673✔
1657
                            setVal(primary_arg, &val, context);
512,673✔
1658
                        }
512,673✔
1659
                        else
1660
                            throw types::TypeCheckingError(
2✔
1661
                                "-",
1✔
1662
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1663
                                { *var, Value(secondary_arg) });
1✔
1664
                    }
1665
                    DISPATCH();
512,673✔
1666
                }
2✔
1667

1668
                TARGET(STORE_TAIL)
1669
                {
1670
                    UNPACK_ARGS();
2✔
1671
                    {
1672
                        Value* list = loadSymbol(primary_arg, context);
2✔
1673
                        Value tail = helper::tail(list);
2✔
1674
                        store(secondary_arg, &tail, context);
2✔
1675
                    }
2✔
1676
                    DISPATCH();
2✔
1677
                }
16✔
1678

1679
                TARGET(STORE_TAIL_BY_INDEX)
1680
                {
1681
                    UNPACK_ARGS();
16✔
1682
                    {
1683
                        Value* list = loadSymbolFromIndex(primary_arg, context);
16✔
1684
                        Value tail = helper::tail(list);
16✔
1685
                        store(secondary_arg, &tail, context);
16✔
1686
                    }
16✔
1687
                    DISPATCH();
16✔
1688
                }
5✔
1689

1690
                TARGET(STORE_HEAD)
1691
                {
1692
                    UNPACK_ARGS();
5✔
1693
                    {
1694
                        Value* list = loadSymbol(primary_arg, context);
5✔
1695
                        Value head = helper::head(list);
5✔
1696
                        store(secondary_arg, &head, context);
5✔
1697
                    }
5✔
1698
                    DISPATCH();
5✔
1699
                }
46✔
1700

1701
                TARGET(STORE_HEAD_BY_INDEX)
1702
                {
1703
                    UNPACK_ARGS();
46✔
1704
                    {
1705
                        Value* list = loadSymbolFromIndex(primary_arg, context);
46✔
1706
                        Value head = helper::head(list);
46✔
1707
                        store(secondary_arg, &head, context);
46✔
1708
                    }
46✔
1709
                    DISPATCH();
46✔
1710
                }
5,588✔
1711

1712
                TARGET(STORE_LIST)
1713
                {
1714
                    UNPACK_ARGS();
5,588✔
1715
                    {
1716
                        Value l = createList(primary_arg, context);
5,588✔
1717
                        store(secondary_arg, &l, context);
5,588✔
1718
                    }
5,588✔
1719
                    DISPATCH();
5,588✔
1720
                }
6✔
1721

1722
                TARGET(SET_VAL_TAIL)
1723
                {
1724
                    UNPACK_ARGS();
6✔
1725
                    {
1726
                        Value* list = loadSymbol(primary_arg, context);
6✔
1727
                        Value tail = helper::tail(list);
6✔
1728
                        setVal(secondary_arg, &tail, context);
6✔
1729
                    }
6✔
1730
                    DISPATCH();
6✔
1731
                }
2✔
1732

1733
                TARGET(SET_VAL_TAIL_BY_INDEX)
1734
                {
1735
                    UNPACK_ARGS();
2✔
1736
                    {
1737
                        Value* list = loadSymbolFromIndex(primary_arg, context);
2✔
1738
                        Value tail = helper::tail(list);
2✔
1739
                        setVal(secondary_arg, &tail, context);
2✔
1740
                    }
2✔
1741
                    DISPATCH();
2✔
1742
                }
2✔
1743

1744
                TARGET(SET_VAL_HEAD)
1745
                {
1746
                    UNPACK_ARGS();
2✔
1747
                    {
1748
                        Value* list = loadSymbol(primary_arg, context);
2✔
1749
                        Value head = helper::head(list);
2✔
1750
                        setVal(secondary_arg, &head, context);
2✔
1751
                    }
2✔
1752
                    DISPATCH();
2✔
1753
                }
2✔
1754

1755
                TARGET(SET_VAL_HEAD_BY_INDEX)
1756
                {
1757
                    UNPACK_ARGS();
2✔
1758
                    {
1759
                        Value* list = loadSymbolFromIndex(primary_arg, context);
2✔
1760
                        Value head = helper::head(list);
2✔
1761
                        setVal(secondary_arg, &head, context);
2✔
1762
                    }
2✔
1763
                    DISPATCH();
2✔
1764
                }
10,130✔
1765

1766
                TARGET(CALL_BUILTIN)
1767
                {
1768
                    UNPACK_ARGS();
10,130✔
1769
                    // no stack size check because we do not push IP/PP since we are just calling a builtin
1770
                    callBuiltin(
20,260✔
1771
                        context,
10,130✔
1772
                        Builtins::builtins[primary_arg].second,
10,130✔
1773
                        secondary_arg,
10,130✔
1774
                        /* remove_return_address= */ true,
1775
                        /* remove_builtin= */ false);
1776
                    if (!m_running)
10,130✔
UNCOV
1777
                        GOTO_HALT();
×
1778
                    DISPATCH();
10,130✔
1779
                }
22,040✔
1780

1781
                TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1782
                {
1783
                    UNPACK_ARGS();
22,040✔
1784
                    // no stack size check because we do not push IP/PP since we are just calling a builtin
1785
                    callBuiltin(
44,080✔
1786
                        context,
22,040✔
1787
                        Builtins::builtins[primary_arg].second,
22,040✔
1788
                        secondary_arg,
22,040✔
1789
                        // we didn't have a PUSH_RETURN_ADDRESS instruction before,
1790
                        // so do not attempt to remove (pp,ip) from the stack: they're not there!
1791
                        /* remove_return_address= */ false,
1792
                        /* remove_builtin= */ false);
1793
                    if (!m_running)
22,040✔
UNCOV
1794
                        GOTO_HALT();
×
1795
                    DISPATCH();
22,040✔
1796
                }
1,022✔
1797

1798
                TARGET(LT_CONST_JUMP_IF_FALSE)
1799
                {
1800
                    UNPACK_ARGS();
1,022✔
1801
                    const Value* sym = popAndResolveAsPtr(context);
1,022✔
1802
                    if (!(*sym < *loadConstAsPtr(primary_arg)))
1,022✔
1803
                        jump(secondary_arg, context);
129✔
1804
                    DISPATCH();
1,022✔
1805
                }
23,011✔
1806

1807
                TARGET(LT_CONST_JUMP_IF_TRUE)
1808
                {
1809
                    UNPACK_ARGS();
23,011✔
1810
                    const Value* sym = popAndResolveAsPtr(context);
23,011✔
1811
                    if (*sym < *loadConstAsPtr(primary_arg))
23,011✔
1812
                        jump(secondary_arg, context);
11,967✔
1813
                    DISPATCH();
23,011✔
1814
                }
14,876✔
1815

1816
                TARGET(LT_SYM_JUMP_IF_FALSE)
1817
                {
1818
                    UNPACK_ARGS();
14,876✔
1819
                    const Value* sym = popAndResolveAsPtr(context);
14,876✔
1820
                    if (!(*sym < *loadSymbol(primary_arg, context)))
14,876✔
1821
                        jump(secondary_arg, context);
941✔
1822
                    DISPATCH();
14,876✔
1823
                }
172,771✔
1824

1825
                TARGET(GT_CONST_JUMP_IF_TRUE)
1826
                {
1827
                    UNPACK_ARGS();
172,771✔
1828
                    const Value* sym = popAndResolveAsPtr(context);
172,771✔
1829
                    const Value* cst = loadConstAsPtr(primary_arg);
172,771✔
1830
                    if (*cst < *sym)
172,771✔
1831
                        jump(secondary_arg, context);
86,750✔
1832
                    DISPATCH();
172,771✔
1833
                }
4,336✔
1834

1835
                TARGET(GT_CONST_JUMP_IF_FALSE)
1836
                {
1837
                    UNPACK_ARGS();
4,336✔
1838
                    const Value* sym = popAndResolveAsPtr(context);
4,336✔
1839
                    const Value* cst = loadConstAsPtr(primary_arg);
4,336✔
1840
                    if (!(*cst < *sym))
4,336✔
1841
                        jump(secondary_arg, context);
1,098✔
1842
                    DISPATCH();
4,336✔
1843
                }
159✔
1844

1845
                TARGET(GT_SYM_JUMP_IF_FALSE)
1846
                {
1847
                    UNPACK_ARGS();
159✔
1848
                    const Value* sym = popAndResolveAsPtr(context);
159✔
1849
                    const Value* rhs = loadSymbol(primary_arg, context);
159✔
1850
                    if (!(*rhs < *sym))
159✔
1851
                        jump(secondary_arg, context);
31✔
1852
                    DISPATCH();
159✔
1853
                }
502,760✔
1854

1855
                TARGET(EQ_CONST_JUMP_IF_TRUE)
1856
                {
1857
                    UNPACK_ARGS();
502,760✔
1858
                    const Value* sym = popAndResolveAsPtr(context);
502,760✔
1859
                    if (*sym == *loadConstAsPtr(primary_arg))
502,760✔
1860
                        jump(secondary_arg, context);
12,230✔
1861
                    DISPATCH();
502,760✔
1862
                }
89,423✔
1863

1864
                TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1865
                {
1866
                    UNPACK_ARGS();
89,423✔
1867
                    const Value* sym = popAndResolveAsPtr(context);
89,423✔
1868
                    if (*sym == *loadSymbolFromIndex(primary_arg, context))
89,423✔
1869
                        jump(secondary_arg, context);
661✔
1870
                    DISPATCH();
89,423✔
1871
                }
22✔
1872

1873
                TARGET(NEQ_CONST_JUMP_IF_TRUE)
1874
                {
1875
                    UNPACK_ARGS();
22✔
1876
                    const Value* sym = popAndResolveAsPtr(context);
22✔
1877
                    if (*sym != *loadConstAsPtr(primary_arg))
22✔
1878
                        jump(secondary_arg, context);
4✔
1879
                    DISPATCH();
22✔
1880
                }
46✔
1881

1882
                TARGET(NEQ_SYM_JUMP_IF_FALSE)
1883
                {
1884
                    UNPACK_ARGS();
46✔
1885
                    const Value* sym = popAndResolveAsPtr(context);
46✔
1886
                    if (*sym == *loadSymbol(primary_arg, context))
46✔
1887
                        jump(secondary_arg, context);
17✔
1888
                    DISPATCH();
46✔
1889
                }
39,582✔
1890

1891
                TARGET(CALL_SYMBOL)
1892
                {
1893
                    UNPACK_ARGS();
39,582✔
1894
                    call(context, secondary_arg, /* function_ptr= */ loadSymbol(primary_arg, context));
39,582✔
1895
                    if (!m_running)
39,582✔
UNCOV
1896
                        GOTO_HALT();
×
1897
                    DISPATCH();
39,582✔
1898
                }
1,314✔
1899

1900
                TARGET(CALL_SYMBOL_BY_INDEX)
1901
                {
1902
                    UNPACK_ARGS();
1,314✔
1903
                    call(context, secondary_arg, /* function_ptr= */ loadSymbolFromIndex(primary_arg, context));
1,314✔
1904
                    if (!m_running)
1,314✔
UNCOV
1905
                        GOTO_HALT();
×
1906
                    DISPATCH();
1,314✔
1907
                }
109,888✔
1908

1909
                TARGET(CALL_CURRENT_PAGE)
1910
                {
1911
                    UNPACK_ARGS();
109,888✔
1912
                    context.last_symbol = primary_arg;
109,888✔
1913
                    call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,888✔
1914
                    if (!m_running)
109,888✔
UNCOV
1915
                        GOTO_HALT();
×
1916
                    DISPATCH();
109,888✔
1917
                }
4,667✔
1918

1919
                TARGET(GET_FIELD_FROM_SYMBOL)
1920
                {
1921
                    UNPACK_ARGS();
4,667✔
1922
                    push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
4,667✔
1923
                    DISPATCH();
4,667✔
1924
                }
580✔
1925

1926
                TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1927
                {
1928
                    UNPACK_ARGS();
580✔
1929
                    push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
580✔
1930
                    DISPATCH();
580✔
1931
                }
547,628✔
1932

1933
                TARGET(AT_SYM_SYM)
1934
                {
1935
                    UNPACK_ARGS();
547,628✔
1936
                    push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
547,628✔
1937
                    DISPATCH();
547,628✔
1938
                }
104✔
1939

1940
                TARGET(AT_SYM_INDEX_SYM_INDEX)
1941
                {
1942
                    UNPACK_ARGS();
104✔
1943
                    push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
104✔
1944
                    DISPATCH();
104✔
1945
                }
11,896✔
1946

1947
                TARGET(AT_SYM_INDEX_CONST)
1948
                {
1949
                    UNPACK_ARGS();
11,896✔
1950
                    push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
11,896✔
1951
                    DISPATCH();
11,896✔
1952
                }
10✔
1953

1954
                TARGET(CHECK_TYPE_OF)
1955
                {
1956
                    UNPACK_ARGS();
10✔
1957
                    const Value* sym = loadSymbol(primary_arg, context);
10✔
1958
                    const Value* cst = loadConstAsPtr(secondary_arg);
10✔
1959
                    push(
10✔
1960
                        cst->valueType() == ValueType::String &&
20✔
1961
                                std::to_string(sym->valueType()) == cst->string()
10✔
1962
                            ? Builtins::trueSym
1963
                            : Builtins::falseSym,
1964
                        context);
10✔
1965
                    DISPATCH();
10✔
1966
                }
240✔
1967

1968
                TARGET(CHECK_TYPE_OF_BY_INDEX)
1969
                {
1970
                    UNPACK_ARGS();
240✔
1971
                    const Value* sym = loadSymbolFromIndex(primary_arg, context);
240✔
1972
                    const Value* cst = loadConstAsPtr(secondary_arg);
240✔
1973
                    push(
240✔
1974
                        cst->valueType() == ValueType::String &&
480✔
1975
                                std::to_string(sym->valueType()) == cst->string()
240✔
1976
                            ? Builtins::trueSym
1977
                            : Builtins::falseSym,
1978
                        context);
240✔
1979
                    DISPATCH();
240✔
1980
                }
23,356✔
1981

1982
                TARGET(APPEND_IN_PLACE_SYM)
1983
                {
1984
                    UNPACK_ARGS();
23,356✔
1985
                    listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
23,356✔
1986
                    DISPATCH();
23,356✔
1987
                }
26✔
1988

1989
                TARGET(APPEND_IN_PLACE_SYM_INDEX)
1990
                {
1991
                    UNPACK_ARGS();
26✔
1992
                    listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
26✔
1993
                    DISPATCH();
26✔
1994
                }
322✔
1995

1996
                TARGET(STORE_LEN)
1997
                {
1998
                    UNPACK_ARGS();
322✔
1999
                    {
2000
                        Value* a = loadSymbolFromIndex(primary_arg, context);
322✔
2001
                        Value len;
322✔
2002
                        if (a->valueType() == ValueType::List)
322✔
2003
                            len = Value(static_cast<int>(a->constList().size()));
146✔
2004
                        else if (a->valueType() == ValueType::String)
176✔
2005
                            len = Value(static_cast<int>(a->string().size()));
175✔
2006
                        else
2007
                            throw types::TypeCheckingError(
2✔
2008
                                "len",
1✔
2009
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2010
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2011
                                { *a });
1✔
2012
                        store(secondary_arg, &len, context);
321✔
2013
                    }
322✔
2014
                    DISPATCH();
321✔
2015
                }
30,361✔
2016

2017
                TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
2018
                {
2019
                    UNPACK_ARGS();
30,361✔
2020
                    {
2021
                        const Value* sym = loadSymbol(primary_arg, context);
30,361✔
2022
                        Value size;
30,361✔
2023

2024
                        if (sym->valueType() == ValueType::List)
30,361✔
2025
                            size = Value(static_cast<int>(sym->constList().size()));
24,215✔
2026
                        else if (sym->valueType() == ValueType::String)
6,146✔
2027
                            size = Value(static_cast<int>(sym->string().size()));
6,145✔
2028
                        else
2029
                            throw types::TypeCheckingError(
2✔
2030
                                "len",
1✔
2031
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2032
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2033
                                { *sym });
1✔
2034

2035
                        if (!(*popAndResolveAsPtr(context) < size))
30,360✔
2036
                            jump(secondary_arg, context);
4,035✔
2037
                    }
30,361✔
2038
                    DISPATCH();
30,360✔
2039
                }
579✔
2040

2041
                TARGET(MUL_BY)
2042
                {
2043
                    UNPACK_ARGS();
579✔
2044
                    {
2045
                        Value* var = loadSymbol(primary_arg, context);
579✔
2046
                        const int other = static_cast<int>(secondary_arg) - 2048;
579✔
2047

2048
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
2049
                        if (var->valueType() == ValueType::Reference)
579✔
UNCOV
2050
                            var = var->reference();
×
2051

2052
                        if (var->valueType() == ValueType::Number)
579✔
2053
                            push(Value(var->number() * other), context);
578✔
2054
                        else
2055
                            throw types::TypeCheckingError(
2✔
2056
                                "*",
1✔
2057
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2058
                                { *var, Value(other) });
1✔
2059
                    }
2060
                    DISPATCH();
578✔
2061
                }
63✔
2062

2063
                TARGET(MUL_BY_INDEX)
2064
                {
2065
                    UNPACK_ARGS();
63✔
2066
                    {
2067
                        Value* var = loadSymbolFromIndex(primary_arg, context);
63✔
2068
                        const int other = static_cast<int>(secondary_arg) - 2048;
63✔
2069

2070
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
2071
                        if (var->valueType() == ValueType::Reference)
63✔
UNCOV
2072
                            var = var->reference();
×
2073

2074
                        if (var->valueType() == ValueType::Number)
63✔
2075
                            push(Value(var->number() * other), context);
62✔
2076
                        else
2077
                            throw types::TypeCheckingError(
2✔
2078
                                "*",
1✔
2079
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2080
                                { *var, Value(other) });
1✔
2081
                    }
2082
                    DISPATCH();
62✔
2083
                }
3✔
2084

2085
                TARGET(MUL_SET_VAL)
2086
                {
2087
                    UNPACK_ARGS();
3✔
2088
                    {
2089
                        Value* var = loadSymbol(primary_arg, context);
3✔
2090
                        const int other = static_cast<int>(secondary_arg) - 2048;
3✔
2091

2092
                        // use internal reference, shouldn't break anything so far, unless it's already a ref
2093
                        if (var->valueType() == ValueType::Reference)
3✔
UNCOV
2094
                            var = var->reference();
×
2095

2096
                        if (var->valueType() == ValueType::Number)
3✔
2097
                        {
2098
                            auto val = Value(var->number() * other);
2✔
2099
                            setVal(primary_arg, &val, context);
2✔
2100
                        }
2✔
2101
                        else
2102
                            throw types::TypeCheckingError(
2✔
2103
                                "*",
1✔
2104
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2105
                                { *var, Value(other) });
1✔
2106
                    }
2107
                    DISPATCH();
2✔
2108
                }
510,128✔
2109

2110
                TARGET(FUSED_MATH)
2111
                {
2112
                    const auto op1 = static_cast<Instruction>(padding),
510,128✔
2113
                               op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
510,128✔
2114
                               op3 = static_cast<Instruction>(arg & 0x00ff);
510,128✔
2115
                    const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
510,128✔
2116

2117
                    const Value* d = popAndResolveAsPtr(context);
510,128✔
2118
                    const Value* c = popAndResolveAsPtr(context);
510,128✔
2119
                    const Value* b = popAndResolveAsPtr(context);
510,128✔
2120

2121
                    if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
510,128✔
2122
                        throw types::TypeCheckingError(
2✔
2123
                            helper::mathInstToStr(op1),
1✔
2124
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2125
                            { *c, *d });
1✔
2126

2127
                    double temp = helper::doMath(c->number(), d->number(), op1);
510,127✔
2128
                    if (b->valueType() != ValueType::Number)
510,127✔
2129
                        throw types::TypeCheckingError(
4✔
2130
                            helper::mathInstToStr(op2),
2✔
2131
                            { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
2✔
2132
                            { *b, Value(temp) });
2✔
2133
                    temp = helper::doMath(b->number(), temp, op2);
510,125✔
2134

2135
                    if (arg_count == 2)
510,125✔
2136
                        push(Value(temp), context);
510,084✔
2137
                    else if (arg_count == 3)
41✔
2138
                    {
2139
                        const Value* a = popAndResolveAsPtr(context);
41✔
2140
                        if (a->valueType() != ValueType::Number)
41✔
2141
                            throw types::TypeCheckingError(
2✔
2142
                                helper::mathInstToStr(op3),
1✔
2143
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2144
                                { *a, Value(temp) });
1✔
2145

2146
                        temp = helper::doMath(a->number(), temp, op3);
40✔
2147
                        push(Value(temp), context);
40✔
2148
                    }
40✔
2149
                    else
UNCOV
2150
                        throw Error(
×
UNCOV
2151
                            fmt::format(
×
UNCOV
2152
                                "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
×
UNCOV
2153
                                arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
×
2154
                    DISPATCH();
510,124✔
2155
                }
2156
#pragma endregion
2157
            }
142✔
2158
#if ARK_USE_COMPUTED_GOTOS
2159
        dispatch_end:
2160
            do
142✔
2161
            {
2162
            } while (false);
142✔
2163
#endif
2164
        }
2165
    }
236✔
2166

2167
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,053✔
2168
    {
2,053✔
2169
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,193✔
2170
        {
2171
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,140✔
2172
                return id;
2,047✔
2173
        }
2,096,140✔
2174
        return MaxValue16Bits;
6✔
2175
    }
2,053✔
2176

2177
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, ExecutionContext& context, const bool skip_function)
7✔
2178
    {
7✔
2179
        std::vector<std::string> arg_names;
7✔
2180
        arg_names.reserve(expected_arg_count + 1);
7✔
2181

2182
        std::size_t index = 0;
7✔
2183
        while (m_state.inst(context.pp, index) == STORE ||
14✔
2184
               m_state.inst(context.pp, index) == STORE_REF)
7✔
2185
        {
UNCOV
2186
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
UNCOV
2187
            arg_names.insert(arg_names.begin(), m_state.m_symbols[id]);
×
UNCOV
2188
            index += 4;
×
UNCOV
2189
        }
×
2190
        // we have no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2191
        if (arg_names.empty() && index == 0)
7✔
2192
        {
2193
            for (std::size_t i = 0; i < expected_arg_count; ++i)
5✔
2194
                arg_names.emplace_back(1, static_cast<char>('a' + i));
2✔
2195
        }
3✔
2196
        if (expected_arg_count > 0)
7✔
2197
            arg_names.insert(arg_names.begin(), "");  // for formatting, so that we have a space between the function and the args
6✔
2198

2199
        std::vector<std::string> arg_vals;
7✔
2200
        arg_vals.reserve(passed_arg_count + 1);
7✔
2201

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

2208
        // set ip/pp to the callee location so that the error can pinpoint the line
2209
        // where the bad call happened
2210
        if (context.sp >= 2 + passed_arg_count)
7✔
2211
        {
2212
            // -2/-3 instead of -1/-2 to skip over the function pushed on the stack
2213
            context.ip = context.stack[context.sp - 1 - (skip_function ? 1 : 0) - passed_arg_count].pageAddr();
7✔
2214
            context.pp = context.stack[context.sp - 2 - (skip_function ? 1 : 0) - passed_arg_count].pageAddr();
7✔
2215
            context.sp -= 2;
7✔
2216
            returnFromFuncCall(context);
7✔
2217
        }
7✔
2218

2219
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
14✔
2220
            ? m_state.m_symbols[context.last_symbol]
7✔
UNCOV
2221
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2222

2223
        throwVMError(
7✔
2224
            ErrorKind::Arity,
2225
            fmt::format(
14✔
2226
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
7✔
2227
                function_name,
2228
                fmt::join(arg_vals, " "),
7✔
2229
                passed_arg_count,
2230
                passed_arg_count > 1 ? "s" : "",
7✔
2231
                expected_arg_count,
2232
                function_name,
2233
                fmt::join(arg_names, " ")));
7✔
2234
    }
14✔
2235

2236
    void VM::initDebugger(ExecutionContext& context)
14✔
2237
    {
14✔
2238
        if (!m_debugger)
14✔
UNCOV
2239
            m_debugger = std::make_unique<Debugger>(context, m_state.m_libenv, m_state.m_symbols, m_state.m_constants);
×
2240
        else
2241
            m_debugger->saveState(context);
14✔
2242
    }
14✔
2243

2244
    void VM::showBacktraceWithException(const std::exception& e, ExecutionContext& context)
1✔
2245
    {
1✔
2246
        std::string text = e.what();
1✔
2247
        if (!text.empty() && text.back() != '\n')
1✔
UNCOV
2248
            text += '\n';
×
2249
        fmt::println(std::cerr, "{}", text);
1✔
2250

2251
        // 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
2252
        const bool error_from_debugger = m_debugger && m_debugger->isRunning();
1✔
2253
        if (m_state.m_features & FeatureVMDebugger && !error_from_debugger)
1✔
2254
            initDebugger(context);
1✔
2255

2256
        const std::size_t saved_ip = context.ip;
1✔
2257
        const std::size_t saved_pp = context.pp;
1✔
2258
        const uint16_t saved_sp = context.sp;
1✔
2259

2260
        backtrace(context);
1✔
2261

2262
        fmt::println(
1✔
2263
            std::cerr,
2264
            "At IP: {}, PP: {}, SP: {}",
1✔
2265
            // dividing by 4 because the instructions are actually on 4 bytes
2266
            fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)),
1✔
2267
            fmt::styled(saved_pp, fmt::fg(fmt::color::green)),
1✔
2268
            fmt::styled(saved_sp, fmt::fg(fmt::color::yellow)));
1✔
2269

2270
        if (m_debugger && !error_from_debugger)
1✔
2271
        {
2272
            m_debugger->resetContextToSavedState(context);
1✔
2273
            m_debugger->run(*this, context, /* from_breakpoint= */ false);
1✔
2274
        }
1✔
2275

2276
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2277
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2278
        m_exit_code = 0;
2279
#else
2280
        m_exit_code = 1;
1✔
2281
#endif
2282
    }
1✔
2283

2284
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,338✔
2285
    {
2,338✔
2286
        std::optional<InstLoc> match = std::nullopt;
2,338✔
2287

2288
        for (const auto location : m_state.m_inst_locations)
11,132✔
2289
        {
2290
            if (location.page_pointer == pp && !match)
8,794✔
2291
                match = location;
2,338✔
2292

2293
            // select the best match: we want to find the location that's nearest our instruction pointer,
2294
            // but not equal to it as the IP will always be pointing to the next instruction,
2295
            // not yet executed. Thus, the erroneous instruction is the previous one.
2296
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
8,794✔
2297
                match = location;
2,577✔
2298

2299
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2300
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
8,794✔
2301
                break;
2,084✔
2302
        }
8,794✔
2303

2304
        return match;
2,338✔
2305
    }
2306

UNCOV
2307
    std::string VM::debugShowSource() const
×
UNCOV
2308
    {
×
UNCOV
2309
        const auto& context = m_execution_contexts.front();
×
UNCOV
2310
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
UNCOV
2311
        if (maybe_source_loc)
×
2312
        {
UNCOV
2313
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
UNCOV
2314
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
UNCOV
2315
        }
×
UNCOV
2316
        return "No source location found";
×
UNCOV
2317
    }
×
2318

2319
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
265✔
2320
    {
265✔
2321
        constexpr std::size_t max_consecutive_traces = 7;
265✔
2322

2323
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
265✔
2324
        if (maybe_location)
265✔
2325
        {
2326
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
265✔
2327

2328
            if (Utils::fileExists(filename))
265✔
2329
                Diagnostics::makeContext(
526✔
2330
                    Diagnostics::ErrorLocation {
526✔
2331
                        .filename = filename,
263✔
2332
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
263✔
2333
                        .end = std::nullopt,
263✔
2334
                        .maybe_content = std::nullopt },
263✔
2335
                    os,
263✔
2336
                    /* maybe_context= */ std::nullopt,
263✔
2337
                    /* colorize= */ colorize);
263✔
2338
            fmt::println(os, "");
265✔
2339
        }
265✔
2340

2341
        if (context.fc > 1)
265✔
2342
        {
2343
            // display call stack trace
2344
            const ScopeView old_scope = context.locals.back();
7✔
2345

2346
            std::string previous_trace;
7✔
2347
            std::size_t displayed_traces = 0;
7✔
2348
            std::size_t consecutive_similar_traces = 0;
7✔
2349

2350
            while (context.fc != 0 && context.pp != 0 && context.sp > 0)
2,060✔
2351
            {
2352
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,053✔
2353
                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,053✔
2354

2355
                const uint16_t id = findNearestVariableIdWithValue(
2,053✔
2356
                    Value(static_cast<PageAddr_t>(context.pp)),
2,053✔
2357
                    context);
2,053✔
2358
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,053✔
2359

2360
                if (func_name + loc_as_text != previous_trace)
2,053✔
2361
                {
2362
                    fmt::println(
14✔
2363
                        os,
7✔
2364
                        "[{:4}] In function `{}'{}",
7✔
2365
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
7✔
2366
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
7✔
2367
                        loc_as_text);
2368
                    previous_trace = func_name + loc_as_text;
7✔
2369
                    ++displayed_traces;
7✔
2370
                    consecutive_similar_traces = 0;
7✔
2371
                }
7✔
2372
                else if (consecutive_similar_traces == 0)
2,046✔
2373
                {
2374
                    fmt::println(os, "       ...");
1✔
2375
                    ++consecutive_similar_traces;
1✔
2376
                }
1✔
2377

2378
                const Value* ip;
2,053✔
2379
                do
2,060✔
2380
                {
2381
                    ip = popAndResolveAsPtr(context);
2,060✔
2382
                } while (ip->valueType() != ValueType::InstPtr);
2,060✔
2383

2384
                context.ip = ip->pageAddr();
2,053✔
2385
                context.pp = pop(context)->pageAddr();
2,053✔
2386
                returnFromFuncCall(context);
2,053✔
2387

2388
                if (displayed_traces > max_consecutive_traces)
2,053✔
2389
                {
UNCOV
2390
                    fmt::println(os, "       ...");
×
UNCOV
2391
                    break;
×
2392
                }
2393
            }
2,053✔
2394

2395
            if (context.pp == 0)
7✔
2396
            {
2397
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
7✔
2398
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
7✔
2399
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
7✔
2400
            }
7✔
2401

2402
            // display variables values in the current scope
2403
            fmt::println(os, "\nCurrent scope variables values:");
7✔
2404
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
8✔
2405
            {
2406
                fmt::println(
2✔
2407
                    os,
1✔
2408
                    "{} = {}",
1✔
2409
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2410
                    old_scope.atPos(i).second.toString(*this));
1✔
2411
            }
1✔
2412
        }
7✔
2413
    }
265✔
2414
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc