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

ArkScript-lang / Ark / 16859398604

10 Aug 2025 08:42AM UTC coverage: 86.849% (-0.02%) from 86.869%
16859398604

Pull #568

github

web-flow
Merge 5e848d20e into c65ea7e5b
Pull Request #568: feat(closures): captures are no longer fully qualified

83 of 104 new or added lines in 11 files covered. (79.81%)

6 existing lines in 2 files now uncovered.

7542 of 8684 relevant lines covered (86.85%)

125696.68 hits per line

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

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

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

10
#include <Ark/Utils/Files.hpp>
11
#include <Ark/Utils/Utils.hpp>
12
#include <Ark/TypeChecker.hpp>
13
#include <Ark/Compiler/Instructions.hpp>
14

15
struct mapping
16
{
17
    char* name;
18
    Ark::Value (*value)(std::vector<Ark::Value>&, Ark::VM*);
19
};
20

21
namespace Ark
22
{
23
    using namespace internal;
24

25
    namespace helper
26
    {
27
        inline Value tail(Value* a)
337✔
28
        {
337✔
29
            if (a->valueType() == ValueType::List)
337✔
30
            {
31
                if (a->constList().size() < 2)
69✔
32
                    return Value(ValueType::List);
18✔
33

34
                std::vector<Value> tmp(a->constList().size() - 1);
51✔
35
                for (std::size_t i = 1, end = a->constList().size(); i < end; ++i)
327✔
36
                    tmp[i - 1] = a->constList()[i];
276✔
37
                return Value(std::move(tmp));
51✔
38
            }
52✔
39
            if (a->valueType() == ValueType::String)
268✔
40
            {
41
                if (a->string().size() < 2)
267✔
42
                    return Value(ValueType::String);
50✔
43

44
                Value b { *a };
217✔
45
                b.stringRef().erase(b.stringRef().begin());
217✔
46
                return b;
217✔
47
            }
217✔
48

49
            throw types::TypeCheckingError(
2✔
50
                "tail",
1✔
51
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
52
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
53
                { *a });
1✔
54
        }
337✔
55

56
        inline Value head(Value* a)
1,131✔
57
        {
1,131✔
58
            if (a->valueType() == ValueType::List)
1,131✔
59
            {
60
                if (a->constList().empty())
863✔
61
                    return Builtins::nil;
×
62
                return a->constList()[0];
863✔
63
            }
64
            if (a->valueType() == ValueType::String)
268✔
65
            {
66
                if (a->string().empty())
267✔
67
                    return Value(ValueType::String);
1✔
68
                return Value(std::string(1, a->stringRef()[0]));
267✔
69
            }
70

71
            throw types::TypeCheckingError(
2✔
72
                "head",
1✔
73
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
74
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
75
                { *a });
1✔
76
        }
1,131✔
77

78
        inline Value at(Value& container, Value& index, VM& vm)
15,492✔
79
        {
15,492✔
80
            if (index.valueType() != ValueType::Number)
15,492✔
81
                throw types::TypeCheckingError(
5✔
82
                    "@",
1✔
83
                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
2✔
84
                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
1✔
85
                    { container, index });
1✔
86

87
            const auto num = static_cast<long>(index.number());
15,491✔
88

89
            if (container.valueType() == ValueType::List)
15,491✔
90
            {
91
                const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.list().size()) + num : num);
7,723✔
92
                if (i < container.list().size())
7,723✔
93
                    return container.list()[i];
7,722✔
94
                else
95
                    VM::throwVMError(
1✔
96
                        ErrorKind::Index,
97
                        fmt::format("{} out of range {} (length {})", num, container.toString(vm), container.list().size()));
1✔
98
            }
7,723✔
99
            else if (container.valueType() == ValueType::String)
7,768✔
100
            {
101
                const auto i = static_cast<std::size_t>(num < 0 ? static_cast<long>(container.string().size()) + num : num);
7,767✔
102
                if (i < container.string().size())
7,767✔
103
                    return Value(std::string(1, container.string()[i]));
7,766✔
104
                else
105
                    VM::throwVMError(
1✔
106
                        ErrorKind::Index,
107
                        fmt::format("{} out of range \"{}\" (length {})", num, container.string(), container.string().size()));
1✔
108
            }
7,767✔
109
            else
110
                throw types::TypeCheckingError(
2✔
111
                    "@",
1✔
112
                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
2✔
113
                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
1✔
114
                    { container, index });
1✔
115
        }
15,495✔
116
    }
117

118
    VM::VM(State& state) noexcept :
537✔
119
        m_state(state), m_exit_code(0), m_running(false)
179✔
120
    {
179✔
121
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
179✔
122
    }
179✔
123

124
    void VM::init() noexcept
180✔
125
    {
180✔
126
        ExecutionContext& context = *m_execution_contexts.back();
180✔
127
        for (const auto& c : m_execution_contexts)
360✔
128
        {
129
            c->ip = 0;
180✔
130
            c->pp = 0;
180✔
131
            c->sp = 0;
180✔
132
        }
180✔
133

134
        context.sp = 0;
180✔
135
        context.fc = 1;
180✔
136

137
        m_shared_lib_objects.clear();
180✔
138
        context.stacked_closure_scopes.clear();
180✔
139
        context.stacked_closure_scopes.emplace_back(nullptr);
180✔
140

141
        context.saved_scope.reset();
180✔
142
        m_exit_code = 0;
180✔
143

144
        context.locals.clear();
180✔
145
        context.locals.reserve(128);
180✔
146
        context.locals.emplace_back(context.scopes_storage.data(), 0);
180✔
147

148
        // loading bound stuff
149
        // put them in the global frame if we can, aka the first one
150
        for (const auto& [sym_id, value] : m_state.m_binded)
377✔
151
        {
152
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
186✔
153
            if (it != m_state.m_symbols.end())
186✔
154
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
11✔
155
        }
186✔
156
    }
180✔
157

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

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

205
    Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
1,535✔
206
    {
1,535✔
207
        Value l(ValueType::List);
1,535✔
208
        if (count != 0)
1,535✔
209
            l.list().reserve(count);
569✔
210

211
        for (std::size_t i = 0; i < count; ++i)
3,008✔
212
            l.push_back(*popAndResolveAsPtr(context));
1,473✔
213

214
        return l;
1,535✔
215
    }
1,535✔
216

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

230
        for (std::size_t i = 0; i < count; ++i)
2,938✔
231
            list->push_back(*popAndResolveAsPtr(context));
1,469✔
232
    }
1,470✔
233

234
    Value& VM::operator[](const std::string& name) noexcept
36✔
235
    {
36✔
236
        // find id of object
237
        const auto it = std::ranges::find(m_state.m_symbols, name);
36✔
238
        if (it == m_state.m_symbols.end())
36✔
239
        {
240
            m_no_value = Builtins::nil;
3✔
241
            return m_no_value;
3✔
242
        }
243

244
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
33✔
245
        if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
33✔
246
        {
247
            ExecutionContext& context = *m_execution_contexts.front();
33✔
248

249
            const auto id = static_cast<uint16_t>(dist);
33✔
250
            Value* var = findNearestVariable(id, context);
33✔
251
            if (var != nullptr)
33✔
252
                return *var;
33✔
253
        }
33✔
254

255
        m_no_value = Builtins::nil;
×
256
        return m_no_value;
×
257
    }
36✔
258

259
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
×
260
    {
×
261
        namespace fs = std::filesystem;
262

263
        const std::string file = m_state.m_constants[id].stringRef();
×
264

265
        std::string path = file;
×
266
        // bytecode loaded from file
267
        if (m_state.m_filename != ARK_NO_NAME_FILE)
×
268
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
×
269

270
        std::shared_ptr<SharedLibrary> lib;
×
271
        // if it exists alongside the .arkc file
272
        if (Utils::fileExists(path))
×
273
            lib = std::make_shared<SharedLibrary>(path);
×
274
        else
275
        {
276
            for (auto const& v : m_state.m_libenv)
×
277
            {
278
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
×
279

280
                // if it's already loaded don't do anything
281
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
×
282
                        return (val->path() == path || val->path() == lib_path);
×
283
                    }) != m_shared_lib_objects.end())
×
284
                    return;
×
285

286
                // check in lib_path
287
                if (Utils::fileExists(lib_path))
×
288
                {
289
                    lib = std::make_shared<SharedLibrary>(lib_path);
×
290
                    break;
×
291
                }
292
            }
×
293
        }
294

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

309
        m_shared_lib_objects.emplace_back(lib);
×
310

311
        // load the mapping from the dynamic library
312
        try
313
        {
NEW
314
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
×
315
            // load the mapping data
NEW
316
            std::size_t i = 0;
×
317
            while (map[i].name != nullptr)
×
318
            {
319
                // put it in the global frame, aka the first one
NEW
320
                auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
NEW
321
                if (it != m_state.m_symbols.end())
×
322
                    context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
×
323

324
                ++i;
×
325
            }
×
326
        }
×
327
        catch (const std::system_error& e)
328
        {
329
            throwVMError(
×
330
                ErrorKind::Module,
331
                fmt::format(
×
332
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
333
                    file, e.what()));
×
334
        }
×
335
    }
×
336

337
    void VM::exit(const int code) noexcept
×
338
    {
×
339
        m_exit_code = code;
×
340
        m_running = false;
×
341
    }
×
342

343
    ExecutionContext* VM::createAndGetContext()
15✔
344
    {
15✔
345
        const std::lock_guard lock(m_mutex);
15✔
346

347
        ExecutionContext* ctx = nullptr;
15✔
348

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

354
        if (m_execution_contexts.size() > 1)
15✔
355
        {
356
            const auto it = std::ranges::find_if(
26✔
357
                m_execution_contexts,
13✔
358
                [](const std::unique_ptr<ExecutionContext>& context) -> bool {
35✔
359
                    return !context->primary && context->isFree();
35✔
360
                });
361

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

374
        if (ctx == nullptr)
15✔
375
            ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
6✔
376

377
        assert(!ctx->primary && "The new context shouldn't be marked as primary!");
15✔
378
        assert(ctx != m_execution_contexts.front().get() && "The new context isn't really new!");
15✔
379

380
        const ExecutionContext& primary_ctx = *m_execution_contexts.front();
15✔
381
        ctx->locals.reserve(primary_ctx.locals.size());
15✔
382
        ctx->scopes_storage = primary_ctx.scopes_storage;
15✔
383
        ctx->stacked_closure_scopes.emplace_back(nullptr);
15✔
384
        ctx->fc = 1;
15✔
385

386
        for (const auto& scope_view : primary_ctx.locals)
56✔
387
        {
388
            auto& new_scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), scope_view.m_start);
41✔
389
            for (std::size_t i = 0; i < scope_view.size(); ++i)
2,017✔
390
            {
391
                const auto& [id, val] = scope_view.atPos(i);
1,976✔
392
                new_scope.push_back(id, val);
1,976✔
393
            }
1,976✔
394
        }
41✔
395

1✔
396
        return ctx;
15✔
397
    }
16✔
398

399
    void VM::deleteContext(ExecutionContext* ec)
15✔
400
    {
14✔
401
        const std::lock_guard lock(m_mutex);
14✔
402

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

429
    Future* VM::createFuture(std::vector<Value>& args)
15✔
430
    {
15✔
431
        ExecutionContext* ctx = createAndGetContext();
15✔
432
        // so that we have access to the presumed symbol id of the function we are calling
433
        // assuming that the callee is always the global context
434
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
15✔
435

436
        // doing this after having created the context
437
        // because the context uses the mutex and we don't want a deadlock
438
        const std::lock_guard lock(m_mutex);
15✔
439
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
15✔
440

441
        return m_futures.back().get();
15✔
442
    }
15✔
443

444
    void VM::deleteFuture(Future* f)
×
445
    {
×
446
        const std::lock_guard lock(m_mutex);
×
447

448
        const auto it =
×
449
            std::ranges::remove_if(
×
450
                m_futures,
×
451
                [f](const std::unique_ptr<Future>& future) {
×
452
                    return future.get() == f;
×
453
                })
454
                .begin();
×
455
        m_futures.erase(it);
×
456
    }
×
457

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

477
                    ++i;
×
478
                }
×
479
            }
×
480

481
            return true;
×
482
        }
×
483
        catch (const std::system_error&)
484
        {
485
            return false;
×
486
        }
×
487
    }
×
488

489
    void VM::throwVMError(ErrorKind kind, const std::string& message)
31✔
490
    {
31✔
491
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
31✔
492
    }
31✔
493

494
    int VM::run(const bool fail_with_exception)
180✔
495
    {
180✔
496
        init();
180✔
497
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
180✔
498
        return m_exit_code;
180✔
499
    }
500

501
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
196✔
502
    {
196✔
503
#if ARK_USE_COMPUTED_GOTOS
504
#    define TARGET(op) TARGET_##op:
505
#    define DISPATCH_GOTO()            \
506
        _Pragma("GCC diagnostic push") \
507
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
508
        _Pragma("GCC diagnostic pop")
509
#    define GOTO_HALT() goto dispatch_end
510
#else
511
#    define TARGET(op) case op:
512
#    define DISPATCH_GOTO() goto dispatch_opcode
513
#    define GOTO_HALT() break
514
#endif
515

516
#define NEXTOPARG()                                                                   \
517
    do                                                                                \
518
    {                                                                                 \
519
        inst = m_state.inst(context.pp, context.ip);                                  \
520
        padding = m_state.inst(context.pp, context.ip + 1);                           \
521
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) + \
522
                                    m_state.inst(context.pp, context.ip + 3));        \
523
        context.ip += 4;                                                              \
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 = {
196✔
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_SET_VAL,
547
                &&TARGET_POP_JUMP_IF_FALSE,
548
                &&TARGET_JUMP,
549
                &&TARGET_RET,
550
                &&TARGET_HALT,
551
                &&TARGET_PUSH_RETURN_ADDRESS,
552
                &&TARGET_CALL,
553
                &&TARGET_CAPTURE,
554
                &&TARGET_RENAME_NEXT_CAPTURE,
555
                &&TARGET_BUILTIN,
556
                &&TARGET_DEL,
557
                &&TARGET_MAKE_CLOSURE,
558
                &&TARGET_GET_FIELD,
559
                &&TARGET_PLUGIN,
560
                &&TARGET_LIST,
561
                &&TARGET_APPEND,
562
                &&TARGET_CONCAT,
563
                &&TARGET_APPEND_IN_PLACE,
564
                &&TARGET_CONCAT_IN_PLACE,
565
                &&TARGET_POP_LIST,
566
                &&TARGET_POP_LIST_IN_PLACE,
567
                &&TARGET_SET_AT_INDEX,
568
                &&TARGET_SET_AT_2_INDEX,
569
                &&TARGET_POP,
570
                &&TARGET_SHORTCIRCUIT_AND,
571
                &&TARGET_SHORTCIRCUIT_OR,
572
                &&TARGET_CREATE_SCOPE,
573
                &&TARGET_RESET_SCOPE_JUMP,
574
                &&TARGET_POP_SCOPE,
575
                &&TARGET_GET_CURRENT_PAGE_ADDR,
576
                &&TARGET_ADD,
577
                &&TARGET_SUB,
578
                &&TARGET_MUL,
579
                &&TARGET_DIV,
580
                &&TARGET_GT,
581
                &&TARGET_LT,
582
                &&TARGET_LE,
583
                &&TARGET_GE,
584
                &&TARGET_NEQ,
585
                &&TARGET_EQ,
586
                &&TARGET_LEN,
587
                &&TARGET_EMPTY,
588
                &&TARGET_TAIL,
589
                &&TARGET_HEAD,
590
                &&TARGET_ISNIL,
591
                &&TARGET_ASSERT,
592
                &&TARGET_TO_NUM,
593
                &&TARGET_TO_STR,
594
                &&TARGET_AT,
595
                &&TARGET_AT_AT,
596
                &&TARGET_MOD,
597
                &&TARGET_TYPE,
598
                &&TARGET_HASFIELD,
599
                &&TARGET_NOT,
600
                &&TARGET_LOAD_CONST_LOAD_CONST,
601
                &&TARGET_LOAD_CONST_STORE,
602
                &&TARGET_LOAD_CONST_SET_VAL,
603
                &&TARGET_STORE_FROM,
604
                &&TARGET_STORE_FROM_INDEX,
605
                &&TARGET_SET_VAL_FROM,
606
                &&TARGET_SET_VAL_FROM_INDEX,
607
                &&TARGET_INCREMENT,
608
                &&TARGET_INCREMENT_BY_INDEX,
609
                &&TARGET_INCREMENT_STORE,
610
                &&TARGET_DECREMENT,
611
                &&TARGET_DECREMENT_BY_INDEX,
612
                &&TARGET_DECREMENT_STORE,
613
                &&TARGET_STORE_TAIL,
614
                &&TARGET_STORE_TAIL_BY_INDEX,
615
                &&TARGET_STORE_HEAD,
616
                &&TARGET_STORE_HEAD_BY_INDEX,
617
                &&TARGET_STORE_LIST,
618
                &&TARGET_SET_VAL_TAIL,
619
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
620
                &&TARGET_SET_VAL_HEAD,
621
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
622
                &&TARGET_CALL_BUILTIN,
623
                &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
624
                &&TARGET_LT_CONST_JUMP_IF_FALSE,
625
                &&TARGET_LT_CONST_JUMP_IF_TRUE,
626
                &&TARGET_LT_SYM_JUMP_IF_FALSE,
627
                &&TARGET_GT_CONST_JUMP_IF_TRUE,
628
                &&TARGET_GT_CONST_JUMP_IF_FALSE,
629
                &&TARGET_GT_SYM_JUMP_IF_FALSE,
630
                &&TARGET_EQ_CONST_JUMP_IF_TRUE,
631
                &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
632
                &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
633
                &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
634
                &&TARGET_CALL_SYMBOL,
635
                &&TARGET_CALL_CURRENT_PAGE,
636
                &&TARGET_GET_FIELD_FROM_SYMBOL,
637
                &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
638
                &&TARGET_AT_SYM_SYM,
639
                &&TARGET_AT_SYM_INDEX_SYM_INDEX,
640
                &&TARGET_CHECK_TYPE_OF,
641
                &&TARGET_CHECK_TYPE_OF_BY_INDEX,
642
                &&TARGET_APPEND_IN_PLACE_SYM,
643
                &&TARGET_APPEND_IN_PLACE_SYM_INDEX
644
            };
645

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

650
        try
651
        {
652
            uint8_t inst = 0;
196✔
653
            uint8_t padding = 0;
196✔
654
            uint16_t arg = 0;
196✔
655
            uint16_t primary_arg = 0;
196✔
656
            uint16_t secondary_arg = 0;
196✔
657

658
            m_running = true;
196✔
659

660
            DISPATCH();
196✔
661
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
662
            {
663
#if !ARK_USE_COMPUTED_GOTOS
664
            dispatch_opcode:
665
                switch (inst)
666
#endif
667
                {
×
668
#pragma region "Instructions"
669
                    TARGET(NOP)
670
                    {
671
                        DISPATCH();
×
672
                    }
146,451✔
673

674
                    TARGET(LOAD_SYMBOL)
675
                    {
676
                        push(loadSymbol(arg, context), context);
146,451✔
677
                        DISPATCH();
146,451✔
678
                    }
333,618✔
679

680
                    TARGET(LOAD_SYMBOL_BY_INDEX)
681
                    {
682
                        push(loadSymbolFromIndex(arg, context), context);
333,618✔
683
                        DISPATCH();
333,618✔
684
                    }
115,917✔
685

686
                    TARGET(LOAD_CONST)
687
                    {
688
                        push(loadConstAsPtr(arg), context);
115,917✔
689
                        DISPATCH();
115,917✔
690
                    }
28,550✔
691

692
                    TARGET(POP_JUMP_IF_TRUE)
693
                    {
694
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
35,505✔
695
                            context.ip = arg * 4;  // instructions are 4 bytes
6,955✔
696
                        DISPATCH();
28,550✔
697
                    }
401,126✔
698

699
                    TARGET(STORE)
700
                    {
701
                        store(arg, popAndResolveAsPtr(context), context);
401,126✔
702
                        DISPATCH();
401,126✔
703
                    }
22,498✔
704

705
                    TARGET(SET_VAL)
706
                    {
707
                        setVal(arg, popAndResolveAsPtr(context), context);
22,498✔
708
                        DISPATCH();
22,498✔
709
                    }
27,447✔
710

711
                    TARGET(POP_JUMP_IF_FALSE)
712
                    {
713
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
29,533✔
714
                            context.ip = arg * 4;  // instructions are 4 bytes
2,086✔
715
                        DISPATCH();
27,447✔
716
                    }
206,762✔
717

718
                    TARGET(JUMP)
719
                    {
720
                        context.ip = arg * 4;  // instructions are 4 bytes
206,762✔
721
                        DISPATCH();
206,762✔
722
                    }
136,493✔
723

724
                    TARGET(RET)
725
                    {
726
                        {
727
                            Value ip_or_val = *popAndResolveAsPtr(context);
136,493✔
728
                            // no return value on the stack
729
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
136,493✔
730
                            {
731
                                context.ip = ip_or_val.pageAddr();
2,858✔
732
                                // we always push PP then IP, thus the next value
733
                                // MUST be the page pointer
734
                                context.pp = pop(context)->pageAddr();
2,858✔
735

736
                                returnFromFuncCall(context);
2,858✔
737
                                push(Builtins::nil, context);
2,858✔
738
                            }
2,858✔
739
                            // value on the stack
740
                            else [[likely]]
741
                            {
742
                                const Value* ip = popAndResolveAsPtr(context);
133,635✔
743
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
133,635✔
744
                                context.ip = ip->pageAddr();
133,635✔
745
                                context.pp = pop(context)->pageAddr();
133,635✔
746

747
                                returnFromFuncCall(context);
133,635✔
748
                                push(std::move(ip_or_val), context);
133,635✔
749
                            }
750

751
                            if (context.fc <= untilFrameCount)
136,493✔
752
                                GOTO_HALT();
16✔
753
                        }
136,493✔
754

755
                        DISPATCH();
136,477✔
756
                    }
63✔
757

758
                    TARGET(HALT)
759
                    {
760
                        m_running = false;
63✔
761
                        GOTO_HALT();
63✔
762
                    }
139,349✔
763

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

772
                    TARGET(CALL)
773
                    {
774
                        call(context, arg);
2,595✔
775
                        if (!m_running)
2,588✔
776
                            GOTO_HALT();
×
777
                        DISPATCH();
2,588✔
778
                    }
3,100✔
779

780
                    TARGET(CAPTURE)
781
                    {
782
                        if (!context.saved_scope)
3,100✔
783
                            context.saved_scope = ClosureScope();
619✔
784

785
                        const Value* ptr = findNearestVariable(arg, context);
3,100✔
786
                        if (!ptr)
3,100✔
NEW
787
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
788
                        else
789
                        {
790
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,100✔
791
                            uint16_t id = context.capture_rename_id.value_or(arg);
3,100✔
792
                            context.saved_scope.value().push_back(id, *ptr);
3,100✔
793
                            context.capture_rename_id.reset();
3,100✔
794
                        }
795

796
                        DISPATCH();
3,100✔
797
                    }
12✔
798

799
                    TARGET(RENAME_NEXT_CAPTURE)
800
                    {
801
                        context.capture_rename_id = arg;
12✔
802
                        DISPATCH();
12✔
803
                    }
1,530✔
804

805
                    TARGET(BUILTIN)
806
                    {
807
                        push(Builtins::builtins[arg].second, context);
1,530✔
808
                        DISPATCH();
1,530✔
809
                    }
1✔
810

811
                    TARGET(DEL)
812
                    {
813
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
814
                        {
815
                            if (var->valueType() == ValueType::User)
×
816
                                var->usertypeRef().del();
×
817
                            *var = Value();
×
818
                            DISPATCH();
×
819
                        }
820

821
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
822
                    }
619✔
823

824
                    TARGET(MAKE_CLOSURE)
825
                    {
826
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
619✔
827
                        context.saved_scope.reset();
619✔
828
                        DISPATCH();
619✔
829
                    }
6✔
830

831
                    TARGET(GET_FIELD)
832
                    {
833
                        Value* var = popAndResolveAsPtr(context);
6✔
834
                        push(getField(var, arg, context), context);
6✔
835
                        DISPATCH();
6✔
836
                    }
×
837

838
                    TARGET(PLUGIN)
839
                    {
840
                        loadPlugin(arg, context);
×
841
                        DISPATCH();
×
842
                    }
632✔
843

844
                    TARGET(LIST)
845
                    {
846
                        {
847
                            Value l = createList(arg, context);
632✔
848
                            push(std::move(l), context);
632✔
849
                        }
632✔
850
                        DISPATCH();
632✔
851
                    }
1,552✔
852

853
                    TARGET(APPEND)
854
                    {
855
                        {
856
                            Value* list = popAndResolveAsPtr(context);
1,552✔
857
                            if (list->valueType() != ValueType::List)
1,552✔
858
                            {
859
                                std::vector<Value> args = { *list };
1✔
860
                                for (uint16_t i = 0; i < arg; ++i)
2✔
861
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
862
                                throw types::TypeCheckingError(
2✔
863
                                    "append",
1✔
864
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
1✔
865
                                    args);
866
                            }
1✔
867

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

870
                            Value obj { *list };
1,551✔
871
                            obj.list().reserve(size + arg);
1,551✔
872

873
                            for (uint16_t i = 0; i < arg; ++i)
3,102✔
874
                                obj.push_back(*popAndResolveAsPtr(context));
1,551✔
875
                            push(std::move(obj), context);
1,551✔
876
                        }
1,551✔
877
                        DISPATCH();
1,551✔
878
                    }
12✔
879

880
                    TARGET(CONCAT)
881
                    {
882
                        {
883
                            Value* list = popAndResolveAsPtr(context);
12✔
884
                            Value obj { *list };
12✔
885

886
                            for (uint16_t i = 0; i < arg; ++i)
24✔
887
                            {
888
                                Value* next = popAndResolveAsPtr(context);
14✔
889

890
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
14✔
891
                                    throw types::TypeCheckingError(
4✔
892
                                        "concat",
2✔
893
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
894
                                        { *list, *next });
2✔
895

896
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
12✔
897
                            }
12✔
898
                            push(std::move(obj), context);
10✔
899
                        }
12✔
900
                        DISPATCH();
10✔
901
                    }
×
902

903
                    TARGET(APPEND_IN_PLACE)
904
                    {
905
                        Value* list = popAndResolveAsPtr(context);
×
906
                        listAppendInPlace(list, arg, context);
×
907
                        DISPATCH();
×
908
                    }
562✔
909

910
                    TARGET(CONCAT_IN_PLACE)
911
                    {
912
                        Value* list = popAndResolveAsPtr(context);
562✔
913

914
                        for (uint16_t i = 0; i < arg; ++i)
1,152✔
915
                        {
916
                            Value* next = popAndResolveAsPtr(context);
592✔
917

918
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
592✔
919
                                throw types::TypeCheckingError(
4✔
920
                                    "concat!",
2✔
921
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
922
                                    { *list, *next });
2✔
923

924
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
590✔
925
                        }
590✔
926
                        DISPATCH();
560✔
927
                    }
6✔
928

929
                    TARGET(POP_LIST)
930
                    {
931
                        {
932
                            Value list = *popAndResolveAsPtr(context);
6✔
933
                            Value number = *popAndResolveAsPtr(context);
6✔
934

935
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
6✔
936
                                throw types::TypeCheckingError(
2✔
937
                                    "pop",
1✔
938
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
939
                                    { list, number });
1✔
940

941
                            long idx = static_cast<long>(number.number());
5✔
942
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
5✔
943
                            if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
5✔
944
                                throwVMError(
2✔
945
                                    ErrorKind::Index,
946
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
947

948
                            list.list().erase(list.list().begin() + idx);
3✔
949
                            push(list, context);
3✔
950
                        }
6✔
951
                        DISPATCH();
3✔
952
                    }
83✔
953

954
                    TARGET(POP_LIST_IN_PLACE)
955
                    {
956
                        {
957
                            Value* list = popAndResolveAsPtr(context);
83✔
958
                            Value number = *popAndResolveAsPtr(context);
83✔
959

960
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
83✔
961
                                throw types::TypeCheckingError(
2✔
962
                                    "pop!",
1✔
963
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
964
                                    { *list, number });
1✔
965

966
                            long idx = static_cast<long>(number.number());
82✔
967
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
82✔
968
                            if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
82✔
969
                                throwVMError(
2✔
970
                                    ErrorKind::Index,
971
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
972

973
                            list->list().erase(list->list().begin() + idx);
80✔
974
                        }
83✔
975
                        DISPATCH();
80✔
976
                    }
490✔
977

978
                    TARGET(SET_AT_INDEX)
979
                    {
980
                        {
981
                            Value* list = popAndResolveAsPtr(context);
490✔
982
                            Value number = *popAndResolveAsPtr(context);
490✔
983
                            Value new_value = *popAndResolveAsPtr(context);
490✔
984

985
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
490✔
986
                                throw types::TypeCheckingError(
2✔
987
                                    "@=",
1✔
988
                                    { { types::Contract {
3✔
989
                                          { types::Typedef("list", ValueType::List),
3✔
990
                                            types::Typedef("index", ValueType::Number),
1✔
991
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
992
                                      { types::Contract {
1✔
993
                                          { types::Typedef("string", ValueType::String),
3✔
994
                                            types::Typedef("index", ValueType::Number),
1✔
995
                                            types::Typedef("char", ValueType::String) } } } },
1✔
996
                                    { *list, number, new_value });
1✔
997

998
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
489✔
999
                            long idx = static_cast<long>(number.number());
489✔
1000
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
489✔
1001
                            if (std::cmp_greater_equal(idx, size) || idx < 0)
489✔
1002
                                throwVMError(
2✔
1003
                                    ErrorKind::Index,
1004
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
1005

1006
                            if (list->valueType() == ValueType::List)
487✔
1007
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
1008
                            else
1009
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
1010
                        }
490✔
1011
                        DISPATCH();
487✔
1012
                    }
12✔
1013

1014
                    TARGET(SET_AT_2_INDEX)
1015
                    {
1016
                        {
1017
                            Value* list = popAndResolveAsPtr(context);
12✔
1018
                            Value x = *popAndResolveAsPtr(context);
12✔
1019
                            Value y = *popAndResolveAsPtr(context);
12✔
1020
                            Value new_value = *popAndResolveAsPtr(context);
12✔
1021

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

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

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

1055
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
1056
                            const std::size_t size =
8✔
1057
                                is_list
16✔
1058
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
1059
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1060

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

1068
                            if (is_list)
6✔
1069
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1070
                            else
1071
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1072
                        }
12✔
1073
                        DISPATCH();
6✔
1074
                    }
2,702✔
1075

1076
                    TARGET(POP)
1077
                    {
1078
                        pop(context);
2,702✔
1079
                        DISPATCH();
2,702✔
1080
                    }
23,415✔
1081

1082
                    TARGET(SHORTCIRCUIT_AND)
1083
                    {
1084
                        if (!*peekAndResolveAsPtr(context))
23,415✔
1085
                            context.ip = arg * 4;
733✔
1086
                        else
1087
                            pop(context);
22,682✔
1088
                        DISPATCH();
23,415✔
1089
                    }
811✔
1090

1091
                    TARGET(SHORTCIRCUIT_OR)
1092
                    {
1093
                        if (!!*peekAndResolveAsPtr(context))
811✔
1094
                            context.ip = arg * 4;
216✔
1095
                        else
1096
                            pop(context);
595✔
1097
                        DISPATCH();
811✔
1098
                    }
2,752✔
1099

1100
                    TARGET(CREATE_SCOPE)
1101
                    {
1102
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
2,752✔
1103
                        DISPATCH();
2,752✔
1104
                    }
31,125✔
1105

1106
                    TARGET(RESET_SCOPE_JUMP)
1107
                    {
1108
                        context.locals.back().reset();
31,125✔
1109
                        context.ip = arg * 4;  // instructions are 4 bytes
31,125✔
1110
                        DISPATCH();
31,125✔
1111
                    }
2,752✔
1112

1113
                    TARGET(POP_SCOPE)
1114
                    {
1115
                        context.locals.pop_back();
2,752✔
1116
                        DISPATCH();
2,752✔
1117
                    }
×
1118

1119
                    TARGET(GET_CURRENT_PAGE_ADDR)
1120
                    {
1121
                        context.last_symbol = arg;
×
1122
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1123
                        DISPATCH();
×
1124
                    }
26,904✔
1125

1126
#pragma endregion
1127

1128
#pragma region "Operators"
1129

1130
                    TARGET(ADD)
1131
                    {
1132
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
26,904✔
1133

1134
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
26,904✔
1135
                            push(Value(a->number() + b->number()), context);
19,459✔
1136
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
7,445✔
1137
                            push(Value(a->string() + b->string()), context);
7,444✔
1138
                        else
1139
                            throw types::TypeCheckingError(
2✔
1140
                                "+",
1✔
1141
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1142
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1143
                                { *a, *b });
1✔
1144
                        DISPATCH();
26,903✔
1145
                    }
328✔
1146

1147
                    TARGET(SUB)
1148
                    {
1149
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
328✔
1150

1151
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
328✔
1152
                            throw types::TypeCheckingError(
2✔
1153
                                "-",
1✔
1154
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1155
                                { *a, *b });
1✔
1156
                        push(Value(a->number() - b->number()), context);
327✔
1157
                        DISPATCH();
327✔
1158
                    }
2,213✔
1159

1160
                    TARGET(MUL)
1161
                    {
1162
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,213✔
1163

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

1173
                    TARGET(DIV)
1174
                    {
1175
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,062✔
1176

1177
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,062✔
1178
                            throw types::TypeCheckingError(
2✔
1179
                                "/",
1✔
1180
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1181
                                { *a, *b });
1✔
1182
                        auto d = b->number();
1,061✔
1183
                        if (d == 0)
1,061✔
1184
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1185

1186
                        push(Value(a->number() / d), context);
1,060✔
1187
                        DISPATCH();
1,060✔
1188
                    }
160✔
1189

1190
                    TARGET(GT)
1191
                    {
1192
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
160✔
1193
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
160✔
1194
                        DISPATCH();
160✔
1195
                    }
28,820✔
1196

1197
                    TARGET(LT)
1198
                    {
1199
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,820✔
1200
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
28,820✔
1201
                        DISPATCH();
28,820✔
1202
                    }
7,192✔
1203

1204
                    TARGET(LE)
1205
                    {
1206
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,192✔
1207
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,192✔
1208
                        DISPATCH();
7,192✔
1209
                    }
5,730✔
1210

1211
                    TARGET(GE)
1212
                    {
1213
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,730✔
1214
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,730✔
1215
                        DISPATCH();
5,730✔
1216
                    }
748✔
1217

1218
                    TARGET(NEQ)
1219
                    {
1220
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
748✔
1221
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
748✔
1222
                        DISPATCH();
748✔
1223
                    }
17,449✔
1224

1225
                    TARGET(EQ)
1226
                    {
1227
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17,449✔
1228
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
17,449✔
1229
                        DISPATCH();
17,449✔
1230
                    }
12,199✔
1231

1232
                    TARGET(LEN)
1233
                    {
1234
                        const Value* a = popAndResolveAsPtr(context);
12,199✔
1235

1236
                        if (a->valueType() == ValueType::List)
12,199✔
1237
                            push(Value(static_cast<int>(a->constList().size())), context);
4,573✔
1238
                        else if (a->valueType() == ValueType::String)
7,626✔
1239
                            push(Value(static_cast<int>(a->string().size())), context);
7,625✔
1240
                        else
1241
                            throw types::TypeCheckingError(
2✔
1242
                                "len",
1✔
1243
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1244
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1245
                                { *a });
1✔
1246
                        DISPATCH();
12,198✔
1247
                    }
540✔
1248

1249
                    TARGET(EMPTY)
1250
                    {
1251
                        const Value* a = popAndResolveAsPtr(context);
540✔
1252

1253
                        if (a->valueType() == ValueType::List)
540✔
1254
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
86✔
1255
                        else if (a->valueType() == ValueType::String)
454✔
1256
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
453✔
1257
                        else
1258
                            throw types::TypeCheckingError(
2✔
1259
                                "empty?",
1✔
1260
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1261
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1262
                                { *a });
1✔
1263
                        DISPATCH();
539✔
1264
                    }
335✔
1265

1266
                    TARGET(TAIL)
1267
                    {
1268
                        Value* const a = popAndResolveAsPtr(context);
335✔
1269
                        push(helper::tail(a), context);
336✔
1270
                        DISPATCH();
334✔
1271
                    }
1,099✔
1272

1273
                    TARGET(HEAD)
1274
                    {
1275
                        Value* const a = popAndResolveAsPtr(context);
1,099✔
1276
                        push(helper::head(a), context);
1,100✔
1277
                        DISPATCH();
1,098✔
1278
                    }
2,322✔
1279

1280
                    TARGET(ISNIL)
1281
                    {
1282
                        const Value* a = popAndResolveAsPtr(context);
2,322✔
1283
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,322✔
1284
                        DISPATCH();
2,322✔
1285
                    }
596✔
1286

1287
                    TARGET(ASSERT)
1288
                    {
1289
                        Value* const b = popAndResolveAsPtr(context);
596✔
1290
                        Value* const a = popAndResolveAsPtr(context);
596✔
1291

1292
                        if (b->valueType() != ValueType::String)
596✔
1293
                            throw types::TypeCheckingError(
2✔
1294
                                "assert",
1✔
1295
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1296
                                { *a, *b });
1✔
1297

1298
                        if (*a == Builtins::falseSym)
595✔
1299
                            throw AssertionFailed(b->stringRef());
×
1300
                        DISPATCH();
595✔
1301
                    }
15✔
1302

1303
                    TARGET(TO_NUM)
1304
                    {
1305
                        const Value* a = popAndResolveAsPtr(context);
15✔
1306

1307
                        if (a->valueType() != ValueType::String)
15✔
1308
                            throw types::TypeCheckingError(
2✔
1309
                                "toNumber",
1✔
1310
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1311
                                { *a });
1✔
1312

1313
                        double val;
1314
                        if (Utils::isDouble(a->string(), &val))
14✔
1315
                            push(Value(val), context);
11✔
1316
                        else
1317
                            push(Builtins::nil, context);
3✔
1318
                        DISPATCH();
14✔
1319
                    }
120✔
1320

1321
                    TARGET(TO_STR)
1322
                    {
1323
                        const Value* a = popAndResolveAsPtr(context);
120✔
1324
                        push(Value(a->toString(*this)), context);
120✔
1325
                        DISPATCH();
120✔
1326
                    }
1,044✔
1327

1328
                    TARGET(AT)
1329
                    {
1330
                        Value& b = *popAndResolveAsPtr(context);
1,044✔
1331
                        Value& a = *popAndResolveAsPtr(context);
1,044✔
1332
                        push(helper::at(a, b, *this), context);
1,048✔
1333
                        DISPATCH();
1,040✔
1334
                    }
26✔
1335

1336
                    TARGET(AT_AT)
1337
                    {
1338
                        {
1339
                            const Value* x = popAndResolveAsPtr(context);
26✔
1340
                            const Value* y = popAndResolveAsPtr(context);
26✔
1341
                            Value& list = *popAndResolveAsPtr(context);
26✔
1342

1343
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
26✔
1344
                                list.valueType() != ValueType::List)
25✔
1345
                                throw types::TypeCheckingError(
2✔
1346
                                    "@@",
1✔
1347
                                    { { types::Contract {
2✔
1348
                                        { types::Typedef("src", ValueType::List),
3✔
1349
                                          types::Typedef("y", ValueType::Number),
1✔
1350
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1351
                                    { list, *y, *x });
1✔
1352

1353
                            long idx_y = static_cast<long>(y->number());
25✔
1354
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
25✔
1355
                            if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
25✔
1356
                                throwVMError(
2✔
1357
                                    ErrorKind::Index,
1358
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
2✔
1359

1360
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
23✔
1361
                            const std::size_t size =
23✔
1362
                                is_list
46✔
1363
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
16✔
1364
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1365

1366
                            long idx_x = static_cast<long>(x->number());
23✔
1367
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
23✔
1368
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
23✔
1369
                                throwVMError(
2✔
1370
                                    ErrorKind::Index,
1371
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1372

1373
                            if (is_list)
21✔
1374
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
14✔
1375
                            else
1376
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1377
                        }
1378
                        DISPATCH();
21✔
1379
                    }
16,354✔
1380

1381
                    TARGET(MOD)
1382
                    {
1383
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
16,354✔
1384
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
16,354✔
1385
                            throw types::TypeCheckingError(
2✔
1386
                                "mod",
1✔
1387
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1388
                                { *a, *b });
1✔
1389
                        push(Value(std::fmod(a->number(), b->number())), context);
16,353✔
1390
                        DISPATCH();
16,353✔
1391
                    }
16✔
1392

1393
                    TARGET(TYPE)
1394
                    {
1395
                        const Value* a = popAndResolveAsPtr(context);
16✔
1396
                        push(Value(std::to_string(a->valueType())), context);
16✔
1397
                        DISPATCH();
16✔
1398
                    }
3✔
1399

1400
                    TARGET(HASFIELD)
1401
                    {
1402
                        {
1403
                            Value* const field = popAndResolveAsPtr(context);
3✔
1404
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1405
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1406
                                throw types::TypeCheckingError(
2✔
1407
                                    "hasField",
1✔
1408
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1409
                                    { *closure, *field });
1✔
1410

1411
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1412
                            if (it == m_state.m_symbols.end())
2✔
1413
                            {
1414
                                push(Builtins::falseSym, context);
1✔
1415
                                DISPATCH();
1✔
1416
                            }
1417

1418
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1419
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1420
                        }
1421
                        DISPATCH();
1✔
1422
                    }
3,568✔
1423

1424
                    TARGET(NOT)
1425
                    {
1426
                        const Value* a = popAndResolveAsPtr(context);
3,568✔
1427
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,568✔
1428
                        DISPATCH();
3,568✔
1429
                    }
5,995✔
1430

1431
#pragma endregion
1432

1433
#pragma region "Super Instructions"
1434
                    TARGET(LOAD_CONST_LOAD_CONST)
1435
                    {
1436
                        UNPACK_ARGS();
5,995✔
1437
                        push(loadConstAsPtr(primary_arg), context);
5,995✔
1438
                        push(loadConstAsPtr(secondary_arg), context);
5,995✔
1439
                        DISPATCH();
5,995✔
1440
                    }
8,007✔
1441

1442
                    TARGET(LOAD_CONST_STORE)
1443
                    {
1444
                        UNPACK_ARGS();
8,007✔
1445
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
8,007✔
1446
                        DISPATCH();
8,007✔
1447
                    }
240✔
1448

1449
                    TARGET(LOAD_CONST_SET_VAL)
1450
                    {
1451
                        UNPACK_ARGS();
240✔
1452
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
240✔
1453
                        DISPATCH();
239✔
1454
                    }
20✔
1455

1456
                    TARGET(STORE_FROM)
1457
                    {
1458
                        UNPACK_ARGS();
20✔
1459
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
20✔
1460
                        DISPATCH();
19✔
1461
                    }
1,096✔
1462

1463
                    TARGET(STORE_FROM_INDEX)
1464
                    {
1465
                        UNPACK_ARGS();
1,096✔
1466
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,096✔
1467
                        DISPATCH();
1,096✔
1468
                    }
547✔
1469

1470
                    TARGET(SET_VAL_FROM)
1471
                    {
1472
                        UNPACK_ARGS();
547✔
1473
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
547✔
1474
                        DISPATCH();
547✔
1475
                    }
221✔
1476

1477
                    TARGET(SET_VAL_FROM_INDEX)
1478
                    {
1479
                        UNPACK_ARGS();
221✔
1480
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
221✔
1481
                        DISPATCH();
221✔
1482
                    }
16✔
1483

1484
                    TARGET(INCREMENT)
1485
                    {
1486
                        UNPACK_ARGS();
16✔
1487
                        {
1488
                            Value* var = loadSymbol(primary_arg, context);
16✔
1489

1490
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1491
                            if (var->valueType() == ValueType::Reference)
16✔
1492
                                var = var->reference();
×
1493

1494
                            if (var->valueType() == ValueType::Number)
16✔
1495
                                push(Value(var->number() + secondary_arg), context);
16✔
1496
                            else
1497
                                throw types::TypeCheckingError(
×
1498
                                    "+",
×
1499
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1500
                                    { *var, Value(secondary_arg) });
×
1501
                        }
1502
                        DISPATCH();
16✔
1503
                    }
87,973✔
1504

1505
                    TARGET(INCREMENT_BY_INDEX)
1506
                    {
1507
                        UNPACK_ARGS();
87,973✔
1508
                        {
1509
                            Value* var = loadSymbolFromIndex(primary_arg, context);
87,973✔
1510

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

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

1526
                    TARGET(INCREMENT_STORE)
1527
                    {
1528
                        UNPACK_ARGS();
31,112✔
1529
                        {
1530
                            Value* var = loadSymbol(primary_arg, context);
31,112✔
1531

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

1536
                            if (var->valueType() == ValueType::Number)
31,112✔
1537
                            {
1538
                                Value val = Value(var->number() + secondary_arg);
31,112✔
1539
                                setVal(primary_arg, &val, context);
31,112✔
1540
                            }
31,112✔
1541
                            else
1542
                                throw types::TypeCheckingError(
×
1543
                                    "+",
×
1544
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1545
                                    { *var, Value(secondary_arg) });
×
1546
                        }
1547
                        DISPATCH();
31,112✔
1548
                    }
1,776✔
1549

1550
                    TARGET(DECREMENT)
1551
                    {
1552
                        UNPACK_ARGS();
1,776✔
1553
                        {
1554
                            Value* var = loadSymbol(primary_arg, context);
1,776✔
1555

1556
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1557
                            if (var->valueType() == ValueType::Reference)
1,776✔
1558
                                var = var->reference();
×
1559

1560
                            if (var->valueType() == ValueType::Number)
1,776✔
1561
                                push(Value(var->number() - secondary_arg), context);
1,776✔
1562
                            else
1563
                                throw types::TypeCheckingError(
×
1564
                                    "-",
×
1565
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1566
                                    { *var, Value(secondary_arg) });
×
1567
                        }
1568
                        DISPATCH();
1,776✔
1569
                    }
194,408✔
1570

1571
                    TARGET(DECREMENT_BY_INDEX)
1572
                    {
1573
                        UNPACK_ARGS();
194,408✔
1574
                        {
1575
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,408✔
1576

1577
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1578
                            if (var->valueType() == ValueType::Reference)
194,408✔
1579
                                var = var->reference();
×
1580

1581
                            if (var->valueType() == ValueType::Number)
194,408✔
1582
                                push(Value(var->number() - secondary_arg), context);
194,407✔
1583
                            else
1584
                                throw types::TypeCheckingError(
2✔
1585
                                    "-",
1✔
1586
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1587
                                    { *var, Value(secondary_arg) });
1✔
1588
                        }
1589
                        DISPATCH();
194,407✔
1590
                    }
×
1591

1592
                    TARGET(DECREMENT_STORE)
1593
                    {
1594
                        UNPACK_ARGS();
×
1595
                        {
1596
                            Value* var = loadSymbol(primary_arg, context);
×
1597

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

1602
                            if (var->valueType() == ValueType::Number)
×
1603
                            {
1604
                                Value val = Value(var->number() - secondary_arg);
×
1605
                                setVal(primary_arg, &val, context);
×
1606
                            }
×
1607
                            else
1608
                                throw types::TypeCheckingError(
×
1609
                                    "-",
×
1610
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1611
                                    { *var, Value(secondary_arg) });
×
1612
                        }
1613
                        DISPATCH();
×
1614
                    }
×
1615

1616
                    TARGET(STORE_TAIL)
1617
                    {
1618
                        UNPACK_ARGS();
×
1619
                        {
1620
                            Value* list = loadSymbol(primary_arg, context);
×
1621
                            Value tail = helper::tail(list);
×
1622
                            store(secondary_arg, &tail, context);
×
1623
                        }
×
1624
                        DISPATCH();
×
1625
                    }
1✔
1626

1627
                    TARGET(STORE_TAIL_BY_INDEX)
1628
                    {
1629
                        UNPACK_ARGS();
1✔
1630
                        {
1631
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1632
                            Value tail = helper::tail(list);
1✔
1633
                            store(secondary_arg, &tail, context);
1✔
1634
                        }
1✔
1635
                        DISPATCH();
1✔
1636
                    }
×
1637

1638
                    TARGET(STORE_HEAD)
1639
                    {
1640
                        UNPACK_ARGS();
×
1641
                        {
1642
                            Value* list = loadSymbol(primary_arg, context);
×
1643
                            Value head = helper::head(list);
×
1644
                            store(secondary_arg, &head, context);
×
1645
                        }
×
1646
                        DISPATCH();
×
1647
                    }
31✔
1648

1649
                    TARGET(STORE_HEAD_BY_INDEX)
1650
                    {
1651
                        UNPACK_ARGS();
31✔
1652
                        {
1653
                            Value* list = loadSymbolFromIndex(primary_arg, context);
31✔
1654
                            Value head = helper::head(list);
31✔
1655
                            store(secondary_arg, &head, context);
31✔
1656
                        }
31✔
1657
                        DISPATCH();
31✔
1658
                    }
903✔
1659

1660
                    TARGET(STORE_LIST)
1661
                    {
1662
                        UNPACK_ARGS();
903✔
1663
                        {
1664
                            Value l = createList(primary_arg, context);
903✔
1665
                            store(secondary_arg, &l, context);
903✔
1666
                        }
903✔
1667
                        DISPATCH();
903✔
1668
                    }
×
1669

1670
                    TARGET(SET_VAL_TAIL)
1671
                    {
1672
                        UNPACK_ARGS();
×
1673
                        {
1674
                            Value* list = loadSymbol(primary_arg, context);
×
1675
                            Value tail = helper::tail(list);
×
1676
                            setVal(secondary_arg, &tail, context);
×
1677
                        }
×
1678
                        DISPATCH();
×
1679
                    }
1✔
1680

1681
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1682
                    {
1683
                        UNPACK_ARGS();
1✔
1684
                        {
1685
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1686
                            Value tail = helper::tail(list);
1✔
1687
                            setVal(secondary_arg, &tail, context);
1✔
1688
                        }
1✔
1689
                        DISPATCH();
1✔
1690
                    }
×
1691

1692
                    TARGET(SET_VAL_HEAD)
1693
                    {
1694
                        UNPACK_ARGS();
×
1695
                        {
1696
                            Value* list = loadSymbol(primary_arg, context);
×
1697
                            Value head = helper::head(list);
×
1698
                            setVal(secondary_arg, &head, context);
×
1699
                        }
×
1700
                        DISPATCH();
×
1701
                    }
1✔
1702

1703
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1704
                    {
1705
                        UNPACK_ARGS();
1✔
1706
                        {
1707
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1708
                            Value head = helper::head(list);
1✔
1709
                            setVal(secondary_arg, &head, context);
1✔
1710
                        }
1✔
1711
                        DISPATCH();
1✔
1712
                    }
807✔
1713

1714
                    TARGET(CALL_BUILTIN)
1715
                    {
1716
                        UNPACK_ARGS();
807✔
1717
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1718
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
807✔
1719
                        if (!m_running)
749✔
1720
                            GOTO_HALT();
×
1721
                        DISPATCH();
749✔
1722
                    }
11,203✔
1723

1724
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1725
                    {
1726
                        UNPACK_ARGS();
11,203✔
1727
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1728
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,203✔
1729
                        if (!m_running)
11,202✔
1730
                            GOTO_HALT();
×
1731
                        DISPATCH();
11,202✔
1732
                    }
857✔
1733

1734
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1735
                    {
1736
                        UNPACK_ARGS();
857✔
1737
                        const Value* sym = popAndResolveAsPtr(context);
857✔
1738
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
857✔
1739
                            context.ip = secondary_arg * 4;
122✔
1740
                        DISPATCH();
857✔
1741
                    }
21,902✔
1742

1743
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1744
                    {
1745
                        UNPACK_ARGS();
21,902✔
1746
                        const Value* sym = popAndResolveAsPtr(context);
21,902✔
1747
                        if (*sym < *loadConstAsPtr(primary_arg))
21,902✔
1748
                            context.ip = secondary_arg * 4;
10,950✔
1749
                        DISPATCH();
21,902✔
1750
                    }
5,543✔
1751

1752
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1753
                    {
1754
                        UNPACK_ARGS();
5,543✔
1755
                        const Value* sym = popAndResolveAsPtr(context);
5,543✔
1756
                        if (!(*sym < *loadSymbol(primary_arg, context)))
5,543✔
1757
                            context.ip = secondary_arg * 4;
536✔
1758
                        DISPATCH();
5,543✔
1759
                    }
172,500✔
1760

1761
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1762
                    {
1763
                        UNPACK_ARGS();
172,500✔
1764
                        const Value* sym = popAndResolveAsPtr(context);
172,500✔
1765
                        const Value* cst = loadConstAsPtr(primary_arg);
172,500✔
1766
                        if (*cst < *sym)
172,500✔
1767
                            context.ip = secondary_arg * 4;
86,586✔
1768
                        DISPATCH();
172,500✔
1769
                    }
15✔
1770

1771
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1772
                    {
1773
                        UNPACK_ARGS();
15✔
1774
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1775
                        const Value* cst = loadConstAsPtr(primary_arg);
15✔
1776
                        if (!(*cst < *sym))
15✔
1777
                            context.ip = secondary_arg * 4;
3✔
1778
                        DISPATCH();
15✔
1779
                    }
×
1780

1781
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1782
                    {
1783
                        UNPACK_ARGS();
×
1784
                        const Value* sym = popAndResolveAsPtr(context);
×
1785
                        const Value* rhs = loadSymbol(primary_arg, context);
×
1786
                        if (!(*rhs < *sym))
×
1787
                            context.ip = secondary_arg * 4;
×
1788
                        DISPATCH();
×
1789
                    }
1,233✔
1790

1791
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1792
                    {
1793
                        UNPACK_ARGS();
1,233✔
1794
                        const Value* sym = popAndResolveAsPtr(context);
1,233✔
1795
                        if (*sym == *loadConstAsPtr(primary_arg))
1,233✔
1796
                            context.ip = secondary_arg * 4;
246✔
1797
                        DISPATCH();
1,233✔
1798
                    }
86,933✔
1799

1800
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1801
                    {
1802
                        UNPACK_ARGS();
86,933✔
1803
                        const Value* sym = popAndResolveAsPtr(context);
86,933✔
1804
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
86,933✔
1805
                            context.ip = secondary_arg * 4;
529✔
1806
                        DISPATCH();
86,933✔
1807
                    }
1✔
1808

1809
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1810
                    {
1811
                        UNPACK_ARGS();
1✔
1812
                        const Value* sym = popAndResolveAsPtr(context);
1✔
1813
                        if (*sym != *loadConstAsPtr(primary_arg))
1✔
1814
                            context.ip = secondary_arg * 4;
1✔
1815
                        DISPATCH();
1✔
1816
                    }
15✔
1817

1818
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1819
                    {
1820
                        UNPACK_ARGS();
15✔
1821
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1822
                        if (*sym == *loadSymbol(primary_arg, context))
15✔
1823
                            context.ip = secondary_arg * 4;
5✔
1824
                        DISPATCH();
15✔
1825
                    }
26,082✔
1826

1827
                    TARGET(CALL_SYMBOL)
1828
                    {
1829
                        UNPACK_ARGS();
26,082✔
1830
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
26,082✔
1831
                        if (!m_running)
26,082✔
1832
                            GOTO_HALT();
×
1833
                        DISPATCH();
26,082✔
1834
                    }
109,861✔
1835

1836
                    TARGET(CALL_CURRENT_PAGE)
1837
                    {
1838
                        UNPACK_ARGS();
109,861✔
1839
                        context.last_symbol = primary_arg;
109,861✔
1840
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,861✔
1841
                        if (!m_running)
109,860✔
1842
                            GOTO_HALT();
×
1843
                        DISPATCH();
109,860✔
1844
                    }
1,891✔
1845

1846
                    TARGET(GET_FIELD_FROM_SYMBOL)
1847
                    {
1848
                        UNPACK_ARGS();
1,891✔
1849
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1,891✔
1850
                        DISPATCH();
1,891✔
1851
                    }
714✔
1852

1853
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1854
                    {
1855
                        UNPACK_ARGS();
714✔
1856
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
716✔
1857
                        DISPATCH();
712✔
1858
                    }
14,448✔
1859

1860
                    TARGET(AT_SYM_SYM)
1861
                    {
1862
                        UNPACK_ARGS();
14,448✔
1863
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
14,448✔
1864
                        DISPATCH();
14,448✔
1865
                    }
×
1866

1867
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1868
                    {
1869
                        UNPACK_ARGS();
×
1870
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
×
1871
                        DISPATCH();
×
1872
                    }
4✔
1873

1874
                    TARGET(CHECK_TYPE_OF)
1875
                    {
1876
                        UNPACK_ARGS();
4✔
1877
                        const Value* sym = loadSymbol(primary_arg, context);
4✔
1878
                        const Value* cst = loadConstAsPtr(secondary_arg);
4✔
1879
                        push(
4✔
1880
                            cst->valueType() == ValueType::String &&
8✔
1881
                                    std::to_string(sym->valueType()) == cst->string()
4✔
1882
                                ? Builtins::trueSym
1883
                                : Builtins::falseSym,
1884
                            context);
4✔
1885
                        DISPATCH();
4✔
1886
                    }
74✔
1887

1888
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1889
                    {
1890
                        UNPACK_ARGS();
74✔
1891
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
74✔
1892
                        const Value* cst = loadConstAsPtr(secondary_arg);
74✔
1893
                        push(
74✔
1894
                            cst->valueType() == ValueType::String &&
148✔
1895
                                    std::to_string(sym->valueType()) == cst->string()
74✔
1896
                                ? Builtins::trueSym
1897
                                : Builtins::falseSym,
1898
                            context);
74✔
1899
                        DISPATCH();
74✔
1900
                    }
1,458✔
1901

1902
                    TARGET(APPEND_IN_PLACE_SYM)
1903
                    {
1904
                        UNPACK_ARGS();
1,458✔
1905
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1,458✔
1906
                        DISPATCH();
1,458✔
1907
                    }
12✔
1908

1909
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1910
                    {
1911
                        UNPACK_ARGS();
12✔
1912
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
12✔
1913
                        DISPATCH();
11✔
1914
                    }
1915
#pragma endregion
1916
                }
79✔
1917
#if ARK_USE_COMPUTED_GOTOS
1918
            dispatch_end:
1919
                do
79✔
1920
                {
1921
                } while (false);
79✔
1922
#endif
1923
            }
1924
        }
196✔
1925
        catch (const Error& e)
1926
        {
1927
            if (fail_with_exception)
75✔
1928
            {
1929
                std::stringstream stream;
75✔
1930
                backtrace(context, stream, /* colorize= */ false);
75✔
1931
                // It's important we have an Ark::Error here, as the constructor for NestedError
1932
                // does more than just aggregate error messages, hence the code duplication.
1933
                throw NestedError(e, stream.str());
75✔
1934
            }
75✔
1935
            else
1936
                showBacktraceWithException(Error(e.details(/* colorize= */ true)), context);
×
1937
        }
154✔
1938
        catch (const std::exception& e)
1939
        {
1940
            if (fail_with_exception)
42✔
1941
            {
1942
                std::stringstream stream;
42✔
1943
                backtrace(context, stream, /* colorize= */ false);
42✔
1944
                throw NestedError(e, stream.str());
42✔
1945
            }
42✔
1946
            else
1947
                showBacktraceWithException(e, context);
×
1948
        }
117✔
1949
        catch (...)
1950
        {
1951
            if (fail_with_exception)
×
1952
                throw;
×
1953

1954
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1955
            throw;
1956
#endif
1957
            fmt::println("Unknown error");
×
1958
            backtrace(context);
×
1959
            m_exit_code = 1;
×
1960
        }
159✔
1961

1962
        return m_exit_code;
79✔
1963
    }
234✔
1964

1965
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,048✔
1966
    {
2,048✔
1967
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,178✔
1968
        {
1969
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,130✔
1970
                return id;
2,048✔
1971
        }
2,096,130✔
1972
        return std::numeric_limits<uint16_t>::max();
×
1973
    }
2,048✔
1974

1975
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
5✔
1976
    {
5✔
1977
        std::vector<std::string> arg_names;
5✔
1978
        arg_names.reserve(expected_arg_count + 1);
5✔
1979
        if (expected_arg_count > 0)
5✔
1980
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
1981

1982
        std::size_t index = 0;
5✔
1983
        while (m_state.inst(context.pp, index) == STORE)
12✔
1984
        {
1985
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
7✔
1986
            arg_names.push_back(m_state.m_symbols[id]);
7✔
1987
            index += 4;
7✔
1988
        }
7✔
1989
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
1990
        if (arg_names.size() == 1 && index == 0)
5✔
1991
        {
1992
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
1993
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
1994
                arg_names.push_back(std::string(1, static_cast<char>('a' + i)));
2✔
1995
        }
2✔
1996

1997
        std::vector<std::string> arg_vals;
5✔
1998
        arg_vals.reserve(passed_arg_count + 1);
5✔
1999
        if (passed_arg_count > 0)
5✔
2000
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
4✔
2001

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

2006
        // set ip/pp to the callee location so that the error can pinpoint the line
2007
        // where the bad call happened
2008
        if (context.sp >= 2 + passed_arg_count)
5✔
2009
        {
2010
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
5✔
2011
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
5✔
2012
            returnFromFuncCall(context);
5✔
2013
        }
5✔
2014

2015
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
10✔
2016
            ? m_state.m_symbols[context.last_symbol]
5✔
2017
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2018

2019
        throwVMError(
5✔
2020
            ErrorKind::Arity,
2021
            fmt::format(
10✔
2022
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
5✔
2023
                function_name,
2024
                fmt::join(arg_vals, " "),
5✔
2025
                passed_arg_count,
2026
                passed_arg_count > 1 ? "s" : "",
5✔
2027
                expected_arg_count,
2028
                function_name,
2029
                fmt::join(arg_names, " ")));
5✔
2030
    }
10✔
2031

2032
    void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
×
2033
    {
×
2034
        std::string text = e.what();
×
2035
        if (!text.empty() && text.back() != '\n')
×
2036
            text += '\n';
×
2037
        fmt::println("{}", text);
×
2038
        backtrace(context);
×
2039
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2040
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2041
        m_exit_code = 0;
2042
#else
2043
        m_exit_code = 1;
×
2044
#endif
2045
    }
×
2046

2047
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp)
2,167✔
2048
    {
2,167✔
2049
        std::optional<InstLoc> match = std::nullopt;
2,167✔
2050

2051
        for (const auto location : m_state.m_inst_locations)
8,724✔
2052
        {
2053
            if (location.page_pointer == pp && !match)
6,557✔
2054
                match = location;
2,167✔
2055

2056
            // select the best match: we want to find the location that's nearest our instruction pointer,
2057
            // but not equal to it as the IP will always be pointing to the next instruction,
2058
            // not yet executed. Thus, the erroneous instruction is the previous one.
2059
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
6,557✔
2060
                match = location;
2,280✔
2061

2062
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2063
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
6,557✔
2064
                break;
13✔
2065
        }
6,557✔
2066

2067
        return match;
2,167✔
2068
    }
2069

2070
    std::string VM::debugShowSource()
×
2071
    {
×
2072
        const auto& context = m_execution_contexts.front();
×
2073
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2074
        if (maybe_source_loc)
×
2075
        {
2076
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2077
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2078
        }
×
2079
        return "No source location found";
×
2080
    }
×
2081

2082
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
117✔
2083
    {
117✔
2084
        const std::size_t saved_ip = context.ip;
117✔
2085
        const std::size_t saved_pp = context.pp;
117✔
2086
        const uint16_t saved_sp = context.sp;
117✔
2087
        const std::size_t max_consecutive_traces = 7;
117✔
2088

2089
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
117✔
2090
        if (maybe_location)
117✔
2091
        {
2092
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
117✔
2093

2094
            if (Utils::fileExists(filename))
117✔
2095
                Diagnostics::makeContext(
234✔
2096
                    os,
117✔
2097
                    filename,
2098
                    /* expr= */ std::nullopt,
117✔
2099
                    /* sym_size= */ 0,
2100
                    maybe_location->line,
117✔
2101
                    /* col_start= */ 0,
2102
                    /* maybe_context= */ std::nullopt,
117✔
2103
                    /* whole_line= */ true,
2104
                    /* colorize= */ colorize);
117✔
2105
            fmt::println(os, "");
117✔
2106
        }
117✔
2107

2108
        if (context.fc > 1)
117✔
2109
        {
2110
            // display call stack trace
2111
            const ScopeView old_scope = context.locals.back();
2✔
2112

2113
            std::string previous_trace;
2✔
2114
            std::size_t displayed_traces = 0;
2✔
2115
            std::size_t consecutive_similar_traces = 0;
2✔
2116

2117
            while (context.fc != 0 && context.pp != 0)
2,050✔
2118
            {
2119
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,048✔
2120
                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,048✔
2121

2122
                const uint16_t id = findNearestVariableIdWithValue(
2,048✔
2123
                    Value(static_cast<PageAddr_t>(context.pp)),
2,048✔
2124
                    context);
2,048✔
2125
                const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,048✔
2126

2127
                if (func_name + loc_as_text != previous_trace)
2,048✔
2128
                {
2129
                    fmt::println(
4✔
2130
                        os,
2✔
2131
                        "[{:4}] In function `{}'{}",
2✔
2132
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2✔
2133
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
2✔
2134
                        loc_as_text);
2135
                    previous_trace = func_name + loc_as_text;
2✔
2136
                    ++displayed_traces;
2✔
2137
                    consecutive_similar_traces = 0;
2✔
2138
                }
2✔
2139
                else if (consecutive_similar_traces == 0)
2,046✔
2140
                {
2141
                    fmt::println(os, "       ...");
1✔
2142
                    ++consecutive_similar_traces;
1✔
2143
                }
1✔
2144

2145
                const Value* ip;
2,048✔
2146
                do
2,049✔
2147
                {
2148
                    ip = popAndResolveAsPtr(context);
2,049✔
2149
                } while (ip->valueType() != ValueType::InstPtr);
2,049✔
2150

2151
                context.ip = ip->pageAddr();
2,048✔
2152
                context.pp = pop(context)->pageAddr();
2,048✔
2153
                returnFromFuncCall(context);
2,048✔
2154

2155
                if (displayed_traces > max_consecutive_traces)
2,048✔
2156
                {
2157
                    fmt::println(os, "       ...");
×
2158
                    break;
×
2159
                }
2160
            }
2,048✔
2161

2162
            if (context.pp == 0)
2✔
2163
            {
2164
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2✔
2165
                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✔
2166
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
2✔
2167
            }
2✔
2168

2169
            // display variables values in the current scope
2170
            fmt::println(os, "\nCurrent scope variables values:");
2✔
2171
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
3✔
2172
            {
2173
                fmt::println(
2✔
2174
                    os,
1✔
2175
                    "{} = {}",
1✔
2176
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2177
                    old_scope.atPos(i).second.toString(*this));
1✔
2178
            }
1✔
2179
        }
2✔
2180

2181
        fmt::println(
234✔
2182
            os,
117✔
2183
            "At IP: {}, PP: {}, SP: {}",
117✔
2184
            // dividing by 4 because the instructions are actually on 4 bytes
2185
            fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
117✔
2186
            fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
117✔
2187
            fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
117✔
2188
    }
117✔
2189
}
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