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

ArkScript-lang / Ark / 16861988279

10 Aug 2025 01:25PM UTC coverage: 87.684% (+0.8%) from 86.869%
16861988279

Pull #568

github

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

106 of 117 new or added lines in 10 files covered. (90.6%)

7 existing lines in 2 files now uncovered.

7632 of 8704 relevant lines covered (87.68%)

125473.89 hits per line

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

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

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

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

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

20
namespace Ark
21
{
22
    using namespace internal;
23

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

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

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

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

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

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

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

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

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

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

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

133
        context.sp = 0;
181✔
134
        context.fc = 1;
181✔
135

136
        m_shared_lib_objects.clear();
181✔
137
        context.stacked_closure_scopes.clear();
181✔
138
        context.stacked_closure_scopes.emplace_back(nullptr);
181✔
139

140
        context.saved_scope.reset();
181✔
141
        m_exit_code = 0;
181✔
142

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

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

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

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

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

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

213
        return l;
1,538✔
214
    }
1,538✔
215

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

229
        for (std::size_t i = 0; i < count; ++i)
2,944✔
230
            list->push_back(*popAndResolveAsPtr(context));
1,472✔
231
    }
1,473✔
232

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

243
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
33✔
244
        if (std::cmp_less(dist, MaxValue16Bits))
33✔
245
        {
246
            ExecutionContext& context = *m_execution_contexts.front();
33✔
247

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

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

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

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

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

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

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

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

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

308
        m_shared_lib_objects.emplace_back(lib);
1✔
309

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

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

323
                ++i;
1✔
324
            }
1✔
325

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

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

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

348
        ExecutionContext* ctx = nullptr;
15✔
349

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

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

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

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

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

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

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

397
        return ctx;
15✔
398
    }
15✔
399

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

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

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

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

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

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

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

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

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

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

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

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

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

517
#define NEXTOPARG()                                                                   \
518
    do                                                                                \
519
    {                                                                                 \
520
        inst = m_state.inst(context.pp, context.ip);                                  \
521
        padding = m_state.inst(context.pp, context.ip + 1);                           \
522
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) + \
523
                                    m_state.inst(context.pp, context.ip + 3));        \
524
        context.ip += 4;                                                              \
525
    } while (false)
526
#define DISPATCH() \
527
    NEXTOPARG();   \
528
    DISPATCH_GOTO();
529
#define UNPACK_ARGS()                                                                 \
530
    do                                                                                \
531
    {                                                                                 \
532
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
533
        primary_arg = arg & 0x0fff;                                                   \
534
    } while (false)
535

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

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

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

659
            m_running = true;
201✔
660

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

675
                    TARGET(LOAD_SYMBOL)
676
                    {
677
                        push(loadSymbol(arg, context), context);
146,461✔
678
                        DISPATCH();
146,459✔
679
                    }
333,631✔
680

681
                    TARGET(LOAD_SYMBOL_BY_INDEX)
682
                    {
683
                        push(loadSymbolFromIndex(arg, context), context);
333,631✔
684
                        DISPATCH();
333,631✔
685
                    }
115,930✔
686

687
                    TARGET(LOAD_CONST)
688
                    {
689
                        push(loadConstAsPtr(arg), context);
115,930✔
690
                        DISPATCH();
115,930✔
691
                    }
28,553✔
692

693
                    TARGET(POP_JUMP_IF_TRUE)
694
                    {
695
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
35,509✔
696
                            context.ip = arg * 4;  // instructions are 4 bytes
6,956✔
697
                        DISPATCH();
28,553✔
698
                    }
401,142✔
699

700
                    TARGET(STORE)
701
                    {
702
                        store(arg, popAndResolveAsPtr(context), context);
401,142✔
703
                        DISPATCH();
401,142✔
704
                    }
22,501✔
705

706
                    TARGET(SET_VAL)
707
                    {
708
                        setVal(arg, popAndResolveAsPtr(context), context);
22,501✔
709
                        DISPATCH();
22,501✔
710
                    }
27,449✔
711

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

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

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

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

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

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

756
                        DISPATCH();
136,489✔
757
                    }
63✔
758

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

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

773
                    TARGET(CALL)
774
                    {
775
                        call(context, arg);
2,605✔
776
                        if (!m_running)
2,598✔
777
                            GOTO_HALT();
×
778
                        DISPATCH();
2,598✔
779
                    }
3,118✔
780

781
                    TARGET(CAPTURE)
782
                    {
783
                        if (!context.saved_scope)
3,118✔
784
                            context.saved_scope = ClosureScope();
620✔
785

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

797
                        DISPATCH();
3,118✔
798
                    }
12✔
799

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

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

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

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

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

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

839
                    TARGET(PLUGIN)
840
                    {
841
                        loadPlugin(arg, context);
1✔
842
                        DISPATCH();
1✔
843
                    }
633✔
844

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

974
                            list->list().erase(list->list().begin() + idx);
81✔
975
                        }
84✔
976
                        DISPATCH();
81✔
977
                    }
490✔
978

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1127
#pragma endregion
1128

1129
#pragma region "Operators"
1130

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1299
                        if (*a == Builtins::falseSym)
597✔
1300
                            throw AssertionFailed(b->stringRef());
1✔
1301
                        DISPATCH();
596✔
1302
                    }
15✔
1303

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1432
#pragma endregion
1433

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

1443
                    TARGET(LOAD_CONST_STORE)
1444
                    {
1445
                        UNPACK_ARGS();
8,021✔
1446
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
8,021✔
1447
                        DISPATCH();
8,021✔
1448
                    }
241✔
1449

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1854
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1855
                    {
1856
                        UNPACK_ARGS();
715✔
1857
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
717✔
1858
                        DISPATCH();
713✔
1859
                    }
14,451✔
1860

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

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

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

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

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

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

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

1963
        return m_exit_code;
79✔
1964
    }
240✔
1965

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

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

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

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

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

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

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

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

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

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

2052
        for (const auto location : m_state.m_inst_locations)
8,726✔
2053
        {
2054
            if (location.page_pointer == pp && !match)
6,558✔
2055
                match = location;
2,168✔
2056

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

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

2068
        return match;
2,168✔
2069
    }
2070

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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