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

ArkScript-lang / Ark / 24081083367

07 Apr 2026 12:22PM UTC coverage: 93.943% (+0.1%) from 93.837%
24081083367

Pull #673

github

web-flow
Merge a917324f5 into b3a547cd0
Pull Request #673: Feat/inst tracking

151 of 170 new or added lines in 7 files covered. (88.82%)

20 existing lines in 1 file now uncovered.

9864 of 10500 relevant lines covered (93.94%)

648889.58 hits per line

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

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

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

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

18
namespace Ark
19
{
20
    using namespace internal;
21

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

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

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

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

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

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

62
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context, const bool push_with_env)
4,615✔
63
    {
4,615✔
64
        if (closure->valueType() != ValueType::Closure)
4,615✔
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)
9,228✔
84
        {
85
            if (push_with_env)
4,613✔
86
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
2,583✔
87
            else
88
                return *field;
2,030✔
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
    }
4,615✔
108

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

213
        m_shared_lib_objects.emplace_back(lib);
1✔
214

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

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

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

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

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

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

253
        ExecutionContext* ctx = nullptr;
17✔
254

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

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

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

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

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

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

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

302
        return ctx;
17✔
303
    }
17✔
304

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

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

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

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

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

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

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

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

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

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

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

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

400
    int VM::run(const bool fail_with_exception)
304✔
401
    {
304✔
402
        init();
304✔
403
        if (m_state.m_features & FeatureVMDebugger)
304✔
404
            safeRun<true>(*m_execution_contexts[0], 0, fail_with_exception);
7✔
405
        else
406
            safeRun<false>(*m_execution_contexts[0], 0, fail_with_exception);
297✔
407
        return m_exit_code;
304✔
408
    }
409

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

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

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

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

585
        try
586
        {
587
            uint8_t inst = 0;
335✔
588
            uint8_t padding = 0;
335✔
589
            uint16_t arg = 0;
335✔
590
            uint16_t primary_arg = 0;
335✔
591
            uint16_t secondary_arg = 0;
335✔
592

593
            m_running = true;
335✔
594

595
            DISPATCH();
335✔
596
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
597
            {
598
#if !ARK_USE_COMPUTED_GOTOS
599
            dispatch_opcode:
600
                switch (inst)
601
#endif
UNCOV
602
                {
×
603
#pragma region "Instructions"
604
                    TARGET(NOP)
605
                    {
UNCOV
606
                        DISPATCH();
×
607
                    }
7,322,780✔
608

609
                    TARGET(LOAD_FAST)
610
                    {
611
                        push(loadSymbol(arg, context), context);
7,322,780✔
612
                        DISPATCH();
7,322,780✔
613
                    }
370,398✔
614

615
                    TARGET(LOAD_FAST_BY_INDEX)
616
                    {
617
                        push(loadSymbolFromIndex(arg, context), context);
370,398✔
618
                        DISPATCH();
370,398✔
619
                    }
4,830✔
620

621
                    TARGET(LOAD_SYMBOL)
622
                    {
623
                        // force resolving the reference
624
                        push(*loadSymbol(arg, context), context);
4,830✔
625
                        DISPATCH();
4,830✔
626
                    }
660,872✔
627

628
                    TARGET(LOAD_CONST)
629
                    {
630
                        push(loadConstAsPtr(arg), context);
660,872✔
631
                        DISPATCH();
660,872✔
632
                    }
44,555✔
633

634
                    TARGET(POP_JUMP_IF_TRUE)
635
                    {
636
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
57,661✔
637
                            jump(arg, context);
13,106✔
638
                        DISPATCH();
44,555✔
639
                    }
426,153✔
640

641
                    TARGET(STORE)
642
                    {
643
                        store(arg, popAndResolveAsPtr(context), context);
426,153✔
644
                        DISPATCH();
426,153✔
645
                    }
2,952✔
646

647
                    TARGET(STORE_REF)
648
                    {
649
                        // Not resolving a potential ref is on purpose!
650
                        // This instruction is only used by functions when storing arguments
651
                        const Value* tmp = pop(context);
2,952✔
652
                        store(arg, tmp, context);
2,952✔
653
                        DISPATCH();
2,952✔
654
                    }
553,117✔
655

656
                    TARGET(SET_VAL)
657
                    {
658
                        setVal(arg, popAndResolveAsPtr(context), context);
553,117✔
659
                        DISPATCH();
553,117✔
660
                    }
1,035,529✔
661

662
                    TARGET(POP_JUMP_IF_FALSE)
663
                    {
664
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
1,040,283✔
665
                            jump(arg, context);
4,754✔
666
                        DISPATCH();
1,035,529✔
667
                    }
620,944✔
668

669
                    TARGET(JUMP)
670
                    {
671
                        jump(arg, context);
620,944✔
672
                        DISPATCH();
620,944✔
673
                    }
160,736✔
674

675
                    TARGET(RET)
676
                    {
677
                        {
678
                            Value ts = *popAndResolveAsPtr(context);
160,736✔
679
                            Value ts1 = *popAndResolveAsPtr(context);
160,736✔
680

681
                            if (ts1.valueType() == ValueType::InstPtr)
160,736✔
682
                            {
683
                                context.ip = ts1.pageAddr();
157,901✔
684
                                // we always push PP then IP, thus the next value
685
                                // MUST be the page pointer
686
                                context.pp = pop(context)->pageAddr();
157,901✔
687

688
                                returnFromFuncCall(context);
157,901✔
689
                                if (ts.valueType() == ValueType::Garbage)
157,901✔
690
                                    push(Builtins::nil, context);
×
691
                                else
692
                                    push(std::move(ts), context);
157,901✔
693
                            }
157,901✔
694
                            else if (ts1.valueType() == ValueType::Garbage)
2,835✔
695
                            {
696
                                const Value* ip = pop(context);
2,722✔
697
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
2,722✔
698
                                context.ip = ip->pageAddr();
2,722✔
699
                                context.pp = pop(context)->pageAddr();
2,722✔
700

701
                                returnFromFuncCall(context);
2,722✔
702
                                push(std::move(ts), context);
2,722✔
703
                            }
2,722✔
704
                            else if (ts.valueType() == ValueType::InstPtr)
113✔
705
                            {
706
                                context.ip = ts.pageAddr();
113✔
707
                                context.pp = ts1.pageAddr();
113✔
708
                                returnFromFuncCall(context);
113✔
709
                                push(Builtins::nil, context);
113✔
710
                            }
113✔
711
                            else
712
                                throw Error(
×
713
                                    fmt::format(
×
714
                                        "Unhandled case when returning from function call. TS=({}){}, TS1=({}){}",
×
715
                                        std::to_string(ts.valueType()),
×
UNCOV
716
                                        ts.toString(*this),
×
717
                                        std::to_string(ts1.valueType()),
×
718
                                        ts1.toString(*this)));
×
719

720
                            if (context.fc <= untilFrameCount)
160,736✔
721
                                GOTO_HALT();
18✔
722
                        }
160,736✔
723

724
                        DISPATCH();
160,718✔
725
                    }
104✔
726

727
                    TARGET(HALT)
728
                    {
729
                        m_running = false;
104✔
730
                        GOTO_HALT();
104✔
731
                    }
170,494✔
732

733
                    TARGET(PUSH_RETURN_ADDRESS)
734
                    {
735
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
170,494✔
736
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
737
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
170,494✔
738
                        context.inst_exec_counter++;
170,494✔
739
                        DISPATCH();
170,494✔
740
                    }
2,712✔
741

742
                    TARGET(CALL)
743
                    {
744
                        call(context, arg);
2,712✔
745
                        if (!m_running)
2,708✔
746
                            GOTO_HALT();
×
747
                        DISPATCH();
2,708✔
748
                    }
87,992✔
749

750
                    TARGET(TAIL_CALL_SELF)
751
                    {
752
                        jump(0, context);
87,992✔
753
                        context.locals.back().reset();
87,992✔
754
                        DISPATCH();
87,992✔
755
                    }
3,227✔
756

757
                    TARGET(CAPTURE)
758
                    {
759
                        if (!context.saved_scope)
3,227✔
760
                            context.saved_scope = ClosureScope();
633✔
761

762
                        const Value* ptr = findNearestVariable(arg, context);
3,227✔
763
                        if (!ptr)
3,227✔
UNCOV
764
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
765
                        else
766
                        {
767
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,227✔
768
                            uint16_t id = context.capture_rename_id.value_or(arg);
3,227✔
769
                            context.saved_scope.value().push_back(id, *ptr);
3,227✔
770
                            context.capture_rename_id.reset();
3,227✔
771
                        }
772

773
                        DISPATCH();
3,227✔
774
                    }
13✔
775

776
                    TARGET(RENAME_NEXT_CAPTURE)
777
                    {
778
                        context.capture_rename_id = arg;
13✔
779
                        DISPATCH();
13✔
780
                    }
6,911✔
781

782
                    TARGET(BUILTIN)
783
                    {
784
                        push(Builtins::builtins[arg].second, context);
6,911✔
785
                        DISPATCH();
6,911✔
786
                    }
2✔
787

788
                    TARGET(DEL)
789
                    {
790
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
2✔
791
                        {
792
                            if (var->valueType() == ValueType::User)
1✔
793
                                var->usertypeRef().del();
1✔
794
                            *var = Value();
1✔
795
                            DISPATCH();
1✔
796
                        }
797

798
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
799
                    }
633✔
800

801
                    TARGET(MAKE_CLOSURE)
802
                    {
803
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
633✔
804
                        context.saved_scope.reset();
633✔
805
                        DISPATCH();
633✔
806
                    }
×
807

808
                    TARGET(GET_FIELD)
809
                    {
810
                        Value* var = popAndResolveAsPtr(context);
×
811
                        push(getField(var, arg, context), context);
×
812
                        DISPATCH();
×
813
                    }
2,583✔
814

815
                    TARGET(GET_FIELD_AS_CLOSURE)
816
                    {
817
                        Value* var = popAndResolveAsPtr(context);
2,583✔
818
                        push(getField(var, arg, context, /* push_with_env= */ true), context);
2,583✔
819
                        DISPATCH();
2,583✔
820
                    }
1✔
821

822
                    TARGET(PLUGIN)
823
                    {
824
                        loadPlugin(arg, context);
1✔
825
                        DISPATCH();
1✔
826
                    }
1,323✔
827

828
                    TARGET(LIST)
829
                    {
830
                        {
831
                            Value l = createList(arg, context);
1,323✔
832
                            push(std::move(l), context);
1,323✔
833
                        }
1,323✔
834
                        DISPATCH();
1,323✔
835
                    }
2,030✔
836

837
                    TARGET(APPEND)
838
                    {
839
                        {
840
                            Value* list = popAndResolveAsPtr(context);
2,030✔
841
                            if (list->valueType() != ValueType::List)
2,030✔
842
                            {
843
                                std::vector<Value> args = { *list };
1✔
844
                                for (uint16_t i = 0; i < arg; ++i)
2✔
845
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
846
                                throw types::TypeCheckingError(
2✔
847
                                    "append",
1✔
848
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
849
                                    args);
850
                            }
1✔
851

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

854
                            Value obj { *list };
2,029✔
855
                            obj.list().reserve(size + arg);
2,029✔
856

857
                            for (uint16_t i = 0; i < arg; ++i)
4,058✔
858
                                obj.push_back(*popAndResolveAsPtr(context));
2,029✔
859
                            push(std::move(obj), context);
2,029✔
860
                        }
2,029✔
861
                        DISPATCH();
2,029✔
862
                    }
20✔
863

864
                    TARGET(CONCAT)
865
                    {
866
                        {
867
                            Value* list = popAndResolveAsPtr(context);
20✔
868
                            Value obj { *list };
20✔
869

870
                            for (uint16_t i = 0; i < arg; ++i)
40✔
871
                            {
872
                                Value* next = popAndResolveAsPtr(context);
22✔
873

874
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
22✔
875
                                    throw types::TypeCheckingError(
4✔
876
                                        "concat",
2✔
877
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
878
                                        { *list, *next });
2✔
879

880
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
20✔
881
                            }
20✔
882
                            push(std::move(obj), context);
18✔
883
                        }
20✔
884
                        DISPATCH();
18✔
885
                    }
1✔
886

887
                    TARGET(APPEND_IN_PLACE)
888
                    {
889
                        Value* list = popAndResolveAsPtr(context);
1✔
890
                        listAppendInPlace(list, arg, context);
1✔
891
                        DISPATCH();
1✔
892
                    }
599✔
893

894
                    TARGET(CONCAT_IN_PLACE)
895
                    {
896
                        Value* list = popAndResolveAsPtr(context);
599✔
897

898
                        for (uint16_t i = 0; i < arg; ++i)
1,233✔
899
                        {
900
                            Value* next = popAndResolveAsPtr(context);
636✔
901

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

908
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
634✔
909
                        }
634✔
910
                        DISPATCH();
597✔
911
                    }
1,087✔
912

913
                    TARGET(POP_LIST)
914
                    {
915
                        {
916
                            Value list = *popAndResolveAsPtr(context);
1,087✔
917
                            Value number = *popAndResolveAsPtr(context);
1,087✔
918

919
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
1,087✔
920
                                throw types::TypeCheckingError(
2✔
921
                                    "pop",
1✔
922
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
923
                                    { list, number });
1✔
924

925
                            long idx = static_cast<long>(number.number());
1,086✔
926
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
1,086✔
927
                            if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
1,086✔
928
                                throwVMError(
2✔
929
                                    ErrorKind::Index,
930
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
931

932
                            list.list().erase(list.list().begin() + idx);
1,084✔
933
                            push(list, context);
1,084✔
934
                        }
1,087✔
935
                        DISPATCH();
1,084✔
936
                    }
254✔
937

938
                    TARGET(POP_LIST_IN_PLACE)
939
                    {
940
                        {
941
                            Value* list = popAndResolveAsPtr(context);
254✔
942
                            Value number = *popAndResolveAsPtr(context);
254✔
943

944
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
254✔
945
                                throw types::TypeCheckingError(
2✔
946
                                    "pop!",
1✔
947
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
948
                                    { *list, number });
1✔
949

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

957
                            // Save the value we're removing to push it later.
958
                            // We need to save the value and push later because we're using a pointer to 'list', and pushing before erasing
959
                            // would overwrite values from the stack.
960
                            if (arg)
251✔
961
                                number = list->list()[static_cast<std::size_t>(idx)];
236✔
962
                            list->list().erase(list->list().begin() + idx);
251✔
963
                            if (arg)
251✔
964
                                push(number, context);
236✔
965
                        }
254✔
966
                        DISPATCH();
251✔
967
                    }
509,214✔
968

969
                    TARGET(SET_AT_INDEX)
970
                    {
971
                        {
972
                            Value* list = popAndResolveAsPtr(context);
509,214✔
973
                            Value number = *popAndResolveAsPtr(context);
509,214✔
974
                            Value new_value = *popAndResolveAsPtr(context);
509,214✔
975

976
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
509,214✔
977
                                throw types::TypeCheckingError(
2✔
978
                                    "@=",
1✔
979
                                    { { types::Contract {
3✔
980
                                          { types::Typedef("list", ValueType::List),
3✔
981
                                            types::Typedef("index", ValueType::Number),
1✔
982
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
983
                                      { types::Contract {
1✔
984
                                          { types::Typedef("string", ValueType::String),
3✔
985
                                            types::Typedef("index", ValueType::Number),
1✔
986
                                            types::Typedef("char", ValueType::String) } } } },
1✔
987
                                    { *list, number, new_value });
1✔
988

989
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
509,213✔
990
                            long idx = static_cast<long>(number.number());
509,213✔
991
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
509,213✔
992
                            if (std::cmp_greater_equal(idx, size) || idx < 0)
509,213✔
993
                                throwVMError(
2✔
994
                                    ErrorKind::Index,
995
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
996

997
                            if (list->valueType() == ValueType::List)
509,211✔
998
                            {
999
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
509,207✔
1000
                                if (arg)
509,207✔
1001
                                    push(new_value, context);
24✔
1002
                            }
509,207✔
1003
                            else
1004
                            {
1005
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
4✔
1006
                                if (arg)
4✔
1007
                                    push(Value(std::string(1, new_value.string()[0])), context);
2✔
1008
                            }
1009
                        }
509,214✔
1010
                        DISPATCH();
509,211✔
1011
                    }
1,117✔
1012

1013
                    TARGET(SET_AT_2_INDEX)
1014
                    {
1015
                        {
1016
                            Value* list = popAndResolveAsPtr(context);
1,117✔
1017
                            Value x = *popAndResolveAsPtr(context);
1,117✔
1018
                            Value y = *popAndResolveAsPtr(context);
1,117✔
1019
                            Value new_value = *popAndResolveAsPtr(context);
1,117✔
1020

1021
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
1,117✔
1022
                                throw types::TypeCheckingError(
2✔
1023
                                    "@@=",
1✔
1024
                                    { { types::Contract {
2✔
1025
                                        { types::Typedef("list", ValueType::List),
4✔
1026
                                          types::Typedef("x", ValueType::Number),
1✔
1027
                                          types::Typedef("y", ValueType::Number),
1✔
1028
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
1029
                                    { *list, x, y, new_value });
1✔
1030

1031
                            long idx_y = static_cast<long>(x.number());
1,116✔
1032
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
1,116✔
1033
                            if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
1,116✔
1034
                                throwVMError(
2✔
1035
                                    ErrorKind::Index,
1036
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
1037

1038
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
1,124✔
1039
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
1,113✔
1040
                                throw types::TypeCheckingError(
2✔
1041
                                    "@@=",
1✔
1042
                                    { { types::Contract {
3✔
1043
                                          { types::Typedef("list", ValueType::List),
4✔
1044
                                            types::Typedef("x", ValueType::Number),
1✔
1045
                                            types::Typedef("y", ValueType::Number),
1✔
1046
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
1047
                                      { types::Contract {
1✔
1048
                                          { types::Typedef("string", ValueType::String),
4✔
1049
                                            types::Typedef("x", ValueType::Number),
1✔
1050
                                            types::Typedef("y", ValueType::Number),
1✔
1051
                                            types::Typedef("char", ValueType::String) } } } },
1✔
1052
                                    { *list, x, y, new_value });
1✔
1053

1054
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
1,113✔
1055
                            const std::size_t size =
1,113✔
1056
                                is_list
2,226✔
1057
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
1,108✔
1058
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
5✔
1059

1060
                            long idx_x = static_cast<long>(y.number());
1,113✔
1061
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
1,113✔
1062
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
1,113✔
1063
                                throwVMError(
2✔
1064
                                    ErrorKind::Index,
1065
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1066

1067
                            if (is_list)
1,111✔
1068
                            {
1069
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
1,106✔
1070
                                if (arg)
1,106✔
1071
                                    push(new_value, context);
2✔
1072
                            }
1,106✔
1073
                            else
1074
                            {
1075
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
5✔
1076
                                if (arg)
5✔
1077
                                    push(Value(std::string(1, new_value.string()[0])), context);
3✔
1078
                            }
1079
                        }
1,117✔
1080
                        DISPATCH();
1,111✔
1081
                    }
10,332✔
1082

1083
                    TARGET(POP)
1084
                    {
1085
                        pop(context);
10,332✔
1086
                        DISPATCH();
10,332✔
1087
                    }
533,862✔
1088

1089
                    TARGET(SHORTCIRCUIT_AND)
1090
                    {
1091
                        if (!*peekAndResolveAsPtr(context))
533,862✔
1092
                            jump(arg, context);
4,713✔
1093
                        else
1094
                            pop(context);
529,149✔
1095
                        DISPATCH();
533,862✔
1096
                    }
2,975✔
1097

1098
                    TARGET(SHORTCIRCUIT_OR)
1099
                    {
1100
                        if (!!*peekAndResolveAsPtr(context))
2,975✔
1101
                            jump(arg, context);
801✔
1102
                        else
1103
                            pop(context);
2,174✔
1104
                        DISPATCH();
2,975✔
1105
                    }
10,581✔
1106

1107
                    TARGET(CREATE_SCOPE)
1108
                    {
1109
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
10,581✔
1110
                        DISPATCH();
10,581✔
1111
                    }
1,071,316✔
1112

1113
                    TARGET(RESET_SCOPE_JUMP)
1114
                    {
1115
                        context.locals.back().reset();
1,071,316✔
1116
                        jump(arg, context);
1,071,316✔
1117
                        DISPATCH();
1,071,316✔
1118
                    }
10,580✔
1119

1120
                    TARGET(POP_SCOPE)
1121
                    {
1122
                        context.locals.pop_back();
10,580✔
1123
                        DISPATCH();
10,580✔
1124
                    }
203✔
1125

1126
                    TARGET(APPLY)
1127
                    {
1128
                        {
1129
                            const Value args_list = *popAndResolveAsPtr(context),
203✔
1130
                                        func = *popAndResolveAsPtr(context);
203✔
1131
                            if (args_list.valueType() != ValueType::List || !func.isFunction())
203✔
1132
                            {
1133
                                throw types::TypeCheckingError(
4✔
1134
                                    "apply",
2✔
1135
                                    { {
8✔
1136
                                        types::Contract {
2✔
1137
                                            { types::Typedef("func", ValueType::PageAddr),
4✔
1138
                                              types::Typedef("args", ValueType::List) } },
2✔
1139
                                        types::Contract {
2✔
1140
                                            { types::Typedef("func", ValueType::Closure),
4✔
1141
                                              types::Typedef("args", ValueType::List) } },
2✔
1142
                                        types::Contract {
2✔
1143
                                            { types::Typedef("func", ValueType::CProc),
4✔
1144
                                              types::Typedef("args", ValueType::List) } },
2✔
1145
                                    } },
1146
                                    { func, args_list });
2✔
1147
                            }
×
1148

1149
                            push(func, context);
201✔
1150
                            for (const Value& a : args_list.constList())
542✔
1151
                                push(a, context);
341✔
1152

1153
                            call(context, static_cast<uint16_t>(args_list.constList().size()));
201✔
1154
                        }
203✔
1155
                        DISPATCH();
155✔
1156
                    }
25✔
1157

1158
#pragma endregion
1159

1160
#pragma region "Operators"
1161

1162
                    TARGET(BREAKPOINT)
1163
                    {
1164
                        {
1165
                            bool breakpoint_active = true;
25✔
1166
                            if (arg == 1)
25✔
1167
                                breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym;
19✔
1168

1169
                            if (m_state.m_features & FeatureVMDebugger && breakpoint_active)
25✔
1170
                            {
1171
                                initDebugger(context);
13✔
1172
                                m_debugger->run(*this, context, /* from_breakpoint= */ true);
13✔
1173
                                m_debugger->resetContextToSavedState(context);
13✔
1174

1175
                                if (m_debugger->shouldQuitVM())
13✔
1176
                                    GOTO_HALT();
1✔
1177
                            }
12✔
1178
                        }
1179
                        DISPATCH();
24✔
1180
                    }
50,011✔
1181

1182
                    TARGET(ADD)
1183
                    {
1184
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
50,011✔
1185

1186
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
50,011✔
1187
                            push(Value(a->number() + b->number()), context);
36,464✔
1188
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
13,547✔
1189
                            push(Value(a->string() + b->string()), context);
13,546✔
1190
                        else
1191
                            throw types::TypeCheckingError(
2✔
1192
                                "+",
1✔
1193
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1194
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1195
                                { *a, *b });
1✔
1196
                        DISPATCH();
50,010✔
1197
                    }
511,246✔
1198

1199
                    TARGET(SUB)
1200
                    {
1201
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
511,246✔
1202

1203
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
511,246✔
1204
                            throw types::TypeCheckingError(
2✔
1205
                                "-",
1✔
1206
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1207
                                { *a, *b });
1✔
1208
                        push(Value(a->number() - b->number()), context);
511,245✔
1209
                        DISPATCH();
511,245✔
1210
                    }
510,019✔
1211

1212
                    TARGET(MUL)
1213
                    {
1214
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
510,019✔
1215

1216
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
510,019✔
1217
                            throw types::TypeCheckingError(
2✔
1218
                                "*",
1✔
1219
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1220
                                { *a, *b });
1✔
1221
                        push(Value(a->number() * b->number()), context);
510,018✔
1222
                        DISPATCH();
510,018✔
1223
                    }
3,296✔
1224

1225
                    TARGET(DIV)
1226
                    {
1227
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
3,296✔
1228

1229
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
3,296✔
1230
                            throw types::TypeCheckingError(
2✔
1231
                                "/",
1✔
1232
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1233
                                { *a, *b });
1✔
1234
                        auto d = b->number();
3,295✔
1235
                        if (d == 0)
3,295✔
1236
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1237

1238
                        push(Value(a->number() / d), context);
3,294✔
1239
                        DISPATCH();
3,294✔
1240
                    }
1,678✔
1241

1242
                    TARGET(GT)
1243
                    {
1244
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,678✔
1245
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
1,678✔
1246
                        DISPATCH();
1,678✔
1247
                    }
25,980✔
1248

1249
                    TARGET(LT)
1250
                    {
1251
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
25,980✔
1252
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
25,980✔
1253
                        DISPATCH();
25,980✔
1254
                    }
512,625✔
1255

1256
                    TARGET(LE)
1257
                    {
1258
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
512,625✔
1259
                        push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
512,625✔
1260
                        DISPATCH();
512,625✔
1261
                    }
7,935✔
1262

1263
                    TARGET(GE)
1264
                    {
1265
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,935✔
1266
                        push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
7,935✔
1267
                        DISPATCH();
7,935✔
1268
                    }
503,927✔
1269

1270
                    TARGET(NEQ)
1271
                    {
1272
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
503,927✔
1273
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
503,927✔
1274
                        DISPATCH();
503,927✔
1275
                    }
28,659✔
1276

1277
                    TARGET(EQ)
1278
                    {
1279
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,659✔
1280
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
28,659✔
1281
                        DISPATCH();
28,659✔
1282
                    }
7,698✔
1283

1284
                    TARGET(LEN)
1285
                    {
1286
                        const Value* a = popAndResolveAsPtr(context);
7,698✔
1287

1288
                        if (a->valueType() == ValueType::List)
7,698✔
1289
                            push(Value(static_cast<int>(a->constList().size())), context);
3,810✔
1290
                        else if (a->valueType() == ValueType::String)
3,888✔
1291
                            push(Value(static_cast<int>(a->string().size())), context);
3,862✔
1292
                        else if (a->valueType() == ValueType::Dict)
26✔
1293
                            push(Value(static_cast<int>(a->dict().size())), context);
25✔
1294
                        else
1295
                            throw types::TypeCheckingError(
2✔
1296
                                "len",
1✔
1297
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
3✔
1298
                                    types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1299
                                    types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1300
                                { *a });
1✔
1301
                        DISPATCH();
7,697✔
1302
                    }
660✔
1303

1304
                    TARGET(IS_EMPTY)
1305
                    {
1306
                        const Value* a = popAndResolveAsPtr(context);
660✔
1307

1308
                        if (a->valueType() == ValueType::List)
660✔
1309
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
129✔
1310
                        else if (a->valueType() == ValueType::String)
531✔
1311
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
523✔
1312
                        else if (a->valueType() == ValueType::Dict)
8✔
1313
                            push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context);
4✔
1314
                        else if (a->valueType() == ValueType::Nil)
4✔
1315
                            push(Builtins::trueSym, context);
3✔
1316
                        else
1317
                            throw types::TypeCheckingError(
2✔
1318
                                "empty?",
1✔
1319
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
4✔
1320
                                    types::Contract { { types::Typedef("value", ValueType::Nil) } },
1✔
1321
                                    types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1322
                                    types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1323
                                { *a });
1✔
1324
                        DISPATCH();
659✔
1325
                    }
411✔
1326

1327
                    TARGET(TAIL)
1328
                    {
1329
                        Value* const a = popAndResolveAsPtr(context);
411✔
1330
                        push(helper::tail(a), context);
411✔
1331
                        DISPATCH();
410✔
1332
                    }
1,433✔
1333

1334
                    TARGET(HEAD)
1335
                    {
1336
                        Value* const a = popAndResolveAsPtr(context);
1,433✔
1337
                        push(helper::head(a), context);
1,433✔
1338
                        DISPATCH();
1,432✔
1339
                    }
2,439✔
1340

1341
                    TARGET(IS_NIL)
1342
                    {
1343
                        const Value* a = popAndResolveAsPtr(context);
2,439✔
1344
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,439✔
1345
                        DISPATCH();
2,439✔
1346
                    }
1,100✔
1347

1348
                    TARGET(TO_NUM)
1349
                    {
1350
                        const Value* a = popAndResolveAsPtr(context);
1,100✔
1351

1352
                        if (a->valueType() != ValueType::String)
1,100✔
1353
                            throw types::TypeCheckingError(
2✔
1354
                                "toNumber",
1✔
1355
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1356
                                { *a });
1✔
1357

1358
                        double val;
1359
                        if (Utils::isDouble(a->string(), &val))
1,099✔
1360
                            push(Value(val), context);
1,095✔
1361
                        else
1362
                            push(Builtins::nil, context);
4✔
1363
                        DISPATCH();
1,099✔
1364
                    }
1,897✔
1365

1366
                    TARGET(TO_STR)
1367
                    {
1368
                        const Value* a = popAndResolveAsPtr(context);
1,897✔
1369
                        push(Value(a->toString(*this)), context);
1,897✔
1370
                        DISPATCH();
1,897✔
1371
                    }
511,786✔
1372

1373
                    TARGET(AT)
1374
                    {
1375
                        Value& b = *popAndResolveAsPtr(context);
511,786✔
1376
                        Value& a = *popAndResolveAsPtr(context);
511,786✔
1377
                        push(helper::at(a, b, *this), context);
511,786✔
1378
                        DISPATCH();
511,784✔
1379
                    }
2,616✔
1380

1381
                    TARGET(AT_AT)
1382
                    {
1383
                        {
1384
                            const Value* x = popAndResolveAsPtr(context);
2,616✔
1385
                            const Value* y = popAndResolveAsPtr(context);
2,616✔
1386
                            Value& list = *popAndResolveAsPtr(context);
2,616✔
1387

1388
                            push(helper::atAt(x, y, list), context);
2,616✔
1389
                        }
1390
                        DISPATCH();
2,611✔
1391
                    }
1,033,219✔
1392

1393
                    TARGET(MOD)
1394
                    {
1395
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,033,219✔
1396
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,033,219✔
1397
                            throw types::TypeCheckingError(
2✔
1398
                                "mod",
1✔
1399
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1400
                                { *a, *b });
1✔
1401
                        push(Value(std::fmod(a->number(), b->number())), context);
1,033,218✔
1402
                        DISPATCH();
1,033,218✔
1403
                    }
32✔
1404

1405
                    TARGET(TYPE)
1406
                    {
1407
                        const Value* a = popAndResolveAsPtr(context);
32✔
1408
                        push(Value(std::to_string(a->valueType())), context);
32✔
1409
                        DISPATCH();
32✔
1410
                    }
3✔
1411

1412
                    TARGET(HAS_FIELD)
1413
                    {
1414
                        {
1415
                            Value* const field = popAndResolveAsPtr(context);
3✔
1416
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1417
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1418
                                throw types::TypeCheckingError(
2✔
1419
                                    "hasField",
1✔
1420
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1421
                                    { *closure, *field });
1✔
1422

1423
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1424
                            if (it == m_state.m_symbols.end())
2✔
1425
                            {
1426
                                push(Builtins::falseSym, context);
1✔
1427
                                DISPATCH();
1✔
1428
                            }
1429

1430
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1431
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1432
                        }
1433
                        DISPATCH();
1✔
1434
                    }
3,792✔
1435

1436
                    TARGET(NOT)
1437
                    {
1438
                        const Value* a = popAndResolveAsPtr(context);
3,792✔
1439
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,792✔
1440
                        DISPATCH();
3,792✔
1441
                    }
8,340✔
1442

1443
#pragma endregion
1444

1445
#pragma region "Super Instructions"
1446
                    TARGET(LOAD_CONST_LOAD_CONST)
1447
                    {
1448
                        UNPACK_ARGS();
8,340✔
1449
                        push(loadConstAsPtr(primary_arg), context);
8,340✔
1450
                        push(loadConstAsPtr(secondary_arg), context);
8,340✔
1451
                        context.inst_exec_counter++;
8,340✔
1452
                        DISPATCH();
8,340✔
1453
                    }
20,238✔
1454

1455
                    TARGET(LOAD_CONST_STORE)
1456
                    {
1457
                        UNPACK_ARGS();
20,238✔
1458
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
20,238✔
1459
                        DISPATCH();
20,238✔
1460
                    }
1,125✔
1461

1462
                    TARGET(LOAD_CONST_SET_VAL)
1463
                    {
1464
                        UNPACK_ARGS();
1,125✔
1465
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
1,125✔
1466
                        DISPATCH();
1,124✔
1467
                    }
33✔
1468

1469
                    TARGET(STORE_FROM)
1470
                    {
1471
                        UNPACK_ARGS();
33✔
1472
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
33✔
1473
                        DISPATCH();
32✔
1474
                    }
1,326✔
1475

1476
                    TARGET(STORE_FROM_INDEX)
1477
                    {
1478
                        UNPACK_ARGS();
1,326✔
1479
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,326✔
1480
                        DISPATCH();
1,326✔
1481
                    }
630✔
1482

1483
                    TARGET(SET_VAL_FROM)
1484
                    {
1485
                        UNPACK_ARGS();
630✔
1486
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
630✔
1487
                        DISPATCH();
630✔
1488
                    }
671✔
1489

1490
                    TARGET(SET_VAL_FROM_INDEX)
1491
                    {
1492
                        UNPACK_ARGS();
671✔
1493
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
671✔
1494
                        DISPATCH();
671✔
1495
                    }
158✔
1496

1497
                    TARGET(INCREMENT)
1498
                    {
1499
                        UNPACK_ARGS();
158✔
1500
                        {
1501
                            Value* var = loadSymbol(primary_arg, context);
158✔
1502

1503
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1504
                            if (var->valueType() == ValueType::Reference)
158✔
1505
                                var = var->reference();
×
1506

1507
                            if (var->valueType() == ValueType::Number)
158✔
1508
                                push(Value(var->number() + secondary_arg), context);
157✔
1509
                            else
1510
                                throw types::TypeCheckingError(
2✔
1511
                                    "+",
1✔
1512
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1513
                                    { *var, Value(secondary_arg) });
1✔
1514
                        }
1515
                        DISPATCH();
157✔
1516
                    }
91,613✔
1517

1518
                    TARGET(INCREMENT_BY_INDEX)
1519
                    {
1520
                        UNPACK_ARGS();
91,613✔
1521
                        {
1522
                            Value* var = loadSymbolFromIndex(primary_arg, context);
91,613✔
1523

1524
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1525
                            if (var->valueType() == ValueType::Reference)
91,613✔
1526
                                var = var->reference();
×
1527

1528
                            if (var->valueType() == ValueType::Number)
91,613✔
1529
                                push(Value(var->number() + secondary_arg), context);
91,612✔
1530
                            else
1531
                                throw types::TypeCheckingError(
2✔
1532
                                    "+",
1✔
1533
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1534
                                    { *var, Value(secondary_arg) });
1✔
1535
                        }
1536
                        DISPATCH();
91,612✔
1537
                    }
559,074✔
1538

1539
                    TARGET(INCREMENT_STORE)
1540
                    {
1541
                        UNPACK_ARGS();
559,074✔
1542
                        {
1543
                            Value* var = loadSymbol(primary_arg, context);
559,074✔
1544

1545
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1546
                            if (var->valueType() == ValueType::Reference)
559,074✔
1547
                                var = var->reference();
×
1548

1549
                            if (var->valueType() == ValueType::Number)
559,074✔
1550
                            {
1551
                                auto val = Value(var->number() + secondary_arg);
559,073✔
1552
                                setVal(primary_arg, &val, context);
559,073✔
1553
                            }
559,073✔
1554
                            else
1555
                                throw types::TypeCheckingError(
2✔
1556
                                    "+",
1✔
1557
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1558
                                    { *var, Value(secondary_arg) });
1✔
1559
                        }
1560
                        DISPATCH();
559,073✔
1561
                    }
515,382✔
1562

1563
                    TARGET(DECREMENT)
1564
                    {
1565
                        UNPACK_ARGS();
515,382✔
1566
                        {
1567
                            Value* var = loadSymbol(primary_arg, context);
515,382✔
1568

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

1573
                            if (var->valueType() == ValueType::Number)
515,382✔
1574
                                push(Value(var->number() - secondary_arg), context);
515,381✔
1575
                            else
1576
                                throw types::TypeCheckingError(
2✔
1577
                                    "-",
1✔
1578
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1579
                                    { *var, Value(secondary_arg) });
1✔
1580
                        }
1581
                        DISPATCH();
515,381✔
1582
                    }
194,660✔
1583

1584
                    TARGET(DECREMENT_BY_INDEX)
1585
                    {
1586
                        UNPACK_ARGS();
194,660✔
1587
                        {
1588
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,660✔
1589

1590
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1591
                            if (var->valueType() == ValueType::Reference)
194,660✔
1592
                                var = var->reference();
×
1593

1594
                            if (var->valueType() == ValueType::Number)
194,660✔
1595
                                push(Value(var->number() - secondary_arg), context);
194,659✔
1596
                            else
1597
                                throw types::TypeCheckingError(
2✔
1598
                                    "-",
1✔
1599
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1600
                                    { *var, Value(secondary_arg) });
1✔
1601
                        }
1602
                        DISPATCH();
194,659✔
1603
                    }
512,558✔
1604

1605
                    TARGET(DECREMENT_STORE)
1606
                    {
1607
                        UNPACK_ARGS();
512,558✔
1608
                        {
1609
                            Value* var = loadSymbol(primary_arg, context);
512,558✔
1610

1611
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1612
                            if (var->valueType() == ValueType::Reference)
512,558✔
1613
                                var = var->reference();
×
1614

1615
                            if (var->valueType() == ValueType::Number)
512,558✔
1616
                            {
1617
                                auto val = Value(var->number() - secondary_arg);
512,557✔
1618
                                setVal(primary_arg, &val, context);
512,557✔
1619
                            }
512,557✔
1620
                            else
1621
                                throw types::TypeCheckingError(
2✔
1622
                                    "-",
1✔
1623
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1624
                                    { *var, Value(secondary_arg) });
1✔
1625
                        }
1626
                        DISPATCH();
512,557✔
1627
                    }
1✔
1628

1629
                    TARGET(STORE_TAIL)
1630
                    {
1631
                        UNPACK_ARGS();
1✔
1632
                        {
1633
                            Value* list = loadSymbol(primary_arg, context);
1✔
1634
                            Value tail = helper::tail(list);
1✔
1635
                            store(secondary_arg, &tail, context);
1✔
1636
                        }
1✔
1637
                        DISPATCH();
1✔
1638
                    }
8✔
1639

1640
                    TARGET(STORE_TAIL_BY_INDEX)
1641
                    {
1642
                        UNPACK_ARGS();
8✔
1643
                        {
1644
                            Value* list = loadSymbolFromIndex(primary_arg, context);
8✔
1645
                            Value tail = helper::tail(list);
8✔
1646
                            store(secondary_arg, &tail, context);
8✔
1647
                        }
8✔
1648
                        DISPATCH();
8✔
1649
                    }
4✔
1650

1651
                    TARGET(STORE_HEAD)
1652
                    {
1653
                        UNPACK_ARGS();
4✔
1654
                        {
1655
                            Value* list = loadSymbol(primary_arg, context);
4✔
1656
                            Value head = helper::head(list);
4✔
1657
                            store(secondary_arg, &head, context);
4✔
1658
                        }
4✔
1659
                        DISPATCH();
4✔
1660
                    }
38✔
1661

1662
                    TARGET(STORE_HEAD_BY_INDEX)
1663
                    {
1664
                        UNPACK_ARGS();
38✔
1665
                        {
1666
                            Value* list = loadSymbolFromIndex(primary_arg, context);
38✔
1667
                            Value head = helper::head(list);
38✔
1668
                            store(secondary_arg, &head, context);
38✔
1669
                        }
38✔
1670
                        DISPATCH();
38✔
1671
                    }
4,285✔
1672

1673
                    TARGET(STORE_LIST)
1674
                    {
1675
                        UNPACK_ARGS();
4,285✔
1676
                        {
1677
                            Value l = createList(primary_arg, context);
4,285✔
1678
                            store(secondary_arg, &l, context);
4,285✔
1679
                        }
4,285✔
1680
                        DISPATCH();
4,285✔
1681
                    }
3✔
1682

1683
                    TARGET(SET_VAL_TAIL)
1684
                    {
1685
                        UNPACK_ARGS();
3✔
1686
                        {
1687
                            Value* list = loadSymbol(primary_arg, context);
3✔
1688
                            Value tail = helper::tail(list);
3✔
1689
                            setVal(secondary_arg, &tail, context);
3✔
1690
                        }
3✔
1691
                        DISPATCH();
3✔
1692
                    }
1✔
1693

1694
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1695
                    {
1696
                        UNPACK_ARGS();
1✔
1697
                        {
1698
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1699
                            Value tail = helper::tail(list);
1✔
1700
                            setVal(secondary_arg, &tail, context);
1✔
1701
                        }
1✔
1702
                        DISPATCH();
1✔
1703
                    }
1✔
1704

1705
                    TARGET(SET_VAL_HEAD)
1706
                    {
1707
                        UNPACK_ARGS();
1✔
1708
                        {
1709
                            Value* list = loadSymbol(primary_arg, context);
1✔
1710
                            Value head = helper::head(list);
1✔
1711
                            setVal(secondary_arg, &head, context);
1✔
1712
                        }
1✔
1713
                        DISPATCH();
1✔
1714
                    }
1✔
1715

1716
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1717
                    {
1718
                        UNPACK_ARGS();
1✔
1719
                        {
1720
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1721
                            Value head = helper::head(list);
1✔
1722
                            setVal(secondary_arg, &head, context);
1✔
1723
                        }
1✔
1724
                        DISPATCH();
1✔
1725
                    }
7,376✔
1726

1727
                    TARGET(CALL_BUILTIN)
1728
                    {
1729
                        UNPACK_ARGS();
7,376✔
1730
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1731
                        callBuiltin(
14,752✔
1732
                            context,
7,376✔
1733
                            Builtins::builtins[primary_arg].second,
7,376✔
1734
                            secondary_arg,
7,376✔
1735
                            /* remove_return_address= */ true,
1736
                            /* remove_builtin= */ false);
1737
                        if (!m_running)
7,290✔
1738
                            GOTO_HALT();
×
1739
                        DISPATCH();
7,290✔
1740
                    }
18,217✔
1741

1742
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1743
                    {
1744
                        UNPACK_ARGS();
18,217✔
1745
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1746
                        callBuiltin(
36,434✔
1747
                            context,
18,217✔
1748
                            Builtins::builtins[primary_arg].second,
18,217✔
1749
                            secondary_arg,
18,217✔
1750
                            // we didn't have a PUSH_RETURN_ADDRESS instruction before,
1751
                            // so do not attempt to remove (pp,ip) from the stack: they're not there!
1752
                            /* remove_return_address= */ false,
1753
                            /* remove_builtin= */ false);
1754
                        if (!m_running)
18,216✔
1755
                            GOTO_HALT();
×
1756
                        DISPATCH();
18,216✔
1757
                    }
877✔
1758

1759
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1760
                    {
1761
                        UNPACK_ARGS();
877✔
1762
                        const Value* sym = popAndResolveAsPtr(context);
877✔
1763
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
877✔
1764
                            jump(secondary_arg, context);
124✔
1765
                        DISPATCH();
877✔
1766
                    }
22,988✔
1767

1768
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1769
                    {
1770
                        UNPACK_ARGS();
22,988✔
1771
                        const Value* sym = popAndResolveAsPtr(context);
22,988✔
1772
                        if (*sym < *loadConstAsPtr(primary_arg))
22,988✔
1773
                            jump(secondary_arg, context);
11,959✔
1774
                        DISPATCH();
22,988✔
1775
                    }
12,757✔
1776

1777
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1778
                    {
1779
                        UNPACK_ARGS();
12,757✔
1780
                        const Value* sym = popAndResolveAsPtr(context);
12,757✔
1781
                        if (!(*sym < *loadSymbol(primary_arg, context)))
12,757✔
1782
                            jump(secondary_arg, context);
1,056✔
1783
                        DISPATCH();
12,757✔
1784
                    }
172,645✔
1785

1786
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1787
                    {
1788
                        UNPACK_ARGS();
172,645✔
1789
                        const Value* sym = popAndResolveAsPtr(context);
172,645✔
1790
                        const Value* cst = loadConstAsPtr(primary_arg);
172,645✔
1791
                        if (*cst < *sym)
172,645✔
1792
                            jump(secondary_arg, context);
86,674✔
1793
                        DISPATCH();
172,645✔
1794
                    }
4,080✔
1795

1796
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1797
                    {
1798
                        UNPACK_ARGS();
4,080✔
1799
                        const Value* sym = popAndResolveAsPtr(context);
4,080✔
1800
                        const Value* cst = loadConstAsPtr(primary_arg);
4,080✔
1801
                        if (!(*cst < *sym))
4,080✔
1802
                            jump(secondary_arg, context);
1,042✔
1803
                        DISPATCH();
4,080✔
1804
                    }
11✔
1805

1806
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1807
                    {
1808
                        UNPACK_ARGS();
11✔
1809
                        const Value* sym = popAndResolveAsPtr(context);
11✔
1810
                        const Value* rhs = loadSymbol(primary_arg, context);
11✔
1811
                        if (!(*rhs < *sym))
11✔
1812
                            jump(secondary_arg, context);
2✔
1813
                        DISPATCH();
11✔
1814
                    }
502,107✔
1815

1816
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1817
                    {
1818
                        UNPACK_ARGS();
502,107✔
1819
                        const Value* sym = popAndResolveAsPtr(context);
502,107✔
1820
                        if (*sym == *loadConstAsPtr(primary_arg))
502,107✔
1821
                            jump(secondary_arg, context);
12,183✔
1822
                        DISPATCH();
502,107✔
1823
                    }
89,117✔
1824

1825
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1826
                    {
1827
                        UNPACK_ARGS();
89,117✔
1828
                        const Value* sym = popAndResolveAsPtr(context);
89,117✔
1829
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
89,117✔
1830
                            jump(secondary_arg, context);
551✔
1831
                        DISPATCH();
89,117✔
1832
                    }
11✔
1833

1834
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1835
                    {
1836
                        UNPACK_ARGS();
11✔
1837
                        const Value* sym = popAndResolveAsPtr(context);
11✔
1838
                        if (*sym != *loadConstAsPtr(primary_arg))
11✔
1839
                            jump(secondary_arg, context);
2✔
1840
                        DISPATCH();
11✔
1841
                    }
30✔
1842

1843
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1844
                    {
1845
                        UNPACK_ARGS();
30✔
1846
                        const Value* sym = popAndResolveAsPtr(context);
30✔
1847
                        if (*sym == *loadSymbol(primary_arg, context))
30✔
1848
                            jump(secondary_arg, context);
10✔
1849
                        DISPATCH();
30✔
1850
                    }
49,108✔
1851

1852
                    TARGET(CALL_SYMBOL)
1853
                    {
1854
                        UNPACK_ARGS();
49,108✔
1855
                        call(context, secondary_arg, /* function_ptr= */ loadSymbol(primary_arg, context));
49,108✔
1856
                        if (!m_running)
49,106✔
UNCOV
1857
                            GOTO_HALT();
×
1858
                        DISPATCH();
49,106✔
1859
                    }
1,151✔
1860

1861
                    TARGET(CALL_SYMBOL_BY_INDEX)
1862
                    {
1863
                        UNPACK_ARGS();
1,151✔
1864
                        call(context, secondary_arg, /* function_ptr= */ loadSymbolFromIndex(primary_arg, context));
1,151✔
1865
                        if (!m_running)
1,146✔
UNCOV
1866
                            GOTO_HALT();
×
1867
                        DISPATCH();
1,146✔
1868
                    }
109,875✔
1869

1870
                    TARGET(CALL_CURRENT_PAGE)
1871
                    {
1872
                        UNPACK_ARGS();
109,875✔
1873
                        context.last_symbol = primary_arg;
109,875✔
1874
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,875✔
1875
                        if (!m_running)
109,874✔
1876
                            GOTO_HALT();
×
1877
                        DISPATCH();
109,874✔
1878
                    }
1,719✔
1879

1880
                    TARGET(GET_FIELD_FROM_SYMBOL)
1881
                    {
1882
                        UNPACK_ARGS();
1,719✔
1883
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1,719✔
1884
                        DISPATCH();
1,719✔
1885
                    }
313✔
1886

1887
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1888
                    {
1889
                        UNPACK_ARGS();
313✔
1890
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
313✔
1891
                        DISPATCH();
311✔
1892
                    }
545,072✔
1893

1894
                    TARGET(AT_SYM_SYM)
1895
                    {
1896
                        UNPACK_ARGS();
545,072✔
1897
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
545,072✔
1898
                        DISPATCH();
545,072✔
1899
                    }
49✔
1900

1901
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1902
                    {
1903
                        UNPACK_ARGS();
49✔
1904
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
49✔
1905
                        DISPATCH();
49✔
1906
                    }
6,408✔
1907

1908
                    TARGET(AT_SYM_INDEX_CONST)
1909
                    {
1910
                        UNPACK_ARGS();
6,408✔
1911
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
6,408✔
1912
                        DISPATCH();
6,405✔
1913
                    }
2✔
1914

1915
                    TARGET(CHECK_TYPE_OF)
1916
                    {
1917
                        UNPACK_ARGS();
2✔
1918
                        const Value* sym = loadSymbol(primary_arg, context);
2✔
1919
                        const Value* cst = loadConstAsPtr(secondary_arg);
2✔
1920
                        push(
2✔
1921
                            cst->valueType() == ValueType::String &&
4✔
1922
                                    std::to_string(sym->valueType()) == cst->string()
2✔
1923
                                ? Builtins::trueSym
1924
                                : Builtins::falseSym,
1925
                            context);
2✔
1926
                        DISPATCH();
2✔
1927
                    }
160✔
1928

1929
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1930
                    {
1931
                        UNPACK_ARGS();
160✔
1932
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
160✔
1933
                        const Value* cst = loadConstAsPtr(secondary_arg);
160✔
1934
                        push(
160✔
1935
                            cst->valueType() == ValueType::String &&
320✔
1936
                                    std::to_string(sym->valueType()) == cst->string()
160✔
1937
                                ? Builtins::trueSym
1938
                                : Builtins::falseSym,
1939
                            context);
160✔
1940
                        DISPATCH();
160✔
1941
                    }
21,909✔
1942

1943
                    TARGET(APPEND_IN_PLACE_SYM)
1944
                    {
1945
                        UNPACK_ARGS();
21,909✔
1946
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
21,909✔
1947
                        DISPATCH();
21,909✔
1948
                    }
17✔
1949

1950
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1951
                    {
1952
                        UNPACK_ARGS();
17✔
1953
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
17✔
1954
                        DISPATCH();
16✔
1955
                    }
164✔
1956

1957
                    TARGET(STORE_LEN)
1958
                    {
1959
                        UNPACK_ARGS();
164✔
1960
                        {
1961
                            Value* a = loadSymbolFromIndex(primary_arg, context);
164✔
1962
                            Value len;
164✔
1963
                            if (a->valueType() == ValueType::List)
164✔
1964
                                len = Value(static_cast<int>(a->constList().size()));
48✔
1965
                            else if (a->valueType() == ValueType::String)
116✔
1966
                                len = Value(static_cast<int>(a->string().size()));
115✔
1967
                            else
1968
                                throw types::TypeCheckingError(
2✔
1969
                                    "len",
1✔
1970
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1971
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1972
                                    { *a });
1✔
1973
                            store(secondary_arg, &len, context);
163✔
1974
                        }
164✔
1975
                        DISPATCH();
163✔
1976
                    }
28,613✔
1977

1978
                    TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
1979
                    {
1980
                        UNPACK_ARGS();
28,613✔
1981
                        {
1982
                            const Value* sym = loadSymbol(primary_arg, context);
28,613✔
1983
                            Value size;
28,613✔
1984

1985
                            if (sym->valueType() == ValueType::List)
28,613✔
1986
                                size = Value(static_cast<int>(sym->constList().size()));
22,649✔
1987
                            else if (sym->valueType() == ValueType::String)
5,964✔
1988
                                size = Value(static_cast<int>(sym->string().size()));
5,963✔
1989
                            else
1990
                                throw types::TypeCheckingError(
2✔
1991
                                    "len",
1✔
1992
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1993
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1994
                                    { *sym });
1✔
1995

1996
                            if (!(*popAndResolveAsPtr(context) < size))
28,612✔
1997
                                jump(secondary_arg, context);
3,592✔
1998
                        }
28,613✔
1999
                        DISPATCH();
28,612✔
2000
                    }
521✔
2001

2002
                    TARGET(MUL_BY)
2003
                    {
2004
                        UNPACK_ARGS();
521✔
2005
                        {
2006
                            Value* var = loadSymbol(primary_arg, context);
521✔
2007
                            const int other = static_cast<int>(secondary_arg) - 2048;
521✔
2008

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

2013
                            if (var->valueType() == ValueType::Number)
521✔
2014
                                push(Value(var->number() * other), context);
520✔
2015
                            else
2016
                                throw types::TypeCheckingError(
2✔
2017
                                    "*",
1✔
2018
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2019
                                    { *var, Value(other) });
1✔
2020
                        }
2021
                        DISPATCH();
520✔
2022
                    }
36✔
2023

2024
                    TARGET(MUL_BY_INDEX)
2025
                    {
2026
                        UNPACK_ARGS();
36✔
2027
                        {
2028
                            Value* var = loadSymbolFromIndex(primary_arg, context);
36✔
2029
                            const int other = static_cast<int>(secondary_arg) - 2048;
36✔
2030

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

2035
                            if (var->valueType() == ValueType::Number)
36✔
2036
                                push(Value(var->number() * other), context);
35✔
2037
                            else
2038
                                throw types::TypeCheckingError(
2✔
2039
                                    "*",
1✔
2040
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2041
                                    { *var, Value(other) });
1✔
2042
                        }
2043
                        DISPATCH();
35✔
2044
                    }
2✔
2045

2046
                    TARGET(MUL_SET_VAL)
2047
                    {
2048
                        UNPACK_ARGS();
2✔
2049
                        {
2050
                            Value* var = loadSymbol(primary_arg, context);
2✔
2051
                            const int other = static_cast<int>(secondary_arg) - 2048;
2✔
2052

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

2057
                            if (var->valueType() == ValueType::Number)
2✔
2058
                            {
2059
                                auto val = Value(var->number() * other);
1✔
2060
                                setVal(primary_arg, &val, context);
1✔
2061
                            }
1✔
2062
                            else
2063
                                throw types::TypeCheckingError(
2✔
2064
                                    "*",
1✔
2065
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2066
                                    { *var, Value(other) });
1✔
2067
                        }
2068
                        DISPATCH();
1✔
2069
                    }
509,611✔
2070

2071
                    TARGET(FUSED_MATH)
2072
                    {
2073
                        const auto op1 = static_cast<Instruction>(padding),
509,611✔
2074
                                   op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
509,611✔
2075
                                   op3 = static_cast<Instruction>(arg & 0x00ff);
509,611✔
2076
                        const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
509,611✔
2077

2078
                        const Value* d = popAndResolveAsPtr(context);
509,611✔
2079
                        const Value* c = popAndResolveAsPtr(context);
509,611✔
2080
                        const Value* b = popAndResolveAsPtr(context);
509,611✔
2081

2082
                        if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
509,611✔
2083
                            throw types::TypeCheckingError(
2✔
2084
                                helper::mathInstToStr(op1),
1✔
2085
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2086
                                { *c, *d });
1✔
2087

2088
                        double temp = helper::doMath(c->number(), d->number(), op1);
509,610✔
2089
                        if (b->valueType() != ValueType::Number)
509,610✔
2090
                            throw types::TypeCheckingError(
4✔
2091
                                helper::mathInstToStr(op2),
2✔
2092
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
2✔
2093
                                { *b, Value(temp) });
2✔
2094
                        temp = helper::doMath(b->number(), temp, op2);
509,608✔
2095

2096
                        if (arg_count == 2)
509,607✔
2097
                            push(Value(temp), context);
509,570✔
2098
                        else if (arg_count == 3)
37✔
2099
                        {
2100
                            const Value* a = popAndResolveAsPtr(context);
37✔
2101
                            if (a->valueType() != ValueType::Number)
37✔
2102
                                throw types::TypeCheckingError(
2✔
2103
                                    helper::mathInstToStr(op3),
1✔
2104
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2105
                                    { *a, Value(temp) });
1✔
2106

2107
                            temp = helper::doMath(a->number(), temp, op3);
36✔
2108
                            push(Value(temp), context);
36✔
2109
                        }
36✔
2110
                        else
2111
                            throw Error(
×
UNCOV
2112
                                fmt::format(
×
2113
                                    "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
×
2114
                                    arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
×
2115
                        DISPATCH();
509,606✔
2116
                    }
2117
#pragma endregion
2118
                }
123✔
2119
#if ARK_USE_COMPUTED_GOTOS
2120
            dispatch_end:
2121
                do
123✔
2122
                {
2123
                } while (false);
123✔
2124
#endif
2125
            }
2126
        }
335✔
2127
        catch (const Error& e)
2128
        {
2129
            if (fail_with_exception)
146✔
2130
            {
2131
                std::stringstream stream;
145✔
2132
                backtrace(context, stream, /* colorize= */ false);
145✔
2133
                // It's important we have an Ark::Error here, as the constructor for NestedError
2134
                // does more than just aggregate error messages, hence the code duplication.
2135
                throw NestedError(e, stream.str(), *this);
145✔
2136
            }
145✔
2137
            else
2138
                showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context);
1✔
2139
        }
269✔
2140
        catch (const std::exception& e)
2141
        {
2142
            if (fail_with_exception)
66✔
2143
            {
2144
                std::stringstream stream;
66✔
2145
                backtrace(context, stream, /* colorize= */ false);
66✔
2146
                throw NestedError(e, stream.str());
66✔
2147
            }
66✔
2148
            else
2149
                showBacktraceWithException(e, context);
×
2150
        }
212✔
2151
        catch (...)
2152
        {
2153
            if (fail_with_exception)
×
2154
                throw;
×
2155

2156
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2157
            throw;
2158
#endif
2159
            fmt::println("Unknown error");
×
UNCOV
2160
            backtrace(context);
×
2161
            m_exit_code = 1;
×
2162
        }
277✔
2163

2164
        return m_exit_code;
124✔
2165
    }
423✔
2166

2167
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,054✔
2168
    {
2,054✔
2169
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,196✔
2170
        {
2171
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,142✔
2172
                return id;
2,048✔
2173
        }
2,096,142✔
2174
        return MaxValue16Bits;
6✔
2175
    }
2,054✔
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;
×
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,287✔
2285
    {
2,287✔
2286
        std::optional<InstLoc> match = std::nullopt;
2,287✔
2287

2288
        for (const auto location : m_state.m_inst_locations)
11,221✔
2289
        {
2290
            if (location.page_pointer == pp && !match)
8,934✔
2291
                match = location;
2,287✔
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,934✔
2297
                match = location;
2,483✔
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,934✔
2301
                break;
2,086✔
2302
        }
8,934✔
2303

2304
        return match;
2,287✔
2305
    }
2306

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

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

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

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

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

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

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

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

2360
                if (func_name + loc_as_text != previous_trace)
2,054✔
2361
                {
2362
                    fmt::println(
16✔
2363
                        os,
8✔
2364
                        "[{:4}] In function `{}'{}",
8✔
2365
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
8✔
2366
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
8✔
2367
                        loc_as_text);
2368
                    previous_trace = func_name + loc_as_text;
8✔
2369
                    ++displayed_traces;
8✔
2370
                    consecutive_similar_traces = 0;
8✔
2371
                }
8✔
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,054✔
2379
                do
2,061✔
2380
                {
2381
                    ip = popAndResolveAsPtr(context);
2,061✔
2382
                } while (ip->valueType() != ValueType::InstPtr);
2,061✔
2383

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

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

2395
            if (context.pp == 0)
8✔
2396
            {
2397
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
8✔
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) : "";
8✔
2399
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
8✔
2400
            }
8✔
2401

2402
            // display variables values in the current scope
2403
            fmt::println(os, "\nCurrent scope variables values:");
8✔
2404
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
9✔
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
        }
8✔
2413
    }
212✔
2414
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc