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

ArkScript-lang / Ark / 20065764942

09 Dec 2025 01:49PM UTC coverage: 90.564% (+0.008%) from 90.556%
20065764942

push

github

SuperFola
chore(tests): adding IR generation tests for arg attributes

8043 of 8881 relevant lines covered (90.56%)

180911.91 hits per line

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

92.18
/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

16
namespace Ark
17
{
18
    using namespace internal;
19

20
    namespace helper
21
    {
22
        inline Value tail(Value* a)
348✔
23
        {
348✔
24
            if (a->valueType() == ValueType::List)
348✔
25
            {
26
                if (a->constList().size() < 2)
80✔
27
                    return Value(ValueType::List);
21✔
28

29
                std::vector<Value> tmp(a->constList().size() - 1);
59✔
30
                for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
348✔
31
                    tmp[i - 1] = a->constList()[i];
289✔
32
                return Value(std::move(tmp));
59✔
33
            }
60✔
34
            if (a->valueType() == ValueType::String)
268✔
35
            {
36
                if (a->string().size() < 2)
267✔
37
                    return Value(ValueType::String);
50✔
38

39
                Value b { *a };
217✔
40
                b.stringRef().erase(b.stringRef().begin());
217✔
41
                return b;
217✔
42
            }
217✔
43

44
            throw types::TypeCheckingError(
2✔
45
                "tail",
1✔
46
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
47
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
48
                { *a });
1✔
49
        }
348✔
50

51
        inline Value head(Value* a)
1,166✔
52
        {
1,166✔
53
            if (a->valueType() == ValueType::List)
1,166✔
54
            {
55
                if (a->constList().empty())
897✔
56
                    return Builtins::nil;
1✔
57
                return a->constList()[0];
896✔
58
            }
59
            if (a->valueType() == ValueType::String)
269✔
60
            {
61
                if (a->string().empty())
268✔
62
                    return Value(ValueType::String);
1✔
63
                return Value(std::string(1, a->stringRef()[0]));
268✔
64
            }
65

66
            throw types::TypeCheckingError(
2✔
67
                "head",
1✔
68
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
69
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
70
                { *a });
1✔
71
        }
1,166✔
72

73
        inline Value at(Value& container, Value& index, VM& vm)
17,311✔
74
        {
17,311✔
75
            if (index.valueType() != ValueType::Number)
17,311✔
76
                throw types::TypeCheckingError(
5✔
77
                    "@",
1✔
78
                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
2✔
79
                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
1✔
80
                    { container, index });
1✔
81

82
            const auto num = static_cast<long>(index.number());
17,310✔
83

84
            if (container.valueType() == ValueType::List)
17,310✔
85
            {
86
                const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.list().size()) + num : num);
8,169✔
87
                if (i < container.list().size())
8,169✔
88
                    return container.list()[i];
8,168✔
89
                else
90
                    VM::throwVMError(
1✔
91
                        ErrorKind::Index,
92
                        fmt::format("{} out of range {} (length {})", num, container.toString(vm), container.list().size()));
1✔
93
            }
8,169✔
94
            else if (container.valueType() == ValueType::String)
9,141✔
95
            {
96
                const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.string().size()) + num : num);
9,140✔
97
                if (i < container.string().size())
9,140✔
98
                    return Value(std::string(1, container.string()[i]));
9,139✔
99
                else
100
                    VM::throwVMError(
1✔
101
                        ErrorKind::Index,
102
                        fmt::format("{} out of range \"{}\" (length {})", num, container.string(), container.string().size()));
1✔
103
            }
9,140✔
104
            else
105
                throw types::TypeCheckingError(
2✔
106
                    "@",
1✔
107
                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
2✔
108
                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
1✔
109
                    { container, index });
1✔
110
        }
17,314✔
111
    }
112

113
    VM::VM(State& state) noexcept :
606✔
114
        m_state(state), m_exit_code(0), m_running(false)
202✔
115
    {
202✔
116
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
202✔
117
    }
202✔
118

119
    void VM::init() noexcept
203✔
120
    {
203✔
121
        ExecutionContext& context = *m_execution_contexts.back();
203✔
122
        for (const auto& c : m_execution_contexts)
406✔
123
        {
124
            c->ip = 0;
203✔
125
            c->pp = 0;
203✔
126
            c->sp = 0;
203✔
127
        }
203✔
128

129
        context.sp = 0;
203✔
130
        context.fc = 1;
203✔
131

132
        m_shared_lib_objects.clear();
203✔
133
        context.stacked_closure_scopes.clear();
203✔
134
        context.stacked_closure_scopes.emplace_back(nullptr);
203✔
135

136
        context.saved_scope.reset();
203✔
137
        m_exit_code = 0;
203✔
138

139
        context.locals.clear();
203✔
140
        context.locals.reserve(128);
203✔
141
        context.locals.emplace_back(context.scopes_storage.data(), 0);
203✔
142

143
        // loading bound stuff
144
        // put them in the global frame if we can, aka the first one
145
        for (const auto& [sym_id, value] : m_state.m_binded)
635✔
146
        {
147
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
414✔
148
            if (it != m_state.m_symbols.end())
414✔
149
                context.locals[0].pushBack(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
18✔
150
        }
414✔
151
    }
203✔
152

153
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context)
3,297✔
154
    {
3,297✔
155
        if (closure->valueType() != ValueType::Closure)
3,297✔
156
        {
157
            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
158
                throwVMError(
2✔
159
                    ErrorKind::Type,
160
                    fmt::format(
3✔
161
                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
162
                        m_state.m_symbols[context.last_symbol],
1✔
163
                        std::to_string(closure->valueType()),
1✔
164
                        m_state.m_symbols[id]));
1✔
165
            else
166
                throwVMError(ErrorKind::Type,
×
167
                             fmt::format(
×
168
                                 "{} is not a Closure, can not get the field `{}' from it",
×
169
                                 std::to_string(closure->valueType()),
×
170
                                 m_state.m_symbols[id]));
×
171
        }
172

173
        if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
6,592✔
174
        {
175
            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
176
            if (m_state.inst(context.pp, context.ip) == CALL)
3,295✔
177
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
1,817✔
178
            else
179
                return *field;
1,680✔
180
        }
181
        else
182
        {
183
            if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
1✔
184
                throwVMError(
1✔
185
                    ErrorKind::Scope,
186
                    fmt::format(
2✔
187
                        "`{0}' isn't in the closure environment: {1}",
1✔
188
                        m_state.m_symbols[id],
1✔
189
                        closure->refClosure().toString(*this)));
1✔
190
            throwVMError(
×
191
                ErrorKind::Scope,
192
                fmt::format(
×
193
                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
×
194
                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
195
                    m_state.m_symbols[id],
×
196
                    closure->refClosure().toString(*this)));
×
197
        }
198
    }
3,297✔
199

200
    Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
1,658✔
201
    {
1,658✔
202
        Value l(ValueType::List);
1,658✔
203
        if (count != 0)
1,658✔
204
            l.list().reserve(count);
630✔
205

206
        for (std::size_t i = 0; i < count; ++i)
3,299✔
207
            l.push_back(*popAndResolveAsPtr(context));
1,641✔
208

209
        return l;
1,658✔
210
    }
1,658✔
211

212
    void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
1,735✔
213
    {
1,735✔
214
        if (list->valueType() != ValueType::List)
1,735✔
215
        {
216
            std::vector<Value> args = { *list };
1✔
217
            for (std::size_t i = 0; i < count; ++i)
2✔
218
                args.push_back(*popAndResolveAsPtr(context));
1✔
219
            throw types::TypeCheckingError(
2✔
220
                "append!",
1✔
221
                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
222
                args);
223
        }
1✔
224

225
        for (std::size_t i = 0; i < count; ++i)
3,468✔
226
            list->push_back(*popAndResolveAsPtr(context));
1,734✔
227
    }
1,735✔
228

229
    Value& VM::operator[](const std::string& name) noexcept
38✔
230
    {
38✔
231
        // find id of object
232
        const auto it = std::ranges::find(m_state.m_symbols, name);
38✔
233
        if (it == m_state.m_symbols.end())
38✔
234
        {
235
            m_no_value = Builtins::nil;
3✔
236
            return m_no_value;
3✔
237
        }
238

239
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
35✔
240
        if (std::cmp_less(dist, MaxValue16Bits))
35✔
241
        {
242
            ExecutionContext& context = *m_execution_contexts.front();
35✔
243

244
            const auto id = static_cast<uint16_t>(dist);
35✔
245
            Value* var = findNearestVariable(id, context);
35✔
246
            if (var != nullptr)
35✔
247
                return *var;
35✔
248
        }
35✔
249

250
        m_no_value = Builtins::nil;
×
251
        return m_no_value;
×
252
    }
38✔
253

254
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
1✔
255
    {
1✔
256
        namespace fs = std::filesystem;
257

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

260
        std::string path = file;
1✔
261
        // bytecode loaded from file
262
        if (m_state.m_filename != ARK_NO_NAME_FILE)
1✔
263
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
1✔
264

265
        std::shared_ptr<SharedLibrary> lib;
1✔
266
        // if it exists alongside the .arkc file
267
        if (Utils::fileExists(path))
1✔
268
            lib = std::make_shared<SharedLibrary>(path);
×
269
        else
270
        {
271
            for (auto const& v : m_state.m_libenv)
3✔
272
            {
273
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
2✔
274

275
                // if it's already loaded don't do anything
276
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
2✔
277
                        return (val->path() == path || val->path() == lib_path);
×
278
                    }) != m_shared_lib_objects.end())
2✔
279
                    return;
×
280

281
                // check in lib_path
282
                if (Utils::fileExists(lib_path))
2✔
283
                {
284
                    lib = std::make_shared<SharedLibrary>(lib_path);
1✔
285
                    break;
1✔
286
                }
287
            }
2✔
288
        }
289

290
        if (!lib)
1✔
291
        {
292
            auto lib_path = std::accumulate(
×
293
                std::next(m_state.m_libenv.begin()),
×
294
                m_state.m_libenv.end(),
×
295
                m_state.m_libenv[0].string(),
×
296
                [](const std::string& a, const fs::path& b) -> std::string {
×
297
                    return a + "\n\t- " + b.string();
×
298
                });
×
299
            throwVMError(
×
300
                ErrorKind::Module,
301
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
302
        }
×
303

304
        m_shared_lib_objects.emplace_back(lib);
1✔
305

306
        // load the mapping from the dynamic library
307
        try
308
        {
309
            std::vector<ScopeView::pair_t> data;
1✔
310
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
1✔
311

312
            std::size_t i = 0;
1✔
313
            while (map[i].name != nullptr)
2✔
314
            {
315
                const auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
1✔
316
                if (it != m_state.m_symbols.end())
1✔
317
                    data.emplace_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
1✔
318

319
                ++i;
1✔
320
            }
1✔
321

322
            context.locals.back().insertFront(data);
1✔
323
        }
1✔
324
        catch (const std::system_error& e)
325
        {
326
            throwVMError(
×
327
                ErrorKind::Module,
328
                fmt::format(
×
329
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
330
                    file, e.what()));
×
331
        }
1✔
332
    }
1✔
333

334
    void VM::exit(const int code) noexcept
×
335
    {
×
336
        m_exit_code = code;
×
337
        m_running = false;
×
338
    }
×
339

340
    ExecutionContext* VM::createAndGetContext()
17✔
341
    {
17✔
342
        const std::lock_guard lock(m_mutex);
17✔
343

344
        ExecutionContext* ctx = nullptr;
17✔
345

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

351
        if (m_execution_contexts.size() > 1)
17✔
352
        {
353
            const auto it = std::ranges::find_if(
28✔
354
                m_execution_contexts,
14✔
355
                [](const std::unique_ptr<ExecutionContext>& context) -> bool {
38✔
356
                    return !context->primary && context->isFree();
38✔
357
                });
358

359
            if (it != m_execution_contexts.end())
14✔
360
            {
361
                ctx = it->get();
10✔
362
                ctx->setActive(true);
10✔
363
                // reset the context before using it
364
                ctx->sp = 0;
10✔
365
                ctx->saved_scope.reset();
10✔
366
                ctx->stacked_closure_scopes.clear();
10✔
367
                ctx->locals.clear();
10✔
368
            }
10✔
369
        }
14✔
370

371
        if (ctx == nullptr)
17✔
372
            ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
7✔
373

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

377
        const ExecutionContext& primary_ctx = *m_execution_contexts.front();
17✔
378
        ctx->locals.reserve(primary_ctx.locals.size());
17✔
379
        ctx->scopes_storage = primary_ctx.scopes_storage;
17✔
380
        ctx->stacked_closure_scopes.emplace_back(nullptr);
17✔
381
        ctx->fc = 1;
17✔
382

383
        for (const auto& scope_view : primary_ctx.locals)
62✔
384
        {
385
            auto& new_scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), scope_view.m_start);
45✔
386
            for (std::size_t i = 0; i < scope_view.size(); ++i)
2,577✔
387
            {
388
                const auto& [id, val] = scope_view.atPos(i);
2,532✔
389
                new_scope.pushBack(id, val);
2,532✔
390
            }
2,532✔
391
        }
45✔
392

393
        return ctx;
17✔
394
    }
17✔
395

396
    void VM::deleteContext(ExecutionContext* ec)
16✔
397
    {
16✔
398
        const std::lock_guard lock(m_mutex);
16✔
399

400
        // 1 + 4 additional contexts, it's a bit much (~600kB per context) to have in memory
401
        if (m_execution_contexts.size() > 5)
16✔
402
        {
403
            const auto it =
1✔
404
                std::ranges::remove_if(
2✔
405
                    m_execution_contexts,
1✔
406
                    [ec](const std::unique_ptr<ExecutionContext>& ctx) {
7✔
407
                        return ctx.get() == ec;
6✔
408
                    })
409
                    .begin();
1✔
410
            m_execution_contexts.erase(it);
1✔
411
        }
1✔
412
        else
413
        {
414
            // mark the used context as ready to be used again
415
            for (std::size_t i = 1; i < m_execution_contexts.size(); ++i)
40✔
416
            {
417
                if (m_execution_contexts[i].get() == ec)
25✔
418
                {
419
                    ec->setActive(false);
15✔
420
                    break;
15✔
421
                }
422
            }
10✔
423
        }
424
    }
16✔
425

426
    Future* VM::createFuture(std::vector<Value>& args)
17✔
427
    {
17✔
428
        const std::lock_guard lock(m_mutex_futures);
17✔
429

430
        ExecutionContext* ctx = createAndGetContext();
17✔
431
        // so that we have access to the presumed symbol id of the function we are calling
432
        // assuming that the callee is always the global context
433
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
17✔
434

435
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
17✔
436
        return m_futures.back().get();
17✔
437
    }
17✔
438

439
    void VM::deleteFuture(Future* f)
1✔
440
    {
1✔
441
        const std::lock_guard lock(m_mutex_futures);
1✔
442

443
        std::erase_if(
1✔
444
            m_futures,
1✔
445
            [f](const std::unique_ptr<Future>& future) {
3✔
446
                return future.get() == f;
2✔
447
            });
448
    }
1✔
449

450
    bool VM::forceReloadPlugins() const
×
451
    {
×
452
        // load the mapping from the dynamic library
453
        try
454
        {
455
            for (const auto& shared_lib : m_shared_lib_objects)
×
456
            {
457
                const mapping* map = shared_lib->get<mapping* (*)()>("getFunctionsMapping")();
×
458
                // load the mapping data
459
                std::size_t i = 0;
×
460
                while (map[i].name != nullptr)
×
461
                {
462
                    // put it in the global frame, aka the first one
463
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
464
                    if (it != m_state.m_symbols.end())
×
465
                        m_execution_contexts[0]->locals[0].pushBack(
×
466
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
467
                            Value(map[i].value));
×
468

469
                    ++i;
×
470
                }
×
471
            }
×
472

473
            return true;
×
474
        }
×
475
        catch (const std::system_error&)
476
        {
477
            return false;
×
478
        }
×
479
    }
×
480

481
    void VM::throwVMError(ErrorKind kind, const std::string& message)
32✔
482
    {
32✔
483
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
32✔
484
    }
32✔
485

486
    int VM::run(const bool fail_with_exception)
203✔
487
    {
203✔
488
        init();
203✔
489
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
203✔
490
        return m_exit_code;
203✔
491
    }
492

493
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
221✔
494
    {
221✔
495
#if ARK_USE_COMPUTED_GOTOS
496
#    define TARGET(op) TARGET_##op:
497
#    define DISPATCH_GOTO()            \
498
        _Pragma("GCC diagnostic push") \
499
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
500
        _Pragma("GCC diagnostic pop")
501
#    define GOTO_HALT() goto dispatch_end
502
#else
503
#    define TARGET(op) case op:
504
#    define DISPATCH_GOTO() goto dispatch_opcode
505
#    define GOTO_HALT() break
506
#endif
507

508
#define NEXTOPARG()                                                                                                               \
509
    do                                                                                                                            \
510
    {                                                                                                                             \
511
        inst = m_state.inst(context.pp, context.ip);                                                                              \
512
        padding = m_state.inst(context.pp, context.ip + 1);                                                                       \
513
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) +                                             \
514
                                    m_state.inst(context.pp, context.ip + 3));                                                    \
515
        context.ip += 4;                                                                                                          \
516
        context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize;                                       \
517
        if (context.inst_exec_counter < 2 && context.sp >= VMStackSize)                                                           \
518
        {                                                                                                                         \
519
            if (context.pp != 0)                                                                                                  \
520
                throw Error("Stack overflow. You could consider rewriting your function to make use of tail-call optimization."); \
521
            else                                                                                                                  \
522
                throw Error("Stack overflow. Are you trying to call a function with too many arguments?");                        \
523
        }                                                                                                                         \
524
    } while (false)
525
#define DISPATCH() \
526
    NEXTOPARG();   \
527
    DISPATCH_GOTO();
528
#define UNPACK_ARGS()                                                                 \
529
    do                                                                                \
530
    {                                                                                 \
531
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
532
        primary_arg = arg & 0x0fff;                                                   \
533
    } while (false)
534

535
#if ARK_USE_COMPUTED_GOTOS
536
#    pragma GCC diagnostic push
537
#    pragma GCC diagnostic ignored "-Wpedantic"
538
            constexpr std::array opcode_targets = {
221✔
539
                // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
540
                &&TARGET_NOP,
541
                &&TARGET_LOAD_SYMBOL,
542
                &&TARGET_LOAD_SYMBOL_BY_INDEX,
543
                &&TARGET_LOAD_CONST,
544
                &&TARGET_POP_JUMP_IF_TRUE,
545
                &&TARGET_STORE,
546
                &&TARGET_STORE_REF,
547
                &&TARGET_SET_VAL,
548
                &&TARGET_POP_JUMP_IF_FALSE,
549
                &&TARGET_JUMP,
550
                &&TARGET_RET,
551
                &&TARGET_HALT,
552
                &&TARGET_PUSH_RETURN_ADDRESS,
553
                &&TARGET_CALL,
554
                &&TARGET_CAPTURE,
555
                &&TARGET_RENAME_NEXT_CAPTURE,
556
                &&TARGET_BUILTIN,
557
                &&TARGET_DEL,
558
                &&TARGET_MAKE_CLOSURE,
559
                &&TARGET_GET_FIELD,
560
                &&TARGET_PLUGIN,
561
                &&TARGET_LIST,
562
                &&TARGET_APPEND,
563
                &&TARGET_CONCAT,
564
                &&TARGET_APPEND_IN_PLACE,
565
                &&TARGET_CONCAT_IN_PLACE,
566
                &&TARGET_POP_LIST,
567
                &&TARGET_POP_LIST_IN_PLACE,
568
                &&TARGET_SET_AT_INDEX,
569
                &&TARGET_SET_AT_2_INDEX,
570
                &&TARGET_POP,
571
                &&TARGET_SHORTCIRCUIT_AND,
572
                &&TARGET_SHORTCIRCUIT_OR,
573
                &&TARGET_CREATE_SCOPE,
574
                &&TARGET_RESET_SCOPE_JUMP,
575
                &&TARGET_POP_SCOPE,
576
                &&TARGET_GET_CURRENT_PAGE_ADDR,
577
                &&TARGET_ADD,
578
                &&TARGET_SUB,
579
                &&TARGET_MUL,
580
                &&TARGET_DIV,
581
                &&TARGET_GT,
582
                &&TARGET_LT,
583
                &&TARGET_LE,
584
                &&TARGET_GE,
585
                &&TARGET_NEQ,
586
                &&TARGET_EQ,
587
                &&TARGET_LEN,
588
                &&TARGET_EMPTY,
589
                &&TARGET_TAIL,
590
                &&TARGET_HEAD,
591
                &&TARGET_ISNIL,
592
                &&TARGET_ASSERT,
593
                &&TARGET_TO_NUM,
594
                &&TARGET_TO_STR,
595
                &&TARGET_AT,
596
                &&TARGET_AT_AT,
597
                &&TARGET_MOD,
598
                &&TARGET_TYPE,
599
                &&TARGET_HASFIELD,
600
                &&TARGET_NOT,
601
                &&TARGET_LOAD_CONST_LOAD_CONST,
602
                &&TARGET_LOAD_CONST_STORE,
603
                &&TARGET_LOAD_CONST_SET_VAL,
604
                &&TARGET_STORE_FROM,
605
                &&TARGET_STORE_FROM_INDEX,
606
                &&TARGET_SET_VAL_FROM,
607
                &&TARGET_SET_VAL_FROM_INDEX,
608
                &&TARGET_INCREMENT,
609
                &&TARGET_INCREMENT_BY_INDEX,
610
                &&TARGET_INCREMENT_STORE,
611
                &&TARGET_DECREMENT,
612
                &&TARGET_DECREMENT_BY_INDEX,
613
                &&TARGET_DECREMENT_STORE,
614
                &&TARGET_STORE_TAIL,
615
                &&TARGET_STORE_TAIL_BY_INDEX,
616
                &&TARGET_STORE_HEAD,
617
                &&TARGET_STORE_HEAD_BY_INDEX,
618
                &&TARGET_STORE_LIST,
619
                &&TARGET_SET_VAL_TAIL,
620
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
621
                &&TARGET_SET_VAL_HEAD,
622
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
623
                &&TARGET_CALL_BUILTIN,
624
                &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
625
                &&TARGET_LT_CONST_JUMP_IF_FALSE,
626
                &&TARGET_LT_CONST_JUMP_IF_TRUE,
627
                &&TARGET_LT_SYM_JUMP_IF_FALSE,
628
                &&TARGET_GT_CONST_JUMP_IF_TRUE,
629
                &&TARGET_GT_CONST_JUMP_IF_FALSE,
630
                &&TARGET_GT_SYM_JUMP_IF_FALSE,
631
                &&TARGET_EQ_CONST_JUMP_IF_TRUE,
632
                &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
633
                &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
634
                &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
635
                &&TARGET_CALL_SYMBOL,
636
                &&TARGET_CALL_CURRENT_PAGE,
637
                &&TARGET_GET_FIELD_FROM_SYMBOL,
638
                &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
639
                &&TARGET_AT_SYM_SYM,
640
                &&TARGET_AT_SYM_INDEX_SYM_INDEX,
641
                &&TARGET_AT_SYM_INDEX_CONST,
642
                &&TARGET_CHECK_TYPE_OF,
643
                &&TARGET_CHECK_TYPE_OF_BY_INDEX,
644
                &&TARGET_APPEND_IN_PLACE_SYM,
645
                &&TARGET_APPEND_IN_PLACE_SYM_INDEX,
646
                &&TARGET_STORE_LEN,
647
                &&TARGET_LT_LEN_SYM_JUMP_IF_FALSE
648
            };
649

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

654
        try
655
        {
656
            uint8_t inst = 0;
221✔
657
            uint8_t padding = 0;
221✔
658
            uint16_t arg = 0;
221✔
659
            uint16_t primary_arg = 0;
221✔
660
            uint16_t secondary_arg = 0;
221✔
661

662
            m_running = true;
221✔
663

664
            DISPATCH();
221✔
665
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
666
            {
667
#if !ARK_USE_COMPUTED_GOTOS
668
            dispatch_opcode:
669
                switch (inst)
670
#endif
671
                {
×
672
#pragma region "Instructions"
673
                    TARGET(NOP)
674
                    {
675
                        DISPATCH();
×
676
                    }
147,726✔
677

678
                    TARGET(LOAD_SYMBOL)
679
                    {
680
                        push(loadSymbol(arg, context), context);
147,726✔
681
                        DISPATCH();
147,726✔
682
                    }
334,793✔
683

684
                    TARGET(LOAD_SYMBOL_BY_INDEX)
685
                    {
686
                        push(loadSymbolFromIndex(arg, context), context);
334,793✔
687
                        DISPATCH();
334,793✔
688
                    }
116,364✔
689

690
                    TARGET(LOAD_CONST)
691
                    {
692
                        push(loadConstAsPtr(arg), context);
116,364✔
693
                        DISPATCH();
116,364✔
694
                    }
30,235✔
695

696
                    TARGET(POP_JUMP_IF_TRUE)
697
                    {
698
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
38,366✔
699
                            jump(arg, context);
8,131✔
700
                        DISPATCH();
30,235✔
701
                    }
402,535✔
702

703
                    TARGET(STORE)
704
                    {
705
                        store(arg, popAndResolveAsPtr(context), context);
402,535✔
706
                        DISPATCH();
402,535✔
707
                    }
391✔
708

709
                    TARGET(STORE_REF)
710
                    {
711
                        // Not resolving a potential ref is on purpose!
712
                        // This instruction is only used by functions when storing arguments
713
                        Value* tmp = pop(context);
391✔
714
                        store(arg, tmp, context);
391✔
715
                        DISPATCH();
391✔
716
                    }
23,886✔
717

718
                    TARGET(SET_VAL)
719
                    {
720
                        setVal(arg, popAndResolveAsPtr(context), context);
23,886✔
721
                        DISPATCH();
23,886✔
722
                    }
18,965✔
723

724
                    TARGET(POP_JUMP_IF_FALSE)
725
                    {
726
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
20,029✔
727
                            jump(arg, context);
1,064✔
728
                        DISPATCH();
18,965✔
729
                    }
208,109✔
730

731
                    TARGET(JUMP)
732
                    {
733
                        jump(arg, context);
208,109✔
734
                        DISPATCH();
208,109✔
735
                    }
138,222✔
736

737
                    TARGET(RET)
738
                    {
739
                        {
740
                            Value ip_or_val = *popAndResolveAsPtr(context);
138,222✔
741
                            // no return value on the stack
742
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
138,222✔
743
                            {
744
                                context.ip = ip_or_val.pageAddr();
3,322✔
745
                                // we always push PP then IP, thus the next value
746
                                // MUST be the page pointer
747
                                context.pp = pop(context)->pageAddr();
3,322✔
748

749
                                returnFromFuncCall(context);
3,322✔
750
                                push(Builtins::nil, context);
3,322✔
751
                            }
3,322✔
752
                            // value on the stack
753
                            else [[likely]]
754
                            {
755
                                const Value* ip = popAndResolveAsPtr(context);
134,900✔
756
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
134,900✔
757
                                context.ip = ip->pageAddr();
134,900✔
758
                                context.pp = pop(context)->pageAddr();
134,900✔
759

760
                                returnFromFuncCall(context);
134,900✔
761
                                push(std::move(ip_or_val), context);
134,900✔
762
                            }
763

764
                            if (context.fc <= untilFrameCount)
138,222✔
765
                                GOTO_HALT();
18✔
766
                        }
138,222✔
767

768
                        DISPATCH();
138,204✔
769
                    }
72✔
770

771
                    TARGET(HALT)
772
                    {
773
                        m_running = false;
72✔
774
                        GOTO_HALT();
72✔
775
                    }
141,239✔
776

777
                    TARGET(PUSH_RETURN_ADDRESS)
778
                    {
779
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
141,239✔
780
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
781
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
141,239✔
782
                        context.inst_exec_counter++;
141,239✔
783
                        DISPATCH();
141,239✔
784
                    }
2,942✔
785

786
                    TARGET(CALL)
787
                    {
788
                        call(context, arg);
2,942✔
789
                        if (!m_running)
2,935✔
790
                            GOTO_HALT();
×
791
                        DISPATCH();
2,935✔
792
                    }
3,189✔
793

794
                    TARGET(CAPTURE)
795
                    {
796
                        if (!context.saved_scope)
3,189✔
797
                            context.saved_scope = ClosureScope();
629✔
798

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

810
                        DISPATCH();
3,189✔
811
                    }
13✔
812

813
                    TARGET(RENAME_NEXT_CAPTURE)
814
                    {
815
                        context.capture_rename_id = arg;
13✔
816
                        DISPATCH();
13✔
817
                    }
1,745✔
818

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

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

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

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

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

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

858
                    TARGET(LIST)
859
                    {
860
                        {
861
                            Value l = createList(arg, context);
707✔
862
                            push(std::move(l), context);
707✔
863
                        }
707✔
864
                        DISPATCH();
707✔
865
                    }
1,552✔
866

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

882
                            const auto size = static_cast<uint16_t>(list->constList().size());
1,551✔
883

884
                            Value obj { *list };
1,551✔
885
                            obj.list().reserve(size + arg);
1,551✔
886

887
                            for (uint16_t i = 0; i < arg; ++i)
3,102✔
888
                                obj.push_back(*popAndResolveAsPtr(context));
1,551✔
889
                            push(std::move(obj), context);
1,551✔
890
                        }
1,551✔
891
                        DISPATCH();
1,551✔
892
                    }
15✔
893

894
                    TARGET(CONCAT)
895
                    {
896
                        {
897
                            Value* list = popAndResolveAsPtr(context);
15✔
898
                            Value obj { *list };
15✔
899

900
                            for (uint16_t i = 0; i < arg; ++i)
30✔
901
                            {
902
                                Value* next = popAndResolveAsPtr(context);
17✔
903

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

910
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
15✔
911
                            }
15✔
912
                            push(std::move(obj), context);
13✔
913
                        }
15✔
914
                        DISPATCH();
13✔
915
                    }
1✔
916

917
                    TARGET(APPEND_IN_PLACE)
918
                    {
919
                        Value* list = popAndResolveAsPtr(context);
1✔
920
                        listAppendInPlace(list, arg, context);
1✔
921
                        DISPATCH();
1✔
922
                    }
570✔
923

924
                    TARGET(CONCAT_IN_PLACE)
925
                    {
926
                        Value* list = popAndResolveAsPtr(context);
570✔
927

928
                        for (uint16_t i = 0; i < arg; ++i)
1,175✔
929
                        {
930
                            Value* next = popAndResolveAsPtr(context);
607✔
931

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

938
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
605✔
939
                        }
605✔
940
                        DISPATCH();
568✔
941
                    }
6✔
942

943
                    TARGET(POP_LIST)
944
                    {
945
                        {
946
                            Value list = *popAndResolveAsPtr(context);
6✔
947
                            Value number = *popAndResolveAsPtr(context);
6✔
948

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

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

962
                            list.list().erase(list.list().begin() + idx);
3✔
963
                            push(list, context);
3✔
964
                        }
6✔
965
                        DISPATCH();
3✔
966
                    }
141✔
967

968
                    TARGET(POP_LIST_IN_PLACE)
969
                    {
970
                        {
971
                            Value* list = popAndResolveAsPtr(context);
141✔
972
                            Value number = *popAndResolveAsPtr(context);
141✔
973

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

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

987
                            list->list().erase(list->list().begin() + idx);
138✔
988
                        }
141✔
989
                        DISPATCH();
138✔
990
                    }
490✔
991

992
                    TARGET(SET_AT_INDEX)
993
                    {
994
                        {
995
                            Value* list = popAndResolveAsPtr(context);
490✔
996
                            Value number = *popAndResolveAsPtr(context);
490✔
997
                            Value new_value = *popAndResolveAsPtr(context);
490✔
998

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

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

1020
                            if (list->valueType() == ValueType::List)
487✔
1021
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
1022
                            else
1023
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
1024
                        }
490✔
1025
                        DISPATCH();
487✔
1026
                    }
12✔
1027

1028
                    TARGET(SET_AT_2_INDEX)
1029
                    {
1030
                        {
1031
                            Value* list = popAndResolveAsPtr(context);
12✔
1032
                            Value x = *popAndResolveAsPtr(context);
12✔
1033
                            Value y = *popAndResolveAsPtr(context);
12✔
1034
                            Value new_value = *popAndResolveAsPtr(context);
12✔
1035

1036
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
12✔
1037
                                throw types::TypeCheckingError(
2✔
1038
                                    "@@=",
1✔
1039
                                    { { types::Contract {
2✔
1040
                                        { types::Typedef("list", ValueType::List),
4✔
1041
                                          types::Typedef("x", ValueType::Number),
1✔
1042
                                          types::Typedef("y", ValueType::Number),
1✔
1043
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
1044
                                    { *list, x, y, new_value });
1✔
1045

1046
                            long idx_y = static_cast<long>(x.number());
11✔
1047
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
11✔
1048
                            if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
11✔
1049
                                throwVMError(
2✔
1050
                                    ErrorKind::Index,
1051
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
1052

1053
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
13✔
1054
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
8✔
1055
                                throw types::TypeCheckingError(
2✔
1056
                                    "@@=",
1✔
1057
                                    { { types::Contract {
3✔
1058
                                          { types::Typedef("list", ValueType::List),
4✔
1059
                                            types::Typedef("x", ValueType::Number),
1✔
1060
                                            types::Typedef("y", ValueType::Number),
1✔
1061
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
1062
                                      { types::Contract {
1✔
1063
                                          { types::Typedef("string", ValueType::String),
4✔
1064
                                            types::Typedef("x", ValueType::Number),
1✔
1065
                                            types::Typedef("y", ValueType::Number),
1✔
1066
                                            types::Typedef("char", ValueType::String) } } } },
1✔
1067
                                    { *list, x, y, new_value });
1✔
1068

1069
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
1070
                            const std::size_t size =
8✔
1071
                                is_list
16✔
1072
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
1073
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1074

1075
                            long idx_x = static_cast<long>(y.number());
8✔
1076
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
8✔
1077
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
8✔
1078
                                throwVMError(
2✔
1079
                                    ErrorKind::Index,
1080
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1081

1082
                            if (is_list)
6✔
1083
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1084
                            else
1085
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1086
                        }
12✔
1087
                        DISPATCH();
6✔
1088
                    }
3,289✔
1089

1090
                    TARGET(POP)
1091
                    {
1092
                        pop(context);
3,289✔
1093
                        DISPATCH();
3,289✔
1094
                    }
23,879✔
1095

1096
                    TARGET(SHORTCIRCUIT_AND)
1097
                    {
1098
                        if (!*peekAndResolveAsPtr(context))
23,879✔
1099
                            jump(arg, context);
819✔
1100
                        else
1101
                            pop(context);
23,060✔
1102
                        DISPATCH();
23,879✔
1103
                    }
851✔
1104

1105
                    TARGET(SHORTCIRCUIT_OR)
1106
                    {
1107
                        if (!!*peekAndResolveAsPtr(context))
851✔
1108
                            jump(arg, context);
219✔
1109
                        else
1110
                            pop(context);
632✔
1111
                        DISPATCH();
851✔
1112
                    }
2,973✔
1113

1114
                    TARGET(CREATE_SCOPE)
1115
                    {
1116
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
2,973✔
1117
                        DISPATCH();
2,973✔
1118
                    }
32,843✔
1119

1120
                    TARGET(RESET_SCOPE_JUMP)
1121
                    {
1122
                        context.locals.back().reset();
32,843✔
1123
                        jump(arg, context);
32,843✔
1124
                        DISPATCH();
32,843✔
1125
                    }
2,972✔
1126

1127
                    TARGET(POP_SCOPE)
1128
                    {
1129
                        context.locals.pop_back();
2,972✔
1130
                        DISPATCH();
2,972✔
1131
                    }
×
1132

1133
                    TARGET(GET_CURRENT_PAGE_ADDR)
1134
                    {
1135
                        context.last_symbol = arg;
×
1136
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1137
                        DISPATCH();
×
1138
                    }
28,265✔
1139

1140
#pragma endregion
1141

1142
#pragma region "Operators"
1143

1144
                    TARGET(ADD)
1145
                    {
1146
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,265✔
1147

1148
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
28,265✔
1149
                            push(Value(a->number() + b->number()), context);
19,578✔
1150
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
8,687✔
1151
                            push(Value(a->string() + b->string()), context);
8,686✔
1152
                        else
1153
                            throw types::TypeCheckingError(
2✔
1154
                                "+",
1✔
1155
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1156
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1157
                                { *a, *b });
1✔
1158
                        DISPATCH();
28,264✔
1159
                    }
379✔
1160

1161
                    TARGET(SUB)
1162
                    {
1163
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
379✔
1164

1165
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
379✔
1166
                            throw types::TypeCheckingError(
2✔
1167
                                "-",
1✔
1168
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1169
                                { *a, *b });
1✔
1170
                        push(Value(a->number() - b->number()), context);
378✔
1171
                        DISPATCH();
378✔
1172
                    }
2,319✔
1173

1174
                    TARGET(MUL)
1175
                    {
1176
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,319✔
1177

1178
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
2,319✔
1179
                            throw types::TypeCheckingError(
2✔
1180
                                "*",
1✔
1181
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1182
                                { *a, *b });
1✔
1183
                        push(Value(a->number() * b->number()), context);
2,318✔
1184
                        DISPATCH();
2,318✔
1185
                    }
1,104✔
1186

1187
                    TARGET(DIV)
1188
                    {
1189
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,104✔
1190

1191
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,104✔
1192
                            throw types::TypeCheckingError(
2✔
1193
                                "/",
1✔
1194
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1195
                                { *a, *b });
1✔
1196
                        auto d = b->number();
1,103✔
1197
                        if (d == 0)
1,103✔
1198
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1199

1200
                        push(Value(a->number() / d), context);
1,102✔
1201
                        DISPATCH();
1,102✔
1202
                    }
168✔
1203

1204
                    TARGET(GT)
1205
                    {
1206
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
168✔
1207
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
168✔
1208
                        DISPATCH();
168✔
1209
                    }
21,056✔
1210

1211
                    TARGET(LT)
1212
                    {
1213
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
21,056✔
1214
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
21,056✔
1215
                        DISPATCH();
21,056✔
1216
                    }
7,272✔
1217

1218
                    TARGET(LE)
1219
                    {
1220
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,272✔
1221
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,272✔
1222
                        DISPATCH();
7,272✔
1223
                    }
5,918✔
1224

1225
                    TARGET(GE)
1226
                    {
1227
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,918✔
1228
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,918✔
1229
                        DISPATCH();
5,918✔
1230
                    }
1,178✔
1231

1232
                    TARGET(NEQ)
1233
                    {
1234
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,178✔
1235
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1,178✔
1236
                        DISPATCH();
1,178✔
1237
                    }
17,772✔
1238

1239
                    TARGET(EQ)
1240
                    {
1241
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17,772✔
1242
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
17,772✔
1243
                        DISPATCH();
17,772✔
1244
                    }
3,840✔
1245

1246
                    TARGET(LEN)
1247
                    {
1248
                        const Value* a = popAndResolveAsPtr(context);
3,840✔
1249

1250
                        if (a->valueType() == ValueType::List)
3,840✔
1251
                            push(Value(static_cast<int>(a->constList().size())), context);
1,495✔
1252
                        else if (a->valueType() == ValueType::String)
2,345✔
1253
                            push(Value(static_cast<int>(a->string().size())), context);
2,344✔
1254
                        else
1255
                            throw types::TypeCheckingError(
2✔
1256
                                "len",
1✔
1257
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1258
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1259
                                { *a });
1✔
1260
                        DISPATCH();
3,839✔
1261
                    }
625✔
1262

1263
                    TARGET(EMPTY)
1264
                    {
1265
                        const Value* a = popAndResolveAsPtr(context);
625✔
1266

1267
                        if (a->valueType() == ValueType::List)
625✔
1268
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
126✔
1269
                        else if (a->valueType() == ValueType::String)
499✔
1270
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
498✔
1271
                        else
1272
                            throw types::TypeCheckingError(
2✔
1273
                                "empty?",
1✔
1274
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1275
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1276
                                { *a });
1✔
1277
                        DISPATCH();
624✔
1278
                    }
335✔
1279

1280
                    TARGET(TAIL)
1281
                    {
1282
                        Value* const a = popAndResolveAsPtr(context);
335✔
1283
                        push(helper::tail(a), context);
335✔
1284
                        DISPATCH();
334✔
1285
                    }
1,122✔
1286

1287
                    TARGET(HEAD)
1288
                    {
1289
                        Value* const a = popAndResolveAsPtr(context);
1,122✔
1290
                        push(helper::head(a), context);
1,122✔
1291
                        DISPATCH();
1,121✔
1292
                    }
2,377✔
1293

1294
                    TARGET(ISNIL)
1295
                    {
1296
                        const Value* a = popAndResolveAsPtr(context);
2,377✔
1297
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,377✔
1298
                        DISPATCH();
2,377✔
1299
                    }
655✔
1300

1301
                    TARGET(ASSERT)
1302
                    {
1303
                        Value* const b = popAndResolveAsPtr(context);
655✔
1304
                        Value* const a = popAndResolveAsPtr(context);
655✔
1305

1306
                        if (b->valueType() != ValueType::String)
655✔
1307
                            throw types::TypeCheckingError(
2✔
1308
                                "assert",
1✔
1309
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1310
                                { *a, *b });
1✔
1311

1312
                        if (*a == Builtins::falseSym)
654✔
1313
                            throw AssertionFailed(b->stringRef());
1✔
1314
                        DISPATCH();
653✔
1315
                    }
15✔
1316

1317
                    TARGET(TO_NUM)
1318
                    {
1319
                        const Value* a = popAndResolveAsPtr(context);
15✔
1320

1321
                        if (a->valueType() != ValueType::String)
15✔
1322
                            throw types::TypeCheckingError(
2✔
1323
                                "toNumber",
1✔
1324
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1325
                                { *a });
1✔
1326

1327
                        double val;
1328
                        if (Utils::isDouble(a->string(), &val))
14✔
1329
                            push(Value(val), context);
11✔
1330
                        else
1331
                            push(Builtins::nil, context);
3✔
1332
                        DISPATCH();
14✔
1333
                    }
145✔
1334

1335
                    TARGET(TO_STR)
1336
                    {
1337
                        const Value* a = popAndResolveAsPtr(context);
145✔
1338
                        push(Value(a->toString(*this)), context);
145✔
1339
                        DISPATCH();
145✔
1340
                    }
121✔
1341

1342
                    TARGET(AT)
1343
                    {
1344
                        Value& b = *popAndResolveAsPtr(context);
121✔
1345
                        Value& a = *popAndResolveAsPtr(context);
121✔
1346
                        push(helper::at(a, b, *this), context);
121✔
1347
                        DISPATCH();
119✔
1348
                    }
26✔
1349

1350
                    TARGET(AT_AT)
1351
                    {
1352
                        {
1353
                            const Value* x = popAndResolveAsPtr(context);
26✔
1354
                            const Value* y = popAndResolveAsPtr(context);
26✔
1355
                            Value& list = *popAndResolveAsPtr(context);
26✔
1356

1357
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
26✔
1358
                                list.valueType() != ValueType::List)
25✔
1359
                                throw types::TypeCheckingError(
2✔
1360
                                    "@@",
1✔
1361
                                    { { types::Contract {
2✔
1362
                                        { types::Typedef("src", ValueType::List),
3✔
1363
                                          types::Typedef("y", ValueType::Number),
1✔
1364
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1365
                                    { list, *y, *x });
1✔
1366

1367
                            long idx_y = static_cast<long>(y->number());
25✔
1368
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
25✔
1369
                            if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
25✔
1370
                                throwVMError(
2✔
1371
                                    ErrorKind::Index,
1372
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
2✔
1373

1374
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
23✔
1375
                            const std::size_t size =
23✔
1376
                                is_list
46✔
1377
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
16✔
1378
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1379

1380
                            long idx_x = static_cast<long>(x->number());
23✔
1381
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
23✔
1382
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
23✔
1383
                                throwVMError(
2✔
1384
                                    ErrorKind::Index,
1385
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1386

1387
                            if (is_list)
21✔
1388
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
14✔
1389
                            else
1390
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1391
                        }
1392
                        DISPATCH();
21✔
1393
                    }
16,371✔
1394

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

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

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

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

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

1438
                    TARGET(NOT)
1439
                    {
1440
                        const Value* a = popAndResolveAsPtr(context);
3,666✔
1441
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,666✔
1442
                        DISPATCH();
3,666✔
1443
                    }
8,222✔
1444

1445
#pragma endregion
1446

1447
#pragma region "Super Instructions"
1448
                    TARGET(LOAD_CONST_LOAD_CONST)
1449
                    {
1450
                        UNPACK_ARGS();
8,222✔
1451
                        push(loadConstAsPtr(primary_arg), context);
8,222✔
1452
                        push(loadConstAsPtr(secondary_arg), context);
8,222✔
1453
                        context.inst_exec_counter++;
8,222✔
1454
                        DISPATCH();
8,222✔
1455
                    }
9,241✔
1456

1457
                    TARGET(LOAD_CONST_STORE)
1458
                    {
1459
                        UNPACK_ARGS();
9,241✔
1460
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
9,241✔
1461
                        DISPATCH();
9,241✔
1462
                    }
299✔
1463

1464
                    TARGET(LOAD_CONST_SET_VAL)
1465
                    {
1466
                        UNPACK_ARGS();
299✔
1467
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
299✔
1468
                        DISPATCH();
298✔
1469
                    }
25✔
1470

1471
                    TARGET(STORE_FROM)
1472
                    {
1473
                        UNPACK_ARGS();
25✔
1474
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
25✔
1475
                        DISPATCH();
24✔
1476
                    }
1,190✔
1477

1478
                    TARGET(STORE_FROM_INDEX)
1479
                    {
1480
                        UNPACK_ARGS();
1,190✔
1481
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,190✔
1482
                        DISPATCH();
1,190✔
1483
                    }
567✔
1484

1485
                    TARGET(SET_VAL_FROM)
1486
                    {
1487
                        UNPACK_ARGS();
567✔
1488
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
567✔
1489
                        DISPATCH();
567✔
1490
                    }
315✔
1491

1492
                    TARGET(SET_VAL_FROM_INDEX)
1493
                    {
1494
                        UNPACK_ARGS();
315✔
1495
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
315✔
1496
                        DISPATCH();
315✔
1497
                    }
50✔
1498

1499
                    TARGET(INCREMENT)
1500
                    {
1501
                        UNPACK_ARGS();
50✔
1502
                        {
1503
                            Value* var = loadSymbol(primary_arg, context);
50✔
1504

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

1509
                            if (var->valueType() == ValueType::Number)
50✔
1510
                                push(Value(var->number() + secondary_arg), context);
49✔
1511
                            else
1512
                                throw types::TypeCheckingError(
2✔
1513
                                    "+",
1✔
1514
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1515
                                    { *var, Value(secondary_arg) });
1✔
1516
                        }
1517
                        DISPATCH();
49✔
1518
                    }
88,015✔
1519

1520
                    TARGET(INCREMENT_BY_INDEX)
1521
                    {
1522
                        UNPACK_ARGS();
88,015✔
1523
                        {
1524
                            Value* var = loadSymbolFromIndex(primary_arg, context);
88,015✔
1525

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

1530
                            if (var->valueType() == ValueType::Number)
88,015✔
1531
                                push(Value(var->number() + secondary_arg), context);
88,014✔
1532
                            else
1533
                                throw types::TypeCheckingError(
2✔
1534
                                    "+",
1✔
1535
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1536
                                    { *var, Value(secondary_arg) });
1✔
1537
                        }
1538
                        DISPATCH();
88,014✔
1539
                    }
32,935✔
1540

1541
                    TARGET(INCREMENT_STORE)
1542
                    {
1543
                        UNPACK_ARGS();
32,935✔
1544
                        {
1545
                            Value* var = loadSymbol(primary_arg, context);
32,935✔
1546

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

1551
                            if (var->valueType() == ValueType::Number)
32,935✔
1552
                            {
1553
                                auto val = Value(var->number() + secondary_arg);
32,934✔
1554
                                setVal(primary_arg, &val, context);
32,934✔
1555
                            }
32,934✔
1556
                            else
1557
                                throw types::TypeCheckingError(
2✔
1558
                                    "+",
1✔
1559
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1560
                                    { *var, Value(secondary_arg) });
1✔
1561
                        }
1562
                        DISPATCH();
32,934✔
1563
                    }
1,840✔
1564

1565
                    TARGET(DECREMENT)
1566
                    {
1567
                        UNPACK_ARGS();
1,840✔
1568
                        {
1569
                            Value* var = loadSymbol(primary_arg, context);
1,840✔
1570

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

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

1586
                    TARGET(DECREMENT_BY_INDEX)
1587
                    {
1588
                        UNPACK_ARGS();
194,410✔
1589
                        {
1590
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,410✔
1591

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

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

1607
                    TARGET(DECREMENT_STORE)
1608
                    {
1609
                        UNPACK_ARGS();
733✔
1610
                        {
1611
                            Value* var = loadSymbol(primary_arg, context);
733✔
1612

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

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

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

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

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

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

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

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

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

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

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

1729
                    TARGET(CALL_BUILTIN)
1730
                    {
1731
                        UNPACK_ARGS();
960✔
1732
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1733
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
960✔
1734
                        if (!m_running)
899✔
1735
                            GOTO_HALT();
×
1736
                        DISPATCH();
899✔
1737
                    }
11,658✔
1738

1739
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1740
                    {
1741
                        UNPACK_ARGS();
11,658✔
1742
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1743
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,658✔
1744
                        if (!m_running)
11,657✔
1745
                            GOTO_HALT();
×
1746
                        DISPATCH();
11,657✔
1747
                    }
857✔
1748

1749
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1750
                    {
1751
                        UNPACK_ARGS();
857✔
1752
                        const Value* sym = popAndResolveAsPtr(context);
857✔
1753
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
857✔
1754
                            jump(secondary_arg, context);
122✔
1755
                        DISPATCH();
857✔
1756
                    }
21,936✔
1757

1758
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1759
                    {
1760
                        UNPACK_ARGS();
21,936✔
1761
                        const Value* sym = popAndResolveAsPtr(context);
21,936✔
1762
                        if (*sym < *loadConstAsPtr(primary_arg))
21,936✔
1763
                            jump(secondary_arg, context);
10,957✔
1764
                        DISPATCH();
21,936✔
1765
                    }
6,859✔
1766

1767
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1768
                    {
1769
                        UNPACK_ARGS();
6,859✔
1770
                        const Value* sym = popAndResolveAsPtr(context);
6,859✔
1771
                        if (!(*sym < *loadSymbol(primary_arg, context)))
6,859✔
1772
                            jump(secondary_arg, context);
613✔
1773
                        DISPATCH();
6,859✔
1774
                    }
172,506✔
1775

1776
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1777
                    {
1778
                        UNPACK_ARGS();
172,506✔
1779
                        const Value* sym = popAndResolveAsPtr(context);
172,506✔
1780
                        const Value* cst = loadConstAsPtr(primary_arg);
172,506✔
1781
                        if (*cst < *sym)
172,506✔
1782
                            jump(secondary_arg, context);
86,589✔
1783
                        DISPATCH();
172,506✔
1784
                    }
16✔
1785

1786
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1787
                    {
1788
                        UNPACK_ARGS();
16✔
1789
                        const Value* sym = popAndResolveAsPtr(context);
16✔
1790
                        const Value* cst = loadConstAsPtr(primary_arg);
16✔
1791
                        if (!(*cst < *sym))
16✔
1792
                            jump(secondary_arg, context);
4✔
1793
                        DISPATCH();
16✔
1794
                    }
6✔
1795

1796
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1797
                    {
1798
                        UNPACK_ARGS();
6✔
1799
                        const Value* sym = popAndResolveAsPtr(context);
6✔
1800
                        const Value* rhs = loadSymbol(primary_arg, context);
6✔
1801
                        if (!(*rhs < *sym))
6✔
1802
                            jump(secondary_arg, context);
1✔
1803
                        DISPATCH();
6✔
1804
                    }
1,202✔
1805

1806
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1807
                    {
1808
                        UNPACK_ARGS();
1,202✔
1809
                        const Value* sym = popAndResolveAsPtr(context);
1,202✔
1810
                        if (*sym == *loadConstAsPtr(primary_arg))
1,202✔
1811
                            jump(secondary_arg, context);
146✔
1812
                        DISPATCH();
1,202✔
1813
                    }
87,011✔
1814

1815
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1816
                    {
1817
                        UNPACK_ARGS();
87,011✔
1818
                        const Value* sym = popAndResolveAsPtr(context);
87,011✔
1819
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
87,011✔
1820
                            jump(secondary_arg, context);
576✔
1821
                        DISPATCH();
87,011✔
1822
                    }
14✔
1823

1824
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1825
                    {
1826
                        UNPACK_ARGS();
14✔
1827
                        const Value* sym = popAndResolveAsPtr(context);
14✔
1828
                        if (*sym != *loadConstAsPtr(primary_arg))
14✔
1829
                            jump(secondary_arg, context);
5✔
1830
                        DISPATCH();
14✔
1831
                    }
30✔
1832

1833
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1834
                    {
1835
                        UNPACK_ARGS();
30✔
1836
                        const Value* sym = popAndResolveAsPtr(context);
30✔
1837
                        if (*sym == *loadSymbol(primary_arg, context))
30✔
1838
                            jump(secondary_arg, context);
10✔
1839
                        DISPATCH();
30✔
1840
                    }
27,458✔
1841

1842
                    TARGET(CALL_SYMBOL)
1843
                    {
1844
                        UNPACK_ARGS();
27,458✔
1845
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
27,458✔
1846
                        if (!m_running)
27,456✔
1847
                            GOTO_HALT();
×
1848
                        DISPATCH();
27,456✔
1849
                    }
109,875✔
1850

1851
                    TARGET(CALL_CURRENT_PAGE)
1852
                    {
1853
                        UNPACK_ARGS();
109,875✔
1854
                        context.last_symbol = primary_arg;
109,875✔
1855
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,875✔
1856
                        if (!m_running)
109,874✔
1857
                            GOTO_HALT();
×
1858
                        DISPATCH();
109,874✔
1859
                    }
2,449✔
1860

1861
                    TARGET(GET_FIELD_FROM_SYMBOL)
1862
                    {
1863
                        UNPACK_ARGS();
2,449✔
1864
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
2,449✔
1865
                        DISPATCH();
2,449✔
1866
                    }
842✔
1867

1868
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1869
                    {
1870
                        UNPACK_ARGS();
842✔
1871
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
842✔
1872
                        DISPATCH();
840✔
1873
                    }
16,097✔
1874

1875
                    TARGET(AT_SYM_SYM)
1876
                    {
1877
                        UNPACK_ARGS();
16,097✔
1878
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
16,097✔
1879
                        DISPATCH();
16,097✔
1880
                    }
49✔
1881

1882
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1883
                    {
1884
                        UNPACK_ARGS();
49✔
1885
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
49✔
1886
                        DISPATCH();
49✔
1887
                    }
1,044✔
1888

1889
                    TARGET(AT_SYM_INDEX_CONST)
1890
                    {
1891
                        UNPACK_ARGS();
1,044✔
1892
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
1,044✔
1893
                        DISPATCH();
1,042✔
1894
                    }
5✔
1895

1896
                    TARGET(CHECK_TYPE_OF)
1897
                    {
1898
                        UNPACK_ARGS();
5✔
1899
                        const Value* sym = loadSymbol(primary_arg, context);
5✔
1900
                        const Value* cst = loadConstAsPtr(secondary_arg);
5✔
1901
                        push(
5✔
1902
                            cst->valueType() == ValueType::String &&
10✔
1903
                                    std::to_string(sym->valueType()) == cst->string()
5✔
1904
                                ? Builtins::trueSym
1905
                                : Builtins::falseSym,
1906
                            context);
5✔
1907
                        DISPATCH();
5✔
1908
                    }
86✔
1909

1910
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1911
                    {
1912
                        UNPACK_ARGS();
86✔
1913
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
86✔
1914
                        const Value* cst = loadConstAsPtr(secondary_arg);
86✔
1915
                        push(
86✔
1916
                            cst->valueType() == ValueType::String &&
172✔
1917
                                    std::to_string(sym->valueType()) == cst->string()
86✔
1918
                                ? Builtins::trueSym
1919
                                : Builtins::falseSym,
1920
                            context);
86✔
1921
                        DISPATCH();
86✔
1922
                    }
1,720✔
1923

1924
                    TARGET(APPEND_IN_PLACE_SYM)
1925
                    {
1926
                        UNPACK_ARGS();
1,720✔
1927
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1,720✔
1928
                        DISPATCH();
1,720✔
1929
                    }
14✔
1930

1931
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1932
                    {
1933
                        UNPACK_ARGS();
14✔
1934
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
14✔
1935
                        DISPATCH();
13✔
1936
                    }
104✔
1937

1938
                    TARGET(STORE_LEN)
1939
                    {
1940
                        UNPACK_ARGS();
104✔
1941
                        {
1942
                            Value* a = loadSymbolFromIndex(primary_arg, context);
104✔
1943
                            Value len;
104✔
1944
                            if (a->valueType() == ValueType::List)
104✔
1945
                                len = Value(static_cast<int>(a->constList().size()));
28✔
1946
                            else if (a->valueType() == ValueType::String)
76✔
1947
                                len = Value(static_cast<int>(a->string().size()));
75✔
1948
                            else
1949
                                throw types::TypeCheckingError(
2✔
1950
                                    "len",
1✔
1951
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1952
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1953
                                    { *a });
1✔
1954
                            store(secondary_arg, &len, context);
103✔
1955
                        }
104✔
1956
                        DISPATCH();
103✔
1957
                    }
9,083✔
1958

1959
                    TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
1960
                    {
1961
                        UNPACK_ARGS();
9,083✔
1962
                        {
1963
                            const Value* sym = loadSymbol(primary_arg, context);
9,083✔
1964
                            Value size;
9,083✔
1965

1966
                            if (sym->valueType() == ValueType::List)
9,083✔
1967
                                size = Value(static_cast<int>(sym->constList().size()));
3,412✔
1968
                            else if (sym->valueType() == ValueType::String)
5,671✔
1969
                                size = Value(static_cast<int>(sym->string().size()));
5,670✔
1970
                            else
1971
                                throw types::TypeCheckingError(
2✔
1972
                                    "len",
1✔
1973
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1974
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1975
                                    { *sym });
1✔
1976

1977
                            if (!(*popAndResolveAsPtr(context) < size))
9,082✔
1978
                                jump(secondary_arg, context);
1,158✔
1979
                        }
9,083✔
1980
                        DISPATCH();
9,082✔
1981
                    }
1982
#pragma endregion
1983
                }
90✔
1984
#if ARK_USE_COMPUTED_GOTOS
1985
            dispatch_end:
1986
                do
90✔
1987
                {
1988
                } while (false);
90✔
1989
#endif
1990
            }
1991
        }
221✔
1992
        catch (const Error& e)
1993
        {
1994
            if (fail_with_exception)
89✔
1995
            {
1996
                std::stringstream stream;
89✔
1997
                backtrace(context, stream, /* colorize= */ false);
89✔
1998
                // It's important we have an Ark::Error here, as the constructor for NestedError
1999
                // does more than just aggregate error messages, hence the code duplication.
2000
                throw NestedError(e, stream.str());
89✔
2001
            }
89✔
2002
            else
2003
                showBacktraceWithException(Error(e.details(/* colorize= */ true)), context);
×
2004
        }
179✔
2005
        catch (const std::exception& e)
2006
        {
2007
            if (fail_with_exception)
42✔
2008
            {
2009
                std::stringstream stream;
42✔
2010
                backtrace(context, stream, /* colorize= */ false);
42✔
2011
                throw NestedError(e, stream.str());
42✔
2012
            }
42✔
2013
            else
2014
                showBacktraceWithException(e, context);
×
2015
        }
131✔
2016
        catch (...)
2017
        {
2018
            if (fail_with_exception)
×
2019
                throw;
×
2020

2021
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2022
            throw;
2023
#endif
2024
            fmt::println("Unknown error");
×
2025
            backtrace(context);
×
2026
            m_exit_code = 1;
×
2027
        }
173✔
2028

2029
        return m_exit_code;
90✔
2030
    }
262✔
2031

2032
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,054✔
2033
    {
2,054✔
2034
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,196✔
2035
        {
2036
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,142✔
2037
                return id;
2,050✔
2038
        }
2,096,142✔
2039
        return MaxValue16Bits;
4✔
2040
    }
2,054✔
2041

2042
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
5✔
2043
    {
5✔
2044
        std::vector<std::string> arg_names;
5✔
2045
        arg_names.reserve(expected_arg_count + 1);
5✔
2046
        if (expected_arg_count > 0)
5✔
2047
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
2048

2049
        std::size_t index = 0;
5✔
2050
        while (m_state.inst(context.pp, index) == STORE ||
10✔
2051
               m_state.inst(context.pp, index) == STORE_REF)
5✔
2052
        {
2053
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
2054
            arg_names.push_back(m_state.m_symbols[id]);
×
2055
            index += 4;
×
2056
        }
×
2057
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2058
        if (arg_names.size() == 1 && index == 0)
5✔
2059
        {
2060
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
2061
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
2062
                arg_names.push_back(std::string(1, static_cast<char>('a' + i)));
2✔
2063
        }
2✔
2064

2065
        std::vector<std::string> arg_vals;
5✔
2066
        arg_vals.reserve(passed_arg_count + 1);
5✔
2067
        if (passed_arg_count > 0)
5✔
2068
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
4✔
2069

2070
        for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.sp; ++i)
15✔
2071
            // -1 on the stack because we always point to the next available slot
2072
            arg_vals.push_back(context.stack[context.sp - i - 1].toString(*this));
10✔
2073

2074
        // set ip/pp to the callee location so that the error can pinpoint the line
2075
        // where the bad call happened
2076
        if (context.sp >= 2 + passed_arg_count)
5✔
2077
        {
2078
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
5✔
2079
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
5✔
2080
            returnFromFuncCall(context);
5✔
2081
        }
5✔
2082

2083
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
10✔
2084
            ? m_state.m_symbols[context.last_symbol]
5✔
2085
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2086

2087
        throwVMError(
5✔
2088
            ErrorKind::Arity,
2089
            fmt::format(
10✔
2090
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
5✔
2091
                function_name,
2092
                fmt::join(arg_vals, " "),
5✔
2093
                passed_arg_count,
2094
                passed_arg_count > 1 ? "s" : "",
5✔
2095
                expected_arg_count,
2096
                function_name,
2097
                fmt::join(arg_names, " ")));
5✔
2098
    }
10✔
2099

2100
    void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
×
2101
    {
×
2102
        std::string text = e.what();
×
2103
        if (!text.empty() && text.back() != '\n')
×
2104
            text += '\n';
×
2105
        fmt::println("{}", text);
×
2106
        backtrace(context);
×
2107
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2108
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2109
        m_exit_code = 0;
2110
#else
2111
        m_exit_code = 1;
×
2112
#endif
2113
    }
×
2114

2115
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,192✔
2116
    {
2,192✔
2117
        std::optional<InstLoc> match = std::nullopt;
2,192✔
2118

2119
        for (const auto location : m_state.m_inst_locations)
10,907✔
2120
        {
2121
            if (location.page_pointer == pp && !match)
8,715✔
2122
                match = location;
2,192✔
2123

2124
            // select the best match: we want to find the location that's nearest our instruction pointer,
2125
            // but not equal to it as the IP will always be pointing to the next instruction,
2126
            // not yet executed. Thus, the erroneous instruction is the previous one.
2127
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
8,715✔
2128
                match = location;
2,328✔
2129

2130
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2131
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
8,715✔
2132
                break;
2,069✔
2133
        }
8,715✔
2134

2135
        return match;
2,192✔
2136
    }
2137

2138
    std::string VM::debugShowSource() const
×
2139
    {
×
2140
        const auto& context = m_execution_contexts.front();
×
2141
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2142
        if (maybe_source_loc)
×
2143
        {
2144
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2145
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2146
        }
×
2147
        return "No source location found";
×
2148
    }
×
2149

2150
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
131✔
2151
    {
131✔
2152
        const std::size_t saved_ip = context.ip;
131✔
2153
        const std::size_t saved_pp = context.pp;
131✔
2154
        const uint16_t saved_sp = context.sp;
131✔
2155
        constexpr std::size_t max_consecutive_traces = 7;
131✔
2156

2157
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
131✔
2158
        if (maybe_location)
131✔
2159
        {
2160
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
131✔
2161

2162
            if (Utils::fileExists(filename))
131✔
2163
                Diagnostics::makeContext(
258✔
2164
                    Diagnostics::ErrorLocation {
258✔
2165
                        .filename = filename,
129✔
2166
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
129✔
2167
                        .end = std::nullopt },
129✔
2168
                    os,
129✔
2169
                    /* maybe_context= */ std::nullopt,
129✔
2170
                    /* colorize= */ colorize);
129✔
2171
            fmt::println(os, "");
131✔
2172
        }
131✔
2173

2174
        if (context.fc > 1)
131✔
2175
        {
2176
            // display call stack trace
2177
            const ScopeView old_scope = context.locals.back();
7✔
2178

2179
            std::string previous_trace;
7✔
2180
            std::size_t displayed_traces = 0;
7✔
2181
            std::size_t consecutive_similar_traces = 0;
7✔
2182

2183
            while (context.fc != 0 && context.pp != 0)
2,061✔
2184
            {
2185
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,054✔
2186
                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✔
2187

2188
                const uint16_t id = findNearestVariableIdWithValue(
2,054✔
2189
                    Value(static_cast<PageAddr_t>(context.pp)),
2,054✔
2190
                    context);
2,054✔
2191
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,054✔
2192

2193
                if (func_name + loc_as_text != previous_trace)
2,054✔
2194
                {
2195
                    fmt::println(
16✔
2196
                        os,
8✔
2197
                        "[{:4}] In function `{}'{}",
8✔
2198
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
8✔
2199
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
8✔
2200
                        loc_as_text);
2201
                    previous_trace = func_name + loc_as_text;
8✔
2202
                    ++displayed_traces;
8✔
2203
                    consecutive_similar_traces = 0;
8✔
2204
                }
8✔
2205
                else if (consecutive_similar_traces == 0)
2,046✔
2206
                {
2207
                    fmt::println(os, "       ...");
1✔
2208
                    ++consecutive_similar_traces;
1✔
2209
                }
1✔
2210

2211
                const Value* ip;
2,054✔
2212
                do
6,259✔
2213
                {
2214
                    ip = popAndResolveAsPtr(context);
6,259✔
2215
                } while (ip->valueType() != ValueType::InstPtr);
6,259✔
2216

2217
                context.ip = ip->pageAddr();
2,054✔
2218
                context.pp = pop(context)->pageAddr();
2,054✔
2219
                returnFromFuncCall(context);
2,054✔
2220

2221
                if (displayed_traces > max_consecutive_traces)
2,054✔
2222
                {
2223
                    fmt::println(os, "       ...");
×
2224
                    break;
×
2225
                }
2226
            }
2,054✔
2227

2228
            if (context.pp == 0)
7✔
2229
            {
2230
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
7✔
2231
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
7✔
2232
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
7✔
2233
            }
7✔
2234

2235
            // display variables values in the current scope
2236
            fmt::println(os, "\nCurrent scope variables values:");
7✔
2237
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
8✔
2238
            {
2239
                fmt::println(
2✔
2240
                    os,
1✔
2241
                    "{} = {}",
1✔
2242
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2243
                    old_scope.atPos(i).second.toString(*this));
1✔
2244
            }
1✔
2245
        }
7✔
2246

2247
        fmt::println(
262✔
2248
            os,
131✔
2249
            "At IP: {}, PP: {}, SP: {}",
131✔
2250
            // dividing by 4 because the instructions are actually on 4 bytes
2251
            fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
131✔
2252
            fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
131✔
2253
            fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
131✔
2254
    }
131✔
2255
}
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