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

ArkScript-lang / Ark / 16862576586

10 Aug 2025 02:21PM UTC coverage: 87.685% (+0.8%) from 86.869%
16862576586

push

github

SuperFola
fix(ci): import tests/unittests/testmodule.arkm in artifacts

7633 of 8705 relevant lines covered (87.69%)

125464.93 hits per line

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

85.64
/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/VM/ModuleMapping.hpp>
13
#include <Ark/Compiler/Instructions.hpp>
14

15
namespace Ark
16
{
17
    using namespace internal;
18

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

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

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

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

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

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

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

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

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

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

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

128
        context.sp = 0;
181✔
129
        context.fc = 1;
181✔
130

131
        m_shared_lib_objects.clear();
181✔
132
        context.stacked_closure_scopes.clear();
181✔
133
        context.stacked_closure_scopes.emplace_back(nullptr);
181✔
134

135
        context.saved_scope.reset();
181✔
136
        m_exit_code = 0;
181✔
137

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

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

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

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

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

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

208
        return l;
1,538✔
209
    }
1,538✔
210

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

224
        for (std::size_t i = 0; i < count; ++i)
2,944✔
225
            list->push_back(*popAndResolveAsPtr(context));
1,472✔
226
    }
1,473✔
227

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

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

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

249
        m_no_value = Builtins::nil;
×
250
        return m_no_value;
×
251
    }
36✔
252

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

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

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

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

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

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

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

303
        m_shared_lib_objects.emplace_back(lib);
1✔
304

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

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

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

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

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

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

343
        ExecutionContext* ctx = nullptr;
15✔
344

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

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

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

370
        if (ctx == nullptr)
15✔
371
            ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
6✔
372

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

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

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

392
        return ctx;
15✔
393
    }
15✔
394

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

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

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

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

437
        return m_futures.back().get();
15✔
438
    }
15✔
439

440
    void VM::deleteFuture(Future* f)
×
441
    {
×
442
        const std::lock_guard lock(m_mutex);
×
443

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

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

473
                    ++i;
×
474
                }
×
475
            }
×
476

477
            return true;
×
478
        }
×
479
        catch (const std::system_error&)
480
        {
481
            return false;
×
482
        }
×
483
    }
×
484

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

490
    int VM::run(const bool fail_with_exception)
181✔
491
    {
181✔
492
        init();
181✔
493
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
181✔
494
        return m_exit_code;
181✔
495
    }
496

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

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

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

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

646
        try
647
        {
648
            uint8_t inst = 0;
199✔
649
            uint8_t padding = 0;
199✔
650
            uint16_t arg = 0;
199✔
651
            uint16_t primary_arg = 0;
199✔
652
            uint16_t secondary_arg = 0;
199✔
653

654
            m_running = true;
199✔
655

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

670
                    TARGET(LOAD_SYMBOL)
671
                    {
672
                        push(loadSymbol(arg, context), context);
146,461✔
673
                        DISPATCH();
146,459✔
674
                    }
333,631✔
675

676
                    TARGET(LOAD_SYMBOL_BY_INDEX)
677
                    {
678
                        push(loadSymbolFromIndex(arg, context), context);
333,631✔
679
                        DISPATCH();
333,631✔
680
                    }
115,930✔
681

682
                    TARGET(LOAD_CONST)
683
                    {
684
                        push(loadConstAsPtr(arg), context);
115,930✔
685
                        DISPATCH();
115,930✔
686
                    }
28,553✔
687

688
                    TARGET(POP_JUMP_IF_TRUE)
689
                    {
690
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
35,509✔
691
                            context.ip = arg * 4;  // instructions are 4 bytes
6,956✔
692
                        DISPATCH();
28,553✔
693
                    }
401,142✔
694

695
                    TARGET(STORE)
696
                    {
697
                        store(arg, popAndResolveAsPtr(context), context);
401,142✔
698
                        DISPATCH();
401,142✔
699
                    }
22,501✔
700

701
                    TARGET(SET_VAL)
702
                    {
703
                        setVal(arg, popAndResolveAsPtr(context), context);
22,501✔
704
                        DISPATCH();
22,501✔
705
                    }
27,449✔
706

707
                    TARGET(POP_JUMP_IF_FALSE)
708
                    {
709
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
29,535✔
710
                            context.ip = arg * 4;  // instructions are 4 bytes
2,086✔
711
                        DISPATCH();
27,449✔
712
                    }
206,765✔
713

714
                    TARGET(JUMP)
715
                    {
716
                        context.ip = arg * 4;  // instructions are 4 bytes
206,765✔
717
                        DISPATCH();
206,765✔
718
                    }
136,505✔
719

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

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

743
                                returnFromFuncCall(context);
133,638✔
744
                                push(std::move(ip_or_val), context);
133,638✔
745
                            }
746

747
                            if (context.fc <= untilFrameCount)
136,505✔
748
                                GOTO_HALT();
16✔
749
                        }
136,505✔
750

751
                        DISPATCH();
136,489✔
752
                    }
63✔
753

754
                    TARGET(HALT)
755
                    {
756
                        m_running = false;
63✔
757
                        GOTO_HALT();
63✔
758
                    }
139,370✔
759

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

768
                    TARGET(CALL)
769
                    {
770
                        call(context, arg);
2,605✔
771
                        if (!m_running)
2,598✔
772
                            GOTO_HALT();
×
773
                        DISPATCH();
2,598✔
774
                    }
3,119✔
775

776
                    TARGET(CAPTURE)
777
                    {
778
                        if (!context.saved_scope)
3,119✔
779
                            context.saved_scope = ClosureScope();
620✔
780

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

792
                        DISPATCH();
3,119✔
793
                    }
13✔
794

795
                    TARGET(RENAME_NEXT_CAPTURE)
796
                    {
797
                        context.capture_rename_id = arg;
13✔
798
                        DISPATCH();
13✔
799
                    }
1,536✔
800

801
                    TARGET(BUILTIN)
802
                    {
803
                        push(Builtins::builtins[arg].second, context);
1,536✔
804
                        DISPATCH();
1,536✔
805
                    }
1✔
806

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

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

820
                    TARGET(MAKE_CLOSURE)
821
                    {
822
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
620✔
823
                        context.saved_scope.reset();
620✔
824
                        DISPATCH();
620✔
825
                    }
6✔
826

827
                    TARGET(GET_FIELD)
828
                    {
829
                        Value* var = popAndResolveAsPtr(context);
6✔
830
                        push(getField(var, arg, context), context);
6✔
831
                        DISPATCH();
6✔
832
                    }
1✔
833

834
                    TARGET(PLUGIN)
835
                    {
836
                        loadPlugin(arg, context);
1✔
837
                        DISPATCH();
1✔
838
                    }
633✔
839

840
                    TARGET(LIST)
841
                    {
842
                        {
843
                            Value l = createList(arg, context);
633✔
844
                            push(std::move(l), context);
633✔
845
                        }
633✔
846
                        DISPATCH();
633✔
847
                    }
1,552✔
848

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

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

866
                            Value obj { *list };
1,551✔
867
                            obj.list().reserve(size + arg);
1,551✔
868

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

876
                    TARGET(CONCAT)
877
                    {
878
                        {
879
                            Value* list = popAndResolveAsPtr(context);
12✔
880
                            Value obj { *list };
12✔
881

882
                            for (uint16_t i = 0; i < arg; ++i)
24✔
883
                            {
884
                                Value* next = popAndResolveAsPtr(context);
14✔
885

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

892
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
12✔
893
                            }
12✔
894
                            push(std::move(obj), context);
10✔
895
                        }
12✔
896
                        DISPATCH();
10✔
897
                    }
×
898

899
                    TARGET(APPEND_IN_PLACE)
900
                    {
901
                        Value* list = popAndResolveAsPtr(context);
×
902
                        listAppendInPlace(list, arg, context);
×
903
                        DISPATCH();
×
904
                    }
562✔
905

906
                    TARGET(CONCAT_IN_PLACE)
907
                    {
908
                        Value* list = popAndResolveAsPtr(context);
562✔
909

910
                        for (uint16_t i = 0; i < arg; ++i)
1,152✔
911
                        {
912
                            Value* next = popAndResolveAsPtr(context);
592✔
913

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

920
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
590✔
921
                        }
590✔
922
                        DISPATCH();
560✔
923
                    }
6✔
924

925
                    TARGET(POP_LIST)
926
                    {
927
                        {
928
                            Value list = *popAndResolveAsPtr(context);
6✔
929
                            Value number = *popAndResolveAsPtr(context);
6✔
930

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

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

944
                            list.list().erase(list.list().begin() + idx);
3✔
945
                            push(list, context);
3✔
946
                        }
6✔
947
                        DISPATCH();
3✔
948
                    }
84✔
949

950
                    TARGET(POP_LIST_IN_PLACE)
951
                    {
952
                        {
953
                            Value* list = popAndResolveAsPtr(context);
84✔
954
                            Value number = *popAndResolveAsPtr(context);
84✔
955

956
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
84✔
957
                                throw types::TypeCheckingError(
2✔
958
                                    "pop!",
1✔
959
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
960
                                    { *list, number });
1✔
961

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

969
                            list->list().erase(list->list().begin() + idx);
81✔
970
                        }
84✔
971
                        DISPATCH();
81✔
972
                    }
490✔
973

974
                    TARGET(SET_AT_INDEX)
975
                    {
976
                        {
977
                            Value* list = popAndResolveAsPtr(context);
490✔
978
                            Value number = *popAndResolveAsPtr(context);
490✔
979
                            Value new_value = *popAndResolveAsPtr(context);
490✔
980

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

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

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

1010
                    TARGET(SET_AT_2_INDEX)
1011
                    {
1012
                        {
1013
                            Value* list = popAndResolveAsPtr(context);
12✔
1014
                            Value x = *popAndResolveAsPtr(context);
12✔
1015
                            Value y = *popAndResolveAsPtr(context);
12✔
1016
                            Value new_value = *popAndResolveAsPtr(context);
12✔
1017

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

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

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

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

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

1064
                            if (is_list)
6✔
1065
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1066
                            else
1067
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1068
                        }
12✔
1069
                        DISPATCH();
6✔
1070
                    }
2,714✔
1071

1072
                    TARGET(POP)
1073
                    {
1074
                        pop(context);
2,714✔
1075
                        DISPATCH();
2,714✔
1076
                    }
23,415✔
1077

1078
                    TARGET(SHORTCIRCUIT_AND)
1079
                    {
1080
                        if (!*peekAndResolveAsPtr(context))
23,415✔
1081
                            context.ip = arg * 4;
733✔
1082
                        else
1083
                            pop(context);
22,682✔
1084
                        DISPATCH();
23,415✔
1085
                    }
812✔
1086

1087
                    TARGET(SHORTCIRCUIT_OR)
1088
                    {
1089
                        if (!!*peekAndResolveAsPtr(context))
812✔
1090
                            context.ip = arg * 4;
216✔
1091
                        else
1092
                            pop(context);
596✔
1093
                        DISPATCH();
812✔
1094
                    }
2,753✔
1095

1096
                    TARGET(CREATE_SCOPE)
1097
                    {
1098
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
2,753✔
1099
                        DISPATCH();
2,753✔
1100
                    }
31,128✔
1101

1102
                    TARGET(RESET_SCOPE_JUMP)
1103
                    {
1104
                        context.locals.back().reset();
31,128✔
1105
                        context.ip = arg * 4;  // instructions are 4 bytes
31,128✔
1106
                        DISPATCH();
31,128✔
1107
                    }
2,753✔
1108

1109
                    TARGET(POP_SCOPE)
1110
                    {
1111
                        context.locals.pop_back();
2,753✔
1112
                        DISPATCH();
2,753✔
1113
                    }
×
1114

1115
                    TARGET(GET_CURRENT_PAGE_ADDR)
1116
                    {
1117
                        context.last_symbol = arg;
×
1118
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1119
                        DISPATCH();
×
1120
                    }
26,906✔
1121

1122
#pragma endregion
1123

1124
#pragma region "Operators"
1125

1126
                    TARGET(ADD)
1127
                    {
1128
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
26,906✔
1129

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

1143
                    TARGET(SUB)
1144
                    {
1145
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
329✔
1146

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

1156
                    TARGET(MUL)
1157
                    {
1158
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,214✔
1159

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

1169
                    TARGET(DIV)
1170
                    {
1171
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,062✔
1172

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

1182
                        push(Value(a->number() / d), context);
1,060✔
1183
                        DISPATCH();
1,060✔
1184
                    }
160✔
1185

1186
                    TARGET(GT)
1187
                    {
1188
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
160✔
1189
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
160✔
1190
                        DISPATCH();
160✔
1191
                    }
28,822✔
1192

1193
                    TARGET(LT)
1194
                    {
1195
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,822✔
1196
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
28,822✔
1197
                        DISPATCH();
28,822✔
1198
                    }
7,192✔
1199

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

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

1214
                    TARGET(NEQ)
1215
                    {
1216
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
748✔
1217
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
748✔
1218
                        DISPATCH();
748✔
1219
                    }
17,451✔
1220

1221
                    TARGET(EQ)
1222
                    {
1223
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17,451✔
1224
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
17,451✔
1225
                        DISPATCH();
17,451✔
1226
                    }
12,202✔
1227

1228
                    TARGET(LEN)
1229
                    {
1230
                        const Value* a = popAndResolveAsPtr(context);
12,202✔
1231

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

1245
                    TARGET(EMPTY)
1246
                    {
1247
                        const Value* a = popAndResolveAsPtr(context);
540✔
1248

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

1262
                    TARGET(TAIL)
1263
                    {
1264
                        Value* const a = popAndResolveAsPtr(context);
335✔
1265
                        push(helper::tail(a), context);
336✔
1266
                        DISPATCH();
334✔
1267
                    }
1,099✔
1268

1269
                    TARGET(HEAD)
1270
                    {
1271
                        Value* const a = popAndResolveAsPtr(context);
1,099✔
1272
                        push(helper::head(a), context);
1,100✔
1273
                        DISPATCH();
1,098✔
1274
                    }
2,323✔
1275

1276
                    TARGET(ISNIL)
1277
                    {
1278
                        const Value* a = popAndResolveAsPtr(context);
2,323✔
1279
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,323✔
1280
                        DISPATCH();
2,323✔
1281
                    }
598✔
1282

1283
                    TARGET(ASSERT)
1284
                    {
1285
                        Value* const b = popAndResolveAsPtr(context);
598✔
1286
                        Value* const a = popAndResolveAsPtr(context);
598✔
1287

1288
                        if (b->valueType() != ValueType::String)
598✔
1289
                            throw types::TypeCheckingError(
2✔
1290
                                "assert",
1✔
1291
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1292
                                { *a, *b });
1✔
1293

1294
                        if (*a == Builtins::falseSym)
597✔
1295
                            throw AssertionFailed(b->stringRef());
1✔
1296
                        DISPATCH();
596✔
1297
                    }
15✔
1298

1299
                    TARGET(TO_NUM)
1300
                    {
1301
                        const Value* a = popAndResolveAsPtr(context);
15✔
1302

1303
                        if (a->valueType() != ValueType::String)
15✔
1304
                            throw types::TypeCheckingError(
2✔
1305
                                "toNumber",
1✔
1306
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1307
                                { *a });
1✔
1308

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

1317
                    TARGET(TO_STR)
1318
                    {
1319
                        const Value* a = popAndResolveAsPtr(context);
120✔
1320
                        push(Value(a->toString(*this)), context);
120✔
1321
                        DISPATCH();
120✔
1322
                    }
1,047✔
1323

1324
                    TARGET(AT)
1325
                    {
1326
                        Value& b = *popAndResolveAsPtr(context);
1,047✔
1327
                        Value& a = *popAndResolveAsPtr(context);
1,047✔
1328
                        push(helper::at(a, b, *this), context);
1,051✔
1329
                        DISPATCH();
1,043✔
1330
                    }
26✔
1331

1332
                    TARGET(AT_AT)
1333
                    {
1334
                        {
1335
                            const Value* x = popAndResolveAsPtr(context);
26✔
1336
                            const Value* y = popAndResolveAsPtr(context);
26✔
1337
                            Value& list = *popAndResolveAsPtr(context);
26✔
1338

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

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

1356
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
23✔
1357
                            const std::size_t size =
23✔
1358
                                is_list
46✔
1359
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
16✔
1360
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1361

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

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

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

1389
                    TARGET(TYPE)
1390
                    {
1391
                        const Value* a = popAndResolveAsPtr(context);
16✔
1392
                        push(Value(std::to_string(a->valueType())), context);
16✔
1393
                        DISPATCH();
16✔
1394
                    }
3✔
1395

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

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

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

1420
                    TARGET(NOT)
1421
                    {
1422
                        const Value* a = popAndResolveAsPtr(context);
3,569✔
1423
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,569✔
1424
                        DISPATCH();
3,569✔
1425
                    }
5,996✔
1426

1427
#pragma endregion
1428

1429
#pragma region "Super Instructions"
1430
                    TARGET(LOAD_CONST_LOAD_CONST)
1431
                    {
1432
                        UNPACK_ARGS();
5,996✔
1433
                        push(loadConstAsPtr(primary_arg), context);
5,996✔
1434
                        push(loadConstAsPtr(secondary_arg), context);
5,996✔
1435
                        DISPATCH();
5,996✔
1436
                    }
8,022✔
1437

1438
                    TARGET(LOAD_CONST_STORE)
1439
                    {
1440
                        UNPACK_ARGS();
8,022✔
1441
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
8,022✔
1442
                        DISPATCH();
8,022✔
1443
                    }
241✔
1444

1445
                    TARGET(LOAD_CONST_SET_VAL)
1446
                    {
1447
                        UNPACK_ARGS();
241✔
1448
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
241✔
1449
                        DISPATCH();
240✔
1450
                    }
20✔
1451

1452
                    TARGET(STORE_FROM)
1453
                    {
1454
                        UNPACK_ARGS();
20✔
1455
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
20✔
1456
                        DISPATCH();
19✔
1457
                    }
1,096✔
1458

1459
                    TARGET(STORE_FROM_INDEX)
1460
                    {
1461
                        UNPACK_ARGS();
1,096✔
1462
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,096✔
1463
                        DISPATCH();
1,096✔
1464
                    }
547✔
1465

1466
                    TARGET(SET_VAL_FROM)
1467
                    {
1468
                        UNPACK_ARGS();
547✔
1469
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
547✔
1470
                        DISPATCH();
547✔
1471
                    }
225✔
1472

1473
                    TARGET(SET_VAL_FROM_INDEX)
1474
                    {
1475
                        UNPACK_ARGS();
225✔
1476
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
225✔
1477
                        DISPATCH();
225✔
1478
                    }
16✔
1479

1480
                    TARGET(INCREMENT)
1481
                    {
1482
                        UNPACK_ARGS();
16✔
1483
                        {
1484
                            Value* var = loadSymbol(primary_arg, context);
16✔
1485

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

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

1501
                    TARGET(INCREMENT_BY_INDEX)
1502
                    {
1503
                        UNPACK_ARGS();
87,973✔
1504
                        {
1505
                            Value* var = loadSymbolFromIndex(primary_arg, context);
87,973✔
1506

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

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

1522
                    TARGET(INCREMENT_STORE)
1523
                    {
1524
                        UNPACK_ARGS();
31,117✔
1525
                        {
1526
                            Value* var = loadSymbol(primary_arg, context);
31,117✔
1527

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

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

1546
                    TARGET(DECREMENT)
1547
                    {
1548
                        UNPACK_ARGS();
1,776✔
1549
                        {
1550
                            Value* var = loadSymbol(primary_arg, context);
1,776✔
1551

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

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

1567
                    TARGET(DECREMENT_BY_INDEX)
1568
                    {
1569
                        UNPACK_ARGS();
194,408✔
1570
                        {
1571
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,408✔
1572

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

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

1588
                    TARGET(DECREMENT_STORE)
1589
                    {
1590
                        UNPACK_ARGS();
×
1591
                        {
1592
                            Value* var = loadSymbol(primary_arg, context);
×
1593

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

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

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

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

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

1645
                    TARGET(STORE_HEAD_BY_INDEX)
1646
                    {
1647
                        UNPACK_ARGS();
31✔
1648
                        {
1649
                            Value* list = loadSymbolFromIndex(primary_arg, context);
31✔
1650
                            Value head = helper::head(list);
31✔
1651
                            store(secondary_arg, &head, context);
31✔
1652
                        }
31✔
1653
                        DISPATCH();
31✔
1654
                    }
905✔
1655

1656
                    TARGET(STORE_LIST)
1657
                    {
1658
                        UNPACK_ARGS();
905✔
1659
                        {
1660
                            Value l = createList(primary_arg, context);
905✔
1661
                            store(secondary_arg, &l, context);
905✔
1662
                        }
905✔
1663
                        DISPATCH();
905✔
1664
                    }
×
1665

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

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

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

1699
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1700
                    {
1701
                        UNPACK_ARGS();
1✔
1702
                        {
1703
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1704
                            Value head = helper::head(list);
1✔
1705
                            setVal(secondary_arg, &head, context);
1✔
1706
                        }
1✔
1707
                        DISPATCH();
1✔
1708
                    }
814✔
1709

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

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

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

1739
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1740
                    {
1741
                        UNPACK_ARGS();
21,902✔
1742
                        const Value* sym = popAndResolveAsPtr(context);
21,902✔
1743
                        if (*sym < *loadConstAsPtr(primary_arg))
21,902✔
1744
                            context.ip = secondary_arg * 4;
10,950✔
1745
                        DISPATCH();
21,902✔
1746
                    }
5,545✔
1747

1748
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1749
                    {
1750
                        UNPACK_ARGS();
5,545✔
1751
                        const Value* sym = popAndResolveAsPtr(context);
5,545✔
1752
                        if (!(*sym < *loadSymbol(primary_arg, context)))
5,545✔
1753
                            context.ip = secondary_arg * 4;
537✔
1754
                        DISPATCH();
5,545✔
1755
                    }
172,502✔
1756

1757
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1758
                    {
1759
                        UNPACK_ARGS();
172,502✔
1760
                        const Value* sym = popAndResolveAsPtr(context);
172,502✔
1761
                        const Value* cst = loadConstAsPtr(primary_arg);
172,502✔
1762
                        if (*cst < *sym)
172,502✔
1763
                            context.ip = secondary_arg * 4;
86,587✔
1764
                        DISPATCH();
172,502✔
1765
                    }
15✔
1766

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

1777
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1778
                    {
1779
                        UNPACK_ARGS();
×
1780
                        const Value* sym = popAndResolveAsPtr(context);
×
1781
                        const Value* rhs = loadSymbol(primary_arg, context);
×
1782
                        if (!(*rhs < *sym))
×
1783
                            context.ip = secondary_arg * 4;
×
1784
                        DISPATCH();
×
1785
                    }
1,234✔
1786

1787
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1788
                    {
1789
                        UNPACK_ARGS();
1,234✔
1790
                        const Value* sym = popAndResolveAsPtr(context);
1,234✔
1791
                        if (*sym == *loadConstAsPtr(primary_arg))
1,234✔
1792
                            context.ip = secondary_arg * 4;
247✔
1793
                        DISPATCH();
1,234✔
1794
                    }
86,933✔
1795

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

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

1814
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1815
                    {
1816
                        UNPACK_ARGS();
15✔
1817
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1818
                        if (*sym == *loadSymbol(primary_arg, context))
15✔
1819
                            context.ip = secondary_arg * 4;
5✔
1820
                        DISPATCH();
15✔
1821
                    }
26,086✔
1822

1823
                    TARGET(CALL_SYMBOL)
1824
                    {
1825
                        UNPACK_ARGS();
26,086✔
1826
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
26,086✔
1827
                        if (!m_running)
26,086✔
1828
                            GOTO_HALT();
×
1829
                        DISPATCH();
26,086✔
1830
                    }
109,861✔
1831

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

1842
                    TARGET(GET_FIELD_FROM_SYMBOL)
1843
                    {
1844
                        UNPACK_ARGS();
1,907✔
1845
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1,907✔
1846
                        DISPATCH();
1,907✔
1847
                    }
715✔
1848

1849
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1850
                    {
1851
                        UNPACK_ARGS();
715✔
1852
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
717✔
1853
                        DISPATCH();
713✔
1854
                    }
14,451✔
1855

1856
                    TARGET(AT_SYM_SYM)
1857
                    {
1858
                        UNPACK_ARGS();
14,451✔
1859
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
14,451✔
1860
                        DISPATCH();
14,451✔
1861
                    }
×
1862

1863
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1864
                    {
1865
                        UNPACK_ARGS();
×
1866
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
×
1867
                        DISPATCH();
×
1868
                    }
4✔
1869

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

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

1898
                    TARGET(APPEND_IN_PLACE_SYM)
1899
                    {
1900
                        UNPACK_ARGS();
1,461✔
1901
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1,461✔
1902
                        DISPATCH();
1,461✔
1903
                    }
12✔
1904

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

1950
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1951
            throw;
1952
#endif
1953
            fmt::println("Unknown error");
×
1954
            backtrace(context);
×
1955
            m_exit_code = 1;
×
1956
        }
160✔
1957

1958
        return m_exit_code;
79✔
1959
    }
240✔
1960

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

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

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

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

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

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

2011
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
10✔
2012
            ? m_state.m_symbols[context.last_symbol]
5✔
2013
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2014

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

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

2043
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp)
2,168✔
2044
    {
2,168✔
2045
        std::optional<InstLoc> match = std::nullopt;
2,168✔
2046

2047
        for (const auto location : m_state.m_inst_locations)
8,726✔
2048
        {
2049
            if (location.page_pointer == pp && !match)
6,558✔
2050
                match = location;
2,168✔
2051

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

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

2063
        return match;
2,168✔
2064
    }
2065

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

2078
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
118✔
2079
    {
118✔
2080
        const std::size_t saved_ip = context.ip;
118✔
2081
        const std::size_t saved_pp = context.pp;
118✔
2082
        const uint16_t saved_sp = context.sp;
118✔
2083
        const std::size_t max_consecutive_traces = 7;
118✔
2084

2085
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
118✔
2086
        if (maybe_location)
118✔
2087
        {
2088
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
118✔
2089

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

2104
        if (context.fc > 1)
118✔
2105
        {
2106
            // display call stack trace
2107
            const ScopeView old_scope = context.locals.back();
2✔
2108

2109
            std::string previous_trace;
2✔
2110
            std::size_t displayed_traces = 0;
2✔
2111
            std::size_t consecutive_similar_traces = 0;
2✔
2112

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

2118
                const uint16_t id = findNearestVariableIdWithValue(
2,048✔
2119
                    Value(static_cast<PageAddr_t>(context.pp)),
2,048✔
2120
                    context);
2,048✔
2121
                const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,048✔
2122

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

2141
                const Value* ip;
2,048✔
2142
                do
2,049✔
2143
                {
2144
                    ip = popAndResolveAsPtr(context);
2,049✔
2145
                } while (ip->valueType() != ValueType::InstPtr);
2,049✔
2146

2147
                context.ip = ip->pageAddr();
2,048✔
2148
                context.pp = pop(context)->pageAddr();
2,048✔
2149
                returnFromFuncCall(context);
2,048✔
2150

2151
                if (displayed_traces > max_consecutive_traces)
2,048✔
2152
                {
2153
                    fmt::println(os, "       ...");
×
2154
                    break;
×
2155
                }
2156
            }
2,048✔
2157

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

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

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