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

ArkScript-lang / Ark / 14823373998

04 May 2025 05:03PM UTC coverage: 86.442% (+0.03%) from 86.409%
14823373998

push

github

SuperFola
fix: change the color of the function name inside runtime typechecking errors from blue to cyan to be easier to read inside dark terminals

0 of 1 new or added line in 1 file covered. (0.0%)

195 existing lines in 22 files now uncovered.

6835 of 7907 relevant lines covered (86.44%)

79668.53 hits per line

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

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

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

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

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

22
namespace Ark
23
{
24
    using namespace internal;
25

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

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

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

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

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

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

80
    VM::VM(State& state) noexcept :
465✔
81
        m_state(state), m_exit_code(0), m_running(false)
155✔
82
    {
155✔
83
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
155✔
84
    }
155✔
85

86
    void VM::init() noexcept
156✔
87
    {
156✔
88
        ExecutionContext& context = *m_execution_contexts.back();
156✔
89
        for (const auto& c : m_execution_contexts)
312✔
90
        {
91
            c->ip = 0;
156✔
92
            c->pp = 0;
156✔
93
            c->sp = 0;
156✔
94
        }
156✔
95

96
        context.sp = 0;
156✔
97
        context.fc = 1;
156✔
98

99
        m_shared_lib_objects.clear();
156✔
100
        context.stacked_closure_scopes.clear();
156✔
101
        context.stacked_closure_scopes.emplace_back(nullptr);
156✔
102

103
        context.saved_scope.reset();
156✔
104
        m_exit_code = 0;
156✔
105

106
        context.locals.clear();
156✔
107
        context.locals.emplace_back(context.scopes_storage.data(), 0);
156✔
108

109
        // loading bound stuff
110
        // put them in the global frame if we can, aka the first one
111
        for (const auto& [sym_id, value] : m_state.m_binded)
183✔
112
        {
113
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
22✔
114
            if (it != m_state.m_symbols.end())
22✔
115
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
5✔
116
        }
22✔
117
    }
156✔
118

119
    Value& VM::operator[](const std::string& name) noexcept
28✔
120
    {
28✔
121
        // find id of object
122
        const auto it = std::ranges::find(m_state.m_symbols, name);
28✔
123
        if (it == m_state.m_symbols.end())
28✔
124
        {
125
            m_no_value = Builtins::nil;
1✔
126
            return m_no_value;
1✔
127
        }
128

129
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
27✔
130
        if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
27✔
131
        {
132
            ExecutionContext& context = *m_execution_contexts.front();
27✔
133

134
            const auto id = static_cast<uint16_t>(dist);
27✔
135
            Value* var = findNearestVariable(id, context);
27✔
136
            if (var != nullptr)
27✔
137
                return *var;
27✔
138
        }
27✔
139

140
        m_no_value = Builtins::nil;
×
141
        return m_no_value;
×
142
    }
28✔
143

144
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
×
145
    {
×
146
        namespace fs = std::filesystem;
147

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

150
        std::string path = file;
×
151
        // bytecode loaded from file
152
        if (m_state.m_filename != ARK_NO_NAME_FILE)
×
153
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
×
154

155
        std::shared_ptr<SharedLibrary> lib;
×
156
        // if it exists alongside the .arkc file
157
        if (Utils::fileExists(path))
×
158
            lib = std::make_shared<SharedLibrary>(path);
×
159
        else
160
        {
161
            for (auto const& v : m_state.m_libenv)
×
162
            {
163
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
×
164

165
                // if it's already loaded don't do anything
155✔
166
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
×
167
                        return (val->path() == path || val->path() == lib_path);
×
168
                    }) != m_shared_lib_objects.end())
×
169
                    return;
×
170

171
                // check in lib_path
172
                if (Utils::fileExists(lib_path))
×
173
                {
174
                    lib = std::make_shared<SharedLibrary>(lib_path);
×
175
                    break;
×
176
                }
177
            }
×
178
        }
179

180
        if (!lib)
×
181
        {
182
            auto lib_path = std::accumulate(
×
183
                std::next(m_state.m_libenv.begin()),
×
184
                m_state.m_libenv.end(),
×
185
                m_state.m_libenv[0].string(),
×
186
                [](const std::string& a, const fs::path& b) -> std::string {
×
187
                    return a + "\n\t- " + b.string();
×
188
                });
×
189
            throwVMError(
×
190
                ErrorKind::Module,
191
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
192
        }
×
193

194
        m_shared_lib_objects.emplace_back(lib);
×
195

196
        // load the mapping from the dynamic library
197
        try
198
        {
199
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
×
200
            // load the mapping data
201
            std::size_t i = 0;
×
202
            while (map[i].name != nullptr)
×
203
            {
204
                // put it in the global frame, aka the first one
205
                auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
206
                if (it != m_state.m_symbols.end())
×
207
                    context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
×
208

209
                ++i;
×
210
            }
×
211
        }
×
212
        catch (const std::system_error& e)
213
        {
214
            throwVMError(
×
215
                ErrorKind::Module,
216
                fmt::format(
×
217
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
218
                    file, e.what()));
×
219
        }
×
220
    }
×
221

222
    void VM::exit(const int code) noexcept
×
223
    {
×
224
        m_exit_code = code;
×
225
        m_running = false;
×
226
    }
×
227

228
    ExecutionContext* VM::createAndGetContext()
6✔
229
    {
6✔
230
        const std::lock_guard lock(m_mutex);
6✔
231

232
        m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
6✔
233
        ExecutionContext* ctx = m_execution_contexts.back().get();
6✔
234
        ctx->stacked_closure_scopes.emplace_back(nullptr);
6✔
235

236
        ctx->locals.reserve(m_execution_contexts.front()->locals.size());
6✔
237
        ctx->scopes_storage = m_execution_contexts.front()->scopes_storage;
6✔
238
        for (const auto& local : m_execution_contexts.front()->locals)
20✔
239
        {
240
            auto& scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), local.m_start);
14✔
241
            scope.m_size = local.m_size;
14✔
242
            scope.m_min_id = local.m_min_id;
14✔
243
            scope.m_max_id = local.m_max_id;
14✔
244
        }
14✔
245

246
        return ctx;
6✔
247
    }
6✔
248

249
    void VM::deleteContext(ExecutionContext* ec)
5✔
250
    {
5✔
251
        const std::lock_guard lock(m_mutex);
5✔
252

253
        const auto it =
5✔
254
            std::ranges::remove_if(
10✔
255
                m_execution_contexts,
5✔
256
                [ec](const std::unique_ptr<ExecutionContext>& ctx) {
21✔
257
                    return ctx.get() == ec;
16✔
258
                })
259
                .begin();
5✔
260
        m_execution_contexts.erase(it);
5✔
261
    }
5✔
262

263
    Future* VM::createFuture(std::vector<Value>& args)
6✔
264
    {
6✔
265
        ExecutionContext* ctx = createAndGetContext();
6✔
266
        // so that we have access to the presumed symbol id of the function we are calling
267
        // assuming that the callee is always the global context
268
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
6✔
269

270
        // doing this after having created the context
271
        // because the context uses the mutex and we don't want a deadlock
272
        const std::lock_guard lock(m_mutex);
6✔
273
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
6✔
274

275
        return m_futures.back().get();
6✔
276
    }
6✔
277

UNCOV
278
    void VM::deleteFuture(Future* f)
×
279
    {
×
280
        const std::lock_guard lock(m_mutex);
×
281

282
        const auto it =
×
283
            std::ranges::remove_if(
×
UNCOV
284
                m_futures,
×
285
                [f](const std::unique_ptr<Future>& future) {
×
286
                    return future.get() == f;
×
287
                })
UNCOV
288
                .begin();
×
289
        m_futures.erase(it);
×
290
    }
×
291

UNCOV
292
    bool VM::forceReloadPlugins() const
×
UNCOV
293
    {
×
294
        // load the mapping from the dynamic library
295
        try
296
        {
UNCOV
297
            for (const auto& shared_lib : m_shared_lib_objects)
×
298
            {
299
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
300
                // load the mapping data
UNCOV
301
                std::size_t i = 0;
×
302
                while (map[i].name != nullptr)
×
303
                {
304
                    // put it in the global frame, aka the first one
305
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
306
                    if (it != m_state.m_symbols.end())
×
UNCOV
307
                        m_execution_contexts[0]->locals[0].push_back(
×
308
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
309
                            Value(map[i].value));
×
310

UNCOV
311
                    ++i;
×
312
                }
×
313
            }
×
314

UNCOV
315
            return true;
×
316
        }
×
317
        catch (const std::system_error&)
318
        {
UNCOV
319
            return false;
×
UNCOV
320
        }
×
UNCOV
321
    }
×
322

323
    int VM::run(const bool fail_with_exception)
156✔
324
    {
156✔
325
        init();
156✔
326
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
156✔
327
        return m_exit_code;
156✔
328
    }
329

330
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
168✔
331
    {
168✔
332
#if ARK_USE_COMPUTED_GOTOS
333
#    define TARGET(op) TARGET_##op:
334
#    define DISPATCH_GOTO()            \
335
        _Pragma("GCC diagnostic push") \
336
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
337
        _Pragma("GCC diagnostic pop")
338
#    define GOTO_HALT() goto dispatch_end
339
#else
340
#    define TARGET(op) case op:
341
#    define DISPATCH_GOTO() goto dispatch_opcode
342
#    define GOTO_HALT() break
343
#endif
344

345
#define NEXTOPARG()                                                                      \
346
    do                                                                                   \
347
    {                                                                                    \
348
        inst = m_state.m_pages[context.pp][context.ip];                                  \
349
        padding = m_state.m_pages[context.pp][context.ip + 1];                           \
350
        arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
351
                                    m_state.m_pages[context.pp][context.ip + 3]);        \
352
        context.ip += 4;                                                                 \
353
    } while (false)
354
#define DISPATCH() \
355
    NEXTOPARG();   \
356
    DISPATCH_GOTO();
357
#define UNPACK_ARGS()                                                                 \
358
    do                                                                                \
359
    {                                                                                 \
360
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
361
        primary_arg = arg & 0x0fff;                                                   \
362
    } while (false)
363

364
#if ARK_USE_COMPUTED_GOTOS
365
#    pragma GCC diagnostic push
366
#    pragma GCC diagnostic ignored "-Wpedantic"
367
            constexpr std::array opcode_targets = {
168✔
368
                // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
369
                &&TARGET_NOP,
370
                &&TARGET_LOAD_SYMBOL,
371
                &&TARGET_LOAD_SYMBOL_BY_INDEX,
372
                &&TARGET_LOAD_CONST,
373
                &&TARGET_POP_JUMP_IF_TRUE,
374
                &&TARGET_STORE,
375
                &&TARGET_SET_VAL,
376
                &&TARGET_POP_JUMP_IF_FALSE,
377
                &&TARGET_JUMP,
378
                &&TARGET_RET,
379
                &&TARGET_HALT,
380
                &&TARGET_CALL,
381
                &&TARGET_CAPTURE,
382
                &&TARGET_BUILTIN,
383
                &&TARGET_DEL,
384
                &&TARGET_MAKE_CLOSURE,
1✔
385
                &&TARGET_GET_FIELD,
386
                &&TARGET_PLUGIN,
1✔
387
                &&TARGET_LIST,
388
                &&TARGET_APPEND,
1✔
389
                &&TARGET_CONCAT,
390
                &&TARGET_APPEND_IN_PLACE,
391
                &&TARGET_CONCAT_IN_PLACE,
392
                &&TARGET_POP_LIST,
393
                &&TARGET_POP_LIST_IN_PLACE,
394
                &&TARGET_SET_AT_INDEX,
395
                &&TARGET_SET_AT_2_INDEX,
396
                &&TARGET_POP,
397
                &&TARGET_DUP,
398
                &&TARGET_CREATE_SCOPE,
399
                &&TARGET_RESET_SCOPE,
400
                &&TARGET_POP_SCOPE,
401
                &&TARGET_ADD,
402
                &&TARGET_SUB,
403
                &&TARGET_MUL,
404
                &&TARGET_DIV,
405
                &&TARGET_GT,
406
                &&TARGET_LT,
407
                &&TARGET_LE,
408
                &&TARGET_GE,
409
                &&TARGET_NEQ,
410
                &&TARGET_EQ,
411
                &&TARGET_LEN,
412
                &&TARGET_EMPTY,
413
                &&TARGET_TAIL,
414
                &&TARGET_HEAD,
415
                &&TARGET_ISNIL,
416
                &&TARGET_ASSERT,
417
                &&TARGET_TO_NUM,
418
                &&TARGET_TO_STR,
419
                &&TARGET_AT,
420
                &&TARGET_AT_AT,
421
                &&TARGET_MOD,
422
                &&TARGET_TYPE,
423
                &&TARGET_HASFIELD,
424
                &&TARGET_NOT,
425
                &&TARGET_LOAD_CONST_LOAD_CONST,
426
                &&TARGET_LOAD_CONST_STORE,
427
                &&TARGET_LOAD_CONST_SET_VAL,
428
                &&TARGET_STORE_FROM,
429
                &&TARGET_STORE_FROM_INDEX,
430
                &&TARGET_SET_VAL_FROM,
431
                &&TARGET_SET_VAL_FROM_INDEX,
432
                &&TARGET_INCREMENT,
433
                &&TARGET_INCREMENT_BY_INDEX,
434
                &&TARGET_DECREMENT,
435
                &&TARGET_DECREMENT_BY_INDEX,
436
                &&TARGET_STORE_TAIL,
437
                &&TARGET_STORE_TAIL_BY_INDEX,
438
                &&TARGET_STORE_HEAD,
439
                &&TARGET_STORE_HEAD_BY_INDEX,
440
                &&TARGET_SET_VAL_TAIL,
441
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
442
                &&TARGET_SET_VAL_HEAD,
443
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
444
                &&TARGET_CALL_BUILTIN
445
            };
446

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

451
        try
452
        {
453
            uint8_t inst = 0;
168✔
454
            uint8_t padding = 0;
168✔
455
            uint16_t arg = 0;
168✔
456
            uint16_t primary_arg = 0;
168✔
457
            uint16_t secondary_arg = 0;
168✔
458

459
            m_running = true;
168✔
460

461
            DISPATCH();
168✔
462
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
463
            {
464
#if !ARK_USE_COMPUTED_GOTOS
465
            dispatch_opcode:
466
                switch (inst)
467
#endif
UNCOV
468
                {
×
469
#pragma region "Instructions"
470
                    TARGET(NOP)
471
                    {
UNCOV
472
                        DISPATCH();
×
473
                    }
220,626✔
474

475
                    TARGET(LOAD_SYMBOL)
476
                    {
477
                        push(loadSymbol(arg, context), context);
220,626✔
478
                        DISPATCH();
220,619✔
479
                    }
414,134✔
480

481
                    TARGET(LOAD_SYMBOL_BY_INDEX)
482
                    {
483
                        push(loadSymbolFromIndex(arg, context), context);
414,134✔
484
                        DISPATCH();
414,134✔
485
                    }
293,227✔
486

487
                    TARGET(LOAD_CONST)
488
                    {
489
                        push(loadConstAsPtr(arg), context);
293,227✔
490
                        DISPATCH();
293,227✔
491
                    }
293,480✔
492

493
                    TARGET(POP_JUMP_IF_TRUE)
494
                    {
495
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
397,397✔
496
                            context.ip = arg * 4;  // instructions are 4 bytes
103,917✔
497
                        DISPATCH();
293,480✔
498
                    }
394,991✔
499

500
                    TARGET(STORE)
501
                    {
502
                        store(arg, popAndResolveAsPtr(context), context);
394,991✔
503
                        DISPATCH();
394,991✔
504
                    }
35,582✔
505

506
                    TARGET(SET_VAL)
507
                    {
508
                        setVal(arg, popAndResolveAsPtr(context), context);
35,582✔
509
                        DISPATCH();
35,584✔
510
                    }
24,875✔
511

512
                    TARGET(POP_JUMP_IF_FALSE)
513
                    {
514
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
27,113✔
515
                            context.ip = arg * 4;  // instructions are 4 bytes
2,238✔
516
                        DISPATCH();
24,875✔
517
                    }
205,580✔
518

519
                    TARGET(JUMP)
520
                    {
521
                        context.ip = arg * 4;  // instructions are 4 bytes
205,580✔
522
                        DISPATCH();
205,580✔
523
                    }
120,737✔
524

525
                    TARGET(RET)
526
                    {
527
                        {
528
                            Value ip_or_val = *popAndResolveAsPtr(context);
120,737✔
529
                            // no return value on the stack
530
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
120,737✔
531
                            {
532
                                context.ip = ip_or_val.pageAddr();
1,362✔
533
                                // we always push PP then IP, thus the next value
534
                                // MUST be the page pointer
535
                                context.pp = pop(context)->pageAddr();
1,362✔
536

537
                                returnFromFuncCall(context);
1,362✔
538
                                push(Builtins::nil, context);
1,362✔
539
                            }
1,362✔
540
                            // value on the stack
541
                            else [[likely]]
542
                            {
543
                                const Value* ip = popAndResolveAsPtr(context);
119,375✔
544
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
119,375✔
545
                                context.ip = ip->pageAddr();
119,375✔
546
                                context.pp = pop(context)->pageAddr();
119,375✔
547

548
                                returnFromFuncCall(context);
119,375✔
549
                                push(std::move(ip_or_val), context);
119,375✔
550
                            }
551

552
                            if (context.fc <= untilFrameCount)
120,737✔
553
                                GOTO_HALT();
6✔
554
                        }
120,737✔
555

556
                        DISPATCH();
120,731✔
557
                    }
54✔
558

559
                    TARGET(HALT)
560
                    {
561
                        m_running = false;
54✔
562
                        GOTO_HALT();
54✔
563
                    }
122,787✔
564

565
                    TARGET(CALL)
566
                    {
567
                        // stack pointer + 2 because we push IP and PP
568
                        if (context.sp + 2u >= VMStackSize) [[unlikely]]
122,787✔
569
                            throwVMError(
1✔
570
                                ErrorKind::VM,
571
                                fmt::format(
2✔
572
                                    "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
573
                                    m_state.m_symbols[context.last_symbol]));
1✔
574
                        call(context, arg);
122,786✔
575
                        if (!m_running)
122,781✔
UNCOV
576
                            GOTO_HALT();
×
577
                        DISPATCH();
122,781✔
578
                    }
457✔
579

580
                    TARGET(CAPTURE)
581
                    {
582
                        if (!context.saved_scope)
457✔
583
                            context.saved_scope = ClosureScope();
102✔
584

585
                        const Value* ptr = findNearestVariable(arg, context);
457✔
586
                        if (!ptr)
457✔
UNCOV
587
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
588
                        else
589
                        {
590
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
457✔
591
                            context.saved_scope.value().push_back(arg, *ptr);
457✔
592
                        }
593

594
                        DISPATCH();
457✔
595
                    }
423✔
596

597
                    TARGET(BUILTIN)
598
                    {
599
                        push(Builtins::builtins[arg].second, context);
423✔
600
                        DISPATCH();
423✔
601
                    }
1✔
602

603
                    TARGET(DEL)
604
                    {
605
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
606
                        {
UNCOV
607
                            if (var->valueType() == ValueType::User)
×
UNCOV
608
                                var->usertypeRef().del();
×
UNCOV
609
                            *var = Value();
×
UNCOV
610
                            DISPATCH();
×
611
                        }
612

613
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
614
                    }
102✔
615

616
                    TARGET(MAKE_CLOSURE)
102✔
617
                    {
618
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
102✔
619
                        context.saved_scope.reset();
102✔
620
                        DISPATCH();
102✔
621
                    }
1,570✔
622

623
                    TARGET(GET_FIELD)
624
                    {
625
                        Value* var = popAndResolveAsPtr(context);
1,570✔
626
                        if (var->valueType() != ValueType::Closure)
1,570✔
627
                        {
628
                            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
629
                                throwVMError(
1✔
630
                                    ErrorKind::Type,
631
                                    fmt::format(
4✔
632
                                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
633
                                        m_state.m_symbols[context.last_symbol],
1✔
634
                                        types_to_str[static_cast<std::size_t>(var->valueType())],
1✔
635
                                        m_state.m_symbols[arg]));
1✔
636
                            else
637
                                throwVMError(ErrorKind::Type,
×
UNCOV
638
                                             fmt::format(
×
UNCOV
639
                                                 "{} is not a Closure, can not get the field `{}' from it",
×
UNCOV
640
                                                 types_to_str[static_cast<std::size_t>(var->valueType())],
×
UNCOV
641
                                                 m_state.m_symbols[arg]));
×
UNCOV
642
                        }
×
643

644
                        if (Value* field = var->refClosure().refScope()[arg]; field != nullptr)
1,569✔
645
                        {
646
                            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
647
                            if (m_state.m_pages[context.pp][context.ip] == CALL)
1,567✔
648
                                push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
710✔
649
                            else
650
                                push(field, context);
857✔
651
                        }
1,567✔
652
                        else
653
                        {
654
                            if (!var->refClosure().hasFieldEndingWith(m_state.m_symbols[arg], *this))
2✔
655
                                throwVMError(
1✔
656
                                    ErrorKind::Scope,
657
                                    fmt::format(
2✔
658
                                        "`{0}' isn't in the closure environment: {1}",
1✔
659
                                        m_state.m_symbols[arg],
1✔
660
                                        var->refClosure().toString(*this)));
1✔
661
                            throwVMError(
1✔
662
                                ErrorKind::Scope,
663
                                fmt::format(
2✔
664
                                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
1✔
665
                                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
666
                                    m_state.m_symbols[arg],
1✔
667
                                    var->refClosure().toString(*this)));
1✔
668
                        }
669
                        DISPATCH();
1,567✔
670
                    }
×
671

672
                    TARGET(PLUGIN)
673
                    {
UNCOV
674
                        loadPlugin(arg, context);
×
UNCOV
675
                        DISPATCH();
×
676
                    }
897✔
677

678
                    TARGET(LIST)
679
                    {
680
                        {
681
                            Value l(ValueType::List);
897✔
682
                            if (arg != 0)
897✔
683
                                l.list().reserve(arg);
484✔
684

685
                            for (uint16_t i = 0; i < arg; ++i)
2,165✔
686
                                l.push_back(*popAndResolveAsPtr(context));
1,268✔
687
                            push(std::move(l), context);
897✔
688
                        }
897✔
689
                        DISPATCH();
897✔
690
                    }
1,034✔
691

692
                    TARGET(APPEND)
693
                    {
694
                        {
695
                            Value* list = popAndResolveAsPtr(context);
1,034✔
696
                            if (list->valueType() != ValueType::List)
1,034✔
697
                            {
698
                                std::vector<Value> args = { *list };
1✔
699
                                for (uint16_t i = 0; i < arg; ++i)
2✔
700
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
701
                                throw types::TypeCheckingError(
2✔
702
                                    "append",
1✔
703
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
1✔
704
                                    args);
705
                            }
1✔
706

707
                            const auto size = static_cast<uint16_t>(list->constList().size());
1,033✔
708

709
                            Value obj { *list };
1,033✔
710
                            obj.list().reserve(size + arg);
1,033✔
711

712
                            for (uint16_t i = 0; i < arg; ++i)
2,066✔
713
                                obj.push_back(*popAndResolveAsPtr(context));
1,033✔
714
                            push(std::move(obj), context);
1,033✔
715
                        }
1,033✔
716
                        DISPATCH();
1,033✔
717
                    }
4✔
718

719
                    TARGET(CONCAT)
720
                    {
721
                        {
722
                            Value* list = popAndResolveAsPtr(context);
4✔
723
                            Value obj { *list };
4✔
724

725
                            for (uint16_t i = 0; i < arg; ++i)
6✔
726
                            {
727
                                Value* next = popAndResolveAsPtr(context);
4✔
728

729
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
4✔
730
                                    throw types::TypeCheckingError(
4✔
731
                                        "concat",
2✔
732
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
733
                                        { *list, *next });
2✔
734

735
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
2✔
736
                            }
2✔
737
                            push(std::move(obj), context);
2✔
738
                        }
4✔
739
                        DISPATCH();
2✔
740
                    }
1,290✔
741

742
                    TARGET(APPEND_IN_PLACE)
743
                    {
744
                        Value* list = popAndResolveAsPtr(context);
1,290✔
745

746
                        if (list->valueType() != ValueType::List)
1,290✔
747
                        {
748
                            std::vector<Value> args = { *list };
1✔
749
                            for (uint16_t i = 0; i < arg; ++i)
2✔
750
                                args.push_back(*popAndResolveAsPtr(context));
1✔
751
                            throw types::TypeCheckingError(
2✔
752
                                "append!",
1✔
753
                                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
1✔
754
                                args);
755
                        }
1✔
756

757
                        for (uint16_t i = 0; i < arg; ++i)
2,578✔
758
                            list->push_back(*popAndResolveAsPtr(context));
1,289✔
759
                        DISPATCH();
1,289✔
760
                    }
52✔
761

762
                    TARGET(CONCAT_IN_PLACE)
763
                    {
764
                        Value* list = popAndResolveAsPtr(context);
52✔
765

766
                        for (uint16_t i = 0; i < arg; ++i)
132✔
767
                        {
768
                            Value* next = popAndResolveAsPtr(context);
82✔
769

770
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
82✔
771
                                throw types::TypeCheckingError(
4✔
772
                                    "concat!",
2✔
773
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
774
                                    { *list, *next });
2✔
775

776
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
80✔
777
                        }
80✔
778
                        DISPATCH();
50✔
779
                    }
5✔
780

781
                    TARGET(POP_LIST)
782
                    {
783
                        {
784
                            Value list = *popAndResolveAsPtr(context);
5✔
785
                            Value number = *popAndResolveAsPtr(context);
5✔
786

787
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
5✔
788
                                throw types::TypeCheckingError(
2✔
789
                                    "pop",
1✔
790
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
791
                                    { list, number });
1✔
792

793
                            long idx = static_cast<long>(number.number());
4✔
794
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
795
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
796
                                throwVMError(
1✔
797
                                    ErrorKind::Index,
798
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
799

800
                            list.list().erase(list.list().begin() + idx);
3✔
801
                            push(list, context);
3✔
802
                        }
5✔
803
                        DISPATCH();
3✔
804
                    }
54✔
805

806
                    TARGET(POP_LIST_IN_PLACE)
807
                    {
808
                        {
809
                            Value* list = popAndResolveAsPtr(context);
54✔
810
                            Value number = *popAndResolveAsPtr(context);
54✔
811

812
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
54✔
813
                                throw types::TypeCheckingError(
2✔
814
                                    "pop!",
1✔
815
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
816
                                    { *list, number });
1✔
817

818
                            long idx = static_cast<long>(number.number());
53✔
819
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
53✔
820
                            if (std::cmp_greater_equal(idx, list->list().size()))
53✔
821
                                throwVMError(
1✔
822
                                    ErrorKind::Index,
823
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
1✔
824

825
                            list->list().erase(list->list().begin() + idx);
52✔
826
                        }
54✔
827
                        DISPATCH();
52✔
828
                    }
489✔
829

830
                    TARGET(SET_AT_INDEX)
831
                    {
832
                        {
833
                            Value* list = popAndResolveAsPtr(context);
489✔
834
                            Value number = *popAndResolveAsPtr(context);
489✔
835
                            Value new_value = *popAndResolveAsPtr(context);
489✔
836

837
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
489✔
838
                                throw types::TypeCheckingError(
2✔
839
                                    "@=",
1✔
840
                                    { { types::Contract {
3✔
841
                                          { types::Typedef("list", ValueType::List),
3✔
842
                                            types::Typedef("index", ValueType::Number),
1✔
843
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
844
                                      { types::Contract {
1✔
845
                                          { types::Typedef("string", ValueType::String),
3✔
846
                                            types::Typedef("index", ValueType::Number),
1✔
847
                                            types::Typedef("char", ValueType::String) } } } },
1✔
848
                                    { *list, number, new_value });
1✔
849

850
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
488✔
851
                            long idx = static_cast<long>(number.number());
488✔
852
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
488✔
853
                            if (std::cmp_greater_equal(idx, size))
488✔
854
                                throwVMError(
1✔
855
                                    ErrorKind::Index,
856
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
1✔
857

858
                            if (list->valueType() == ValueType::List)
487✔
859
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
860
                            else
861
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
862
                        }
489✔
863
                        DISPATCH();
487✔
864
                    }
10✔
865

866
                    TARGET(SET_AT_2_INDEX)
867
                    {
868
                        {
869
                            Value* list = popAndResolveAsPtr(context);
10✔
870
                            Value x = *popAndResolveAsPtr(context);
10✔
871
                            Value y = *popAndResolveAsPtr(context);
10✔
872
                            Value new_value = *popAndResolveAsPtr(context);
10✔
873

874
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
10✔
875
                                throw types::TypeCheckingError(
2✔
876
                                    "@@=",
1✔
877
                                    { { types::Contract {
2✔
878
                                        { types::Typedef("list", ValueType::List),
4✔
879
                                          types::Typedef("x", ValueType::Number),
1✔
880
                                          types::Typedef("y", ValueType::Number),
1✔
881
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
882
                                    { *list, x, y, new_value });
1✔
883

884
                            long idx_y = static_cast<long>(x.number());
9✔
885
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
9✔
886
                            if (std::cmp_greater_equal(idx_y, list->list().size()))
9✔
887
                                throwVMError(
1✔
888
                                    ErrorKind::Index,
889
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
1✔
890

891
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
12✔
892
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
7✔
893
                                throw types::TypeCheckingError(
2✔
894
                                    "@@=",
1✔
895
                                    { { types::Contract {
3✔
896
                                          { types::Typedef("list", ValueType::List),
4✔
897
                                            types::Typedef("x", ValueType::Number),
1✔
898
                                            types::Typedef("y", ValueType::Number),
1✔
899
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
900
                                      { types::Contract {
1✔
901
                                          { types::Typedef("string", ValueType::String),
4✔
902
                                            types::Typedef("x", ValueType::Number),
1✔
903
                                            types::Typedef("y", ValueType::Number),
1✔
904
                                            types::Typedef("char", ValueType::String) } } } },
1✔
905
                                    { *list, x, y, new_value });
1✔
906

907
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
7✔
908
                            const std::size_t size =
7✔
909
                                is_list
14✔
910
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
5✔
911
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
912

913
                            long idx_x = static_cast<long>(y.number());
7✔
914
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
7✔
915
                            if (std::cmp_greater_equal(idx_x, size))
7✔
916
                                throwVMError(
1✔
917
                                    ErrorKind::Index,
918
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
919

920
                            if (is_list)
6✔
921
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
922
                            else
923
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
924
                        }
10✔
925
                        DISPATCH();
6✔
926
                    }
9,199✔
927

928
                    TARGET(POP)
929
                    {
930
                        pop(context);
9,199✔
931
                        DISPATCH();
9,199✔
932
                    }
7,991✔
933

934
                    TARGET(DUP)
935
                    {
936
                        context.stack[context.sp] = context.stack[context.sp - 1];
7,991✔
937
                        ++context.sp;
7,991✔
938
                        DISPATCH();
7,991✔
939
                    }
1,784✔
940

941
                    TARGET(CREATE_SCOPE)
942
                    {
943
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
1,784✔
944
                        DISPATCH();
1,784✔
945
                    }
15,212✔
946

947
                    TARGET(RESET_SCOPE)
948
                    {
949
                        context.locals.back().reset();
15,212✔
950
                        DISPATCH();
15,212✔
951
                    }
1,784✔
952

953
                    TARGET(POP_SCOPE)
954
                    {
955
                        context.locals.pop_back();
1,784✔
956
                        DISPATCH();
1,784✔
957
                    }
25,203✔
958

959
#pragma endregion
960

961
#pragma region "Operators"
962

963
                    TARGET(ADD)
964
                    {
965
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
25,203✔
966

967
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
25,204✔
968
                            push(Value(a->number() + b->number()), context);
17,963✔
969
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
7,241✔
970
                            push(Value(a->string() + b->string()), context);
7,240✔
971
                        else
972
                            throw types::TypeCheckingError(
2✔
973
                                "+",
1✔
974
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
975
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
976
                                { *a, *b });
1✔
977
                        DISPATCH();
25,201✔
978
                    }
118✔
979

980
                    TARGET(SUB)
981
                    {
982
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
118✔
983

984
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
118✔
985
                            throw types::TypeCheckingError(
2✔
986
                                "-",
1✔
987
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
988
                                { *a, *b });
1✔
989
                        push(Value(a->number() - b->number()), context);
117✔
990
                        DISPATCH();
117✔
991
                    }
1,363✔
992

993
                    TARGET(MUL)
994
                    {
995
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,363✔
996

997
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,363✔
998
                            throw types::TypeCheckingError(
2✔
999
                                "*",
1✔
1000
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1001
                                { *a, *b });
1✔
1002
                        push(Value(a->number() * b->number()), context);
1,362✔
1003
                        DISPATCH();
1,362✔
1004
                    }
1,060✔
1005

1006
                    TARGET(DIV)
1007
                    {
1008
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,060✔
1009

1010
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,060✔
1011
                            throw types::TypeCheckingError(
2✔
1012
                                "/",
1✔
1013
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1014
                                { *a, *b });
1✔
1015
                        auto d = b->number();
1,059✔
1016
                        if (d == 0)
1,059✔
1017
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1018

1019
                        push(Value(a->number() / d), context);
1,058✔
1020
                        DISPATCH();
1,058✔
1021
                    }
172,655✔
1022

1023
                    TARGET(GT)
1024
                    {
1025
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
172,655✔
1026
                        push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
172,655✔
1027
                        DISPATCH();
172,655✔
1028
                    }
39,967✔
1029

1030
                    TARGET(LT)
1031
                    {
1032
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
39,967✔
1033
                        push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
39,967✔
1034
                        DISPATCH();
39,967✔
1035
                    }
7,179✔
1036

1037
                    TARGET(LE)
1038
                    {
1039
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,179✔
1040
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,179✔
1041
                        DISPATCH();
7,179✔
1042
                    }
5,730✔
1043

1044
                    TARGET(GE)
1045
                    {
1046
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,730✔
1047
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,730✔
1048
                        DISPATCH();
5,730✔
1049
                    }
630✔
1050

1051
                    TARGET(NEQ)
1052
                    {
1053
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
630✔
1054
                        push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
630✔
1055
                        DISPATCH();
630✔
1056
                    }
89,400✔
1057

1058
                    TARGET(EQ)
1059
                    {
1060
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
89,400✔
1061
                        push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
89,400✔
1062
                        DISPATCH();
89,400✔
1063
                    }
10,828✔
1064

1065
                    TARGET(LEN)
1066
                    {
1067
                        const Value* a = popAndResolveAsPtr(context);
10,828✔
1068

1069
                        if (a->valueType() == ValueType::List)
10,828✔
1070
                            push(Value(static_cast<int>(a->constList().size())), context);
3,202✔
1071
                        else if (a->valueType() == ValueType::String)
7,626✔
1072
                            push(Value(static_cast<int>(a->string().size())), context);
7,625✔
1073
                        else
1074
                            throw types::TypeCheckingError(
2✔
1075
                                "len",
1✔
1076
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1077
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1078
                                { *a });
1✔
1079
                        DISPATCH();
10,827✔
1080
                    }
540✔
1081

1082
                    TARGET(EMPTY)
1083
                    {
1084
                        const Value* a = popAndResolveAsPtr(context);
540✔
1085

1086
                        if (a->valueType() == ValueType::List)
540✔
1087
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
86✔
1088
                        else if (a->valueType() == ValueType::String)
454✔
1089
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
453✔
1090
                        else
1091
                            throw types::TypeCheckingError(
2✔
1092
                                "empty?",
1✔
1093
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1094
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1095
                                { *a });
1✔
1096
                        DISPATCH();
539✔
1097
                    }
335✔
1098

1099
                    TARGET(TAIL)
1100
                    {
1101
                        Value* const a = popAndResolveAsPtr(context);
335✔
1102
                        push(helper::tail(a), context);
336✔
1103
                        DISPATCH();
334✔
1104
                    }
1,097✔
1105

1106
                    TARGET(HEAD)
1107
                    {
1108
                        Value* const a = popAndResolveAsPtr(context);
1,097✔
1109
                        push(helper::head(a), context);
1,098✔
1110
                        DISPATCH();
1,096✔
1111
                    }
1,268✔
1112

1113
                    TARGET(ISNIL)
1114
                    {
1115
                        const Value* a = popAndResolveAsPtr(context);
1,268✔
1116
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
1,268✔
1117
                        DISPATCH();
1,268✔
1118
                    }
566✔
1119

1120
                    TARGET(ASSERT)
1121
                    {
1122
                        Value* const b = popAndResolveAsPtr(context);
566✔
1123
                        Value* const a = popAndResolveAsPtr(context);
566✔
1124

1125
                        if (b->valueType() != ValueType::String)
566✔
1126
                            throw types::TypeCheckingError(
2✔
1127
                                "assert",
1✔
1128
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1129
                                { *a, *b });
1✔
1130

1131
                        if (*a == Builtins::falseSym)
565✔
UNCOV
1132
                            throw AssertionFailed(b->stringRef());
×
1133
                        DISPATCH();
565✔
1134
                    }
14✔
1135

1136
                    TARGET(TO_NUM)
1137
                    {
1138
                        const Value* a = popAndResolveAsPtr(context);
14✔
1139

1140
                        if (a->valueType() != ValueType::String)
14✔
1141
                            throw types::TypeCheckingError(
2✔
1142
                                "toNumber",
1✔
1143
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1144
                                { *a });
1✔
1145

1146
                        double val;
1147
                        if (Utils::isDouble(a->string(), &val))
13✔
1148
                            push(Value(val), context);
10✔
1149
                        else
1150
                            push(Builtins::nil, context);
3✔
1151
                        DISPATCH();
13✔
1152
                    }
16✔
1153

1154
                    TARGET(TO_STR)
1155
                    {
1156
                        const Value* a = popAndResolveAsPtr(context);
16✔
1157
                        push(Value(a->toString(*this)), context);
16✔
1158
                        DISPATCH();
16✔
1159
                    }
14,564✔
1160

1161
                    TARGET(AT)
1162
                    {
1163
                        {
1164
                            const Value* b = popAndResolveAsPtr(context);
14,564✔
1165
                            Value& a = *popAndResolveAsPtr(context);
14,564✔
1166

1167
                            if (b->valueType() != ValueType::Number)
14,564✔
1168
                                throw types::TypeCheckingError(
2✔
1169
                                    "@",
1✔
1170
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
2✔
1171
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
1✔
1172
                                    { a, *b });
1✔
1173

1174
                            long idx = static_cast<long>(b->number());
14,563✔
1175

1176
                            if (a.valueType() == ValueType::List)
14,563✔
1177
                            {
1178
                                if (std::cmp_less(std::abs(idx), a.list().size()))
6,795✔
1179
                                    push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
6,794✔
1180
                                else
1181
                                    throwVMError(
1✔
1182
                                        ErrorKind::Index,
1183
                                        fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1✔
1184
                            }
6,794✔
1185
                            else if (a.valueType() == ValueType::String)
7,768✔
1186
                            {
1187
                                if (std::cmp_less(std::abs(idx), a.string().size()))
7,767✔
1188
                                    push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
7,766✔
1189
                                else
1190
                                    throwVMError(
1✔
1191
                                        ErrorKind::Index,
1192
                                        fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1✔
1193
                            }
7,766✔
1194
                            else
1195
                                throw types::TypeCheckingError(
2✔
1196
                                    "@",
1✔
1197
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
2✔
1198
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
1✔
1199
                                    { a, *b });
1✔
1200
                        }
1201
                        DISPATCH();
14,560✔
1202
                    }
16✔
1203

1204
                    TARGET(AT_AT)
1205
                    {
1206
                        {
1207
                            const Value* x = popAndResolveAsPtr(context);
16✔
1208
                            const Value* y = popAndResolveAsPtr(context);
16✔
1209
                            Value& list = *popAndResolveAsPtr(context);
16✔
1210

1211
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
16✔
1212
                                list.valueType() != ValueType::List)
15✔
1213
                                throw types::TypeCheckingError(
2✔
1214
                                    "@@",
1✔
1215
                                    { { types::Contract {
2✔
1216
                                        { types::Typedef("src", ValueType::List),
3✔
1217
                                          types::Typedef("y", ValueType::Number),
1✔
1218
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1219
                                    { list, *y, *x });
1✔
1220

1221
                            long idx_y = static_cast<long>(y->number());
15✔
1222
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
15✔
1223
                            if (std::cmp_greater_equal(idx_y, list.list().size()))
15✔
1224
                                throwVMError(
1✔
1225
                                    ErrorKind::Index,
1226
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1✔
1227

1228
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
14✔
1229
                            const std::size_t size =
14✔
1230
                                is_list
28✔
1231
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
7✔
1232
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1233

1234
                            long idx_x = static_cast<long>(x->number());
14✔
1235
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
14✔
1236
                            if (std::cmp_greater_equal(idx_x, size))
14✔
1237
                                throwVMError(
1✔
1238
                                    ErrorKind::Index,
1239
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
1240

1241
                            if (is_list)
13✔
1242
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
6✔
1243
                            else
1244
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1245
                        }
1246
                        DISPATCH();
13✔
1247
                    }
790✔
1248

1249
                    TARGET(MOD)
1250
                    {
1251
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
790✔
1252
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
790✔
1253
                            throw types::TypeCheckingError(
2✔
1254
                                "mod",
1✔
1255
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1256
                                { *a, *b });
1✔
1257
                        push(Value(std::fmod(a->number(), b->number())), context);
789✔
1258
                        DISPATCH();
789✔
1259
                    }
87✔
1260

1261
                    TARGET(TYPE)
1262
                    {
1263
                        const Value* a = popAndResolveAsPtr(context);
87✔
1264
                        if (a == &m_undefined_value) [[unlikely]]
87✔
1265
                            throw types::TypeCheckingError(
×
UNCOV
1266
                                "type",
×
UNCOV
1267
                                { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
×
UNCOV
1268
                                {});
×
1269

1270
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
87✔
1271
                        DISPATCH();
87✔
1272
                    }
3✔
1273

1274
                    TARGET(HASFIELD)
1275
                    {
1276
                        {
1277
                            Value* const field = popAndResolveAsPtr(context);
3✔
1278
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1279
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1280
                                throw types::TypeCheckingError(
2✔
1281
                                    "hasField",
1✔
1282
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1283
                                    { *closure, *field });
1✔
1284

1285
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1286
                            if (it == m_state.m_symbols.end())
2✔
1287
                            {
1288
                                push(Builtins::falseSym, context);
1✔
1289
                                DISPATCH();
1✔
1290
                            }
1291

1292
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1293
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1294
                        }
1295
                        DISPATCH();
1✔
1296
                    }
2,349✔
1297

1298
                    TARGET(NOT)
1299
                    {
1300
                        const Value* a = popAndResolveAsPtr(context);
2,349✔
1301
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
2,349✔
1302
                        DISPATCH();
2,349✔
1303
                    }
5,409✔
1304

1305
#pragma endregion
1306

1307
#pragma region "Super Instructions"
1308
                    TARGET(LOAD_CONST_LOAD_CONST)
1309
                    {
1310
                        UNPACK_ARGS();
5,409✔
1311
                        push(loadConstAsPtr(primary_arg), context);
5,409✔
1312
                        push(loadConstAsPtr(secondary_arg), context);
5,409✔
1313
                        DISPATCH();
5,409✔
1314
                    }
5,472✔
1315

1316
                    TARGET(LOAD_CONST_STORE)
1317
                    {
1318
                        UNPACK_ARGS();
5,472✔
1319
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
5,472✔
1320
                        DISPATCH();
5,472✔
1321
                    }
212✔
1322

1323
                    TARGET(LOAD_CONST_SET_VAL)
1324
                    {
1325
                        UNPACK_ARGS();
212✔
1326
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
212✔
1327
                        DISPATCH();
211✔
1328
                    }
15✔
1329

1330
                    TARGET(STORE_FROM)
1331
                    {
1332
                        UNPACK_ARGS();
15✔
1333
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
15✔
1334
                        DISPATCH();
14✔
1335
                    }
581✔
1336

1337
                    TARGET(STORE_FROM_INDEX)
1338
                    {
1339
                        UNPACK_ARGS();
581✔
1340
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
581✔
1341
                        DISPATCH();
581✔
1342
                    }
37✔
1343

1344
                    TARGET(SET_VAL_FROM)
1345
                    {
1346
                        UNPACK_ARGS();
37✔
1347
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
37✔
1348
                        DISPATCH();
37✔
1349
                    }
125✔
1350

1351
                    TARGET(SET_VAL_FROM_INDEX)
1352
                    {
1353
                        UNPACK_ARGS();
125✔
1354
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
125✔
1355
                        DISPATCH();
125✔
1356
                    }
15,043✔
1357

1358
                    TARGET(INCREMENT)
1359
                    {
1360
                        UNPACK_ARGS();
15,043✔
1361
                        {
1362
                            Value* var = loadSymbol(primary_arg, context);
15,043✔
1363

1364
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1365
                            if (var->valueType() == ValueType::Reference)
15,043✔
UNCOV
1366
                                var = var->reference();
×
1367

1368
                            if (var->valueType() == ValueType::Number)
15,043✔
1369
                                push(Value(var->number() + secondary_arg), context);
15,043✔
1370
                            else
1371
                                throw types::TypeCheckingError(
×
UNCOV
1372
                                    "+",
×
UNCOV
1373
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1374
                                    { *var, Value(secondary_arg) });
×
1375
                        }
1376
                        DISPATCH();
15,043✔
1377
                    }
87,965✔
1378

1379
                    TARGET(INCREMENT_BY_INDEX)
1380
                    {
1381
                        UNPACK_ARGS();
87,965✔
1382
                        {
1383
                            Value* var = loadSymbolFromIndex(primary_arg, context);
87,965✔
1384

1385
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1386
                            if (var->valueType() == ValueType::Reference)
87,965✔
UNCOV
1387
                                var = var->reference();
×
1388

1389
                            if (var->valueType() == ValueType::Number)
87,965✔
1390
                                push(Value(var->number() + secondary_arg), context);
87,964✔
1391
                            else
1392
                                throw types::TypeCheckingError(
2✔
1393
                                    "+",
1✔
1394
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1395
                                    { *var, Value(secondary_arg) });
1✔
1396
                        }
1397
                        DISPATCH();
87,964✔
1398
                    }
1,274✔
1399

1400
                    TARGET(DECREMENT)
1401
                    {
1402
                        UNPACK_ARGS();
1,274✔
1403
                        {
1404
                            Value* var = loadSymbol(primary_arg, context);
1,274✔
1405

1406
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1407
                            if (var->valueType() == ValueType::Reference)
1,274✔
UNCOV
1408
                                var = var->reference();
×
1409

1410
                            if (var->valueType() == ValueType::Number)
1,274✔
1411
                                push(Value(var->number() - secondary_arg), context);
1,274✔
1412
                            else
1413
                                throw types::TypeCheckingError(
×
UNCOV
1414
                                    "-",
×
UNCOV
1415
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1416
                                    { *var, Value(secondary_arg) });
×
1417
                        }
1418
                        DISPATCH();
1,274✔
1419
                    }
194,404✔
1420

1421
                    TARGET(DECREMENT_BY_INDEX)
1422
                    {
1423
                        UNPACK_ARGS();
194,404✔
1424
                        {
1425
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,404✔
1426

1427
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1428
                            if (var->valueType() == ValueType::Reference)
194,404✔
UNCOV
1429
                                var = var->reference();
×
1430

1431
                            if (var->valueType() == ValueType::Number)
194,404✔
1432
                                push(Value(var->number() - secondary_arg), context);
194,403✔
1433
                            else
1434
                                throw types::TypeCheckingError(
2✔
1435
                                    "-",
1✔
1436
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1437
                                    { *var, Value(secondary_arg) });
1✔
1438
                        }
1439
                        DISPATCH();
194,403✔
UNCOV
1440
                    }
×
1441

1442
                    TARGET(STORE_TAIL)
1443
                    {
1444
                        UNPACK_ARGS();
×
1445
                        {
1446
                            Value* list = loadSymbol(primary_arg, context);
×
1447
                            Value tail = helper::tail(list);
×
UNCOV
1448
                            store(secondary_arg, &tail, context);
×
UNCOV
1449
                        }
×
UNCOV
1450
                        DISPATCH();
×
1451
                    }
1✔
1452

1453
                    TARGET(STORE_TAIL_BY_INDEX)
1454
                    {
1455
                        UNPACK_ARGS();
1✔
1456
                        {
1457
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1458
                            Value tail = helper::tail(list);
1✔
1459
                            store(secondary_arg, &tail, context);
1✔
1460
                        }
1✔
1461
                        DISPATCH();
1✔
UNCOV
1462
                    }
×
1463

1464
                    TARGET(STORE_HEAD)
1465
                    {
1466
                        UNPACK_ARGS();
×
1467
                        {
1468
                            Value* list = loadSymbol(primary_arg, context);
×
1469
                            Value head = helper::head(list);
×
UNCOV
1470
                            store(secondary_arg, &head, context);
×
UNCOV
1471
                        }
×
UNCOV
1472
                        DISPATCH();
×
1473
                    }
31✔
1474

1475
                    TARGET(STORE_HEAD_BY_INDEX)
1476
                    {
1477
                        UNPACK_ARGS();
31✔
1478
                        {
1479
                            Value* list = loadSymbolFromIndex(primary_arg, context);
31✔
1480
                            Value head = helper::head(list);
31✔
1481
                            store(secondary_arg, &head, context);
31✔
1482
                        }
31✔
1483
                        DISPATCH();
31✔
UNCOV
1484
                    }
×
1485

1486
                    TARGET(SET_VAL_TAIL)
1487
                    {
1488
                        UNPACK_ARGS();
×
1489
                        {
1490
                            Value* list = loadSymbol(primary_arg, context);
×
1491
                            Value tail = helper::tail(list);
×
UNCOV
1492
                            setVal(secondary_arg, &tail, context);
×
UNCOV
1493
                        }
×
UNCOV
1494
                        DISPATCH();
×
1495
                    }
1✔
1496

1497
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1498
                    {
1499
                        UNPACK_ARGS();
1✔
1500
                        {
1501
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1502
                            Value tail = helper::tail(list);
1✔
1503
                            setVal(secondary_arg, &tail, context);
1✔
1504
                        }
1✔
1505
                        DISPATCH();
1✔
UNCOV
1506
                    }
×
1507

1508
                    TARGET(SET_VAL_HEAD)
1509
                    {
1510
                        UNPACK_ARGS();
×
1511
                        {
1512
                            Value* list = loadSymbol(primary_arg, context);
×
1513
                            Value head = helper::head(list);
×
UNCOV
1514
                            setVal(secondary_arg, &head, context);
×
UNCOV
1515
                        }
×
UNCOV
1516
                        DISPATCH();
×
1517
                    }
1✔
1518

1519
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1520
                    {
1521
                        UNPACK_ARGS();
1✔
1522
                        {
1523
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1524
                            Value head = helper::head(list);
1✔
1525
                            setVal(secondary_arg, &head, context);
1✔
1526
                        }
1✔
1527
                        DISPATCH();
1✔
1528
                    }
10,703✔
1529

1530
                    TARGET(CALL_BUILTIN)
1531
                    {
1532
                        UNPACK_ARGS();
10,703✔
1533
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1534
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
10,703✔
1535
                        if (!m_running)
10,651✔
UNCOV
1536
                            GOTO_HALT();
×
1537
                        DISPATCH();
10,651✔
1538
                    }
1539
#pragma endregion
1540
                }
60✔
1541
#if ARK_USE_COMPUTED_GOTOS
1542
            dispatch_end:
1543
                do
60✔
1544
                {
1545
                } while (false);
60✔
1546
#endif
1547
            }
1548
        }
162✔
1549
        catch (const Error& e)
1550
        {
1551
            if (fail_with_exception)
69✔
1552
            {
1553
                std::stringstream stream;
69✔
1554
                backtrace(context, stream, /* colorize= */ false);
69✔
1555
                // It's important we have an Ark::Error here, as the constructor for NestedError
1556
                // does more than just aggregate error messages, hence the code duplication.
1557
                throw NestedError(e, stream.str());
69✔
1558
            }
69✔
1559
            else
UNCOV
1560
                showBacktraceWithException(Error(e.details(/* colorize= */ true)), context);
×
1561
        }
129✔
1562
        catch (const std::exception& e)
1563
        {
1564
            if (fail_with_exception)
33✔
1565
            {
1566
                std::stringstream stream;
33✔
1567
                backtrace(context, stream, /* colorize= */ false);
33✔
1568
                throw NestedError(e, stream.str());
33✔
1569
            }
33✔
1570
            else
UNCOV
1571
                showBacktraceWithException(e, context);
×
1572
        }
102✔
1573
        catch (...)
1574
        {
UNCOV
1575
            if (fail_with_exception)
×
UNCOV
1576
                throw;
×
1577

1578
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1579
            throw;
1580
#endif
UNCOV
1581
            fmt::println("Unknown error");
×
UNCOV
1582
            backtrace(context);
×
UNCOV
1583
            m_exit_code = 1;
×
1584
        }
135✔
1585

1586
        return m_exit_code;
60✔
1587
    }
216✔
1588

1589
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,048✔
1590
    {
2,048✔
1591
        for (auto& local : std::ranges::reverse_view(context.locals))
4,096✔
1592
        {
1593
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,048✔
1594
                return id;
2,048✔
1595
        }
2,048✔
UNCOV
1596
        return std::numeric_limits<uint16_t>::max();
×
1597
    }
2,048✔
1598

1599
    void VM::throwVMError(ErrorKind kind, const std::string& message)
23✔
1600
    {
23✔
1601
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
23✔
1602
    }
23✔
1603

1604
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
3✔
1605
    {
3✔
1606
        std::vector<std::string> arg_names;
3✔
1607
        arg_names.reserve(expected_arg_count + 1);
3✔
1608
        if (expected_arg_count > 0)
3✔
1609
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
3✔
1610

1611
        std::size_t index = 0;
3✔
1612
        while (m_state.m_pages[context.pp][index] == STORE)
10✔
1613
        {
1614
            const auto id = static_cast<uint16_t>((m_state.m_pages[context.pp][index + 2] << 8) + m_state.m_pages[context.pp][index + 3]);
7✔
1615
            arg_names.push_back(m_state.m_symbols[id]);
7✔
1616
            index += 4;
7✔
1617
        }
7✔
1618

1619
        std::vector<std::string> arg_vals;
3✔
1620
        arg_vals.reserve(passed_arg_count + 1);
3✔
1621
        if (passed_arg_count > 0)
3✔
1622
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
3✔
1623

1624
        for (std::size_t i = 0; i < passed_arg_count && i + 1 <= context.sp; ++i)
11✔
1625
            // -1 on the stack because we always point to the next available slot
1626
            arg_vals.push_back(context.stack[context.sp - i - 1].toString(*this));
8✔
1627

1628
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
6✔
1629
            ? m_state.m_symbols[context.last_symbol]
3✔
UNCOV
1630
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
1631

1632
        throwVMError(
3✔
1633
            ErrorKind::Arity,
1634
            fmt::format(
6✔
1635
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
3✔
1636
                function_name,
1637
                fmt::join(arg_vals, " "),
3✔
1638
                passed_arg_count,
1639
                passed_arg_count > 1 ? "s" : "",
3✔
1640
                expected_arg_count,
1641
                function_name,
1642
                fmt::join(arg_names, " ")));
3✔
1643
    }
6✔
1644

UNCOV
1645
    void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
×
UNCOV
1646
    {
×
UNCOV
1647
        std::string text = e.what();
×
UNCOV
1648
        if (!text.empty() && text.back() != '\n')
×
UNCOV
1649
            text += '\n';
×
UNCOV
1650
        fmt::println("{}", text);
×
UNCOV
1651
        backtrace(context);
×
1652
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1653
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1654
        m_exit_code = 0;
1655
#else
UNCOV
1656
        m_exit_code = 1;
×
1657
#endif
UNCOV
1658
    }
×
1659

1660
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp)
2,153✔
1661
    {
2,153✔
1662
        std::optional<InstLoc> match = std::nullopt;
2,153✔
1663

1664
        for (const auto location : m_state.m_inst_locations)
8,447✔
1665
        {
1666
            if (location.page_pointer == pp && !match)
6,294✔
1667
                match = location;
2,153✔
1668

1669
            // select the best match: we want to find the location that's nearest our instruction pointer,
1670
            // but not equal to it as the IP will always be pointing to the next instruction,
1671
            // not yet executed. Thus, the erroneous instruction is the previous one.
1672
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
6,294✔
1673
                match = location;
2,180✔
1674

1675
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
1676
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
6,294✔
1677
                break;
12✔
1678
        }
6,294✔
1679

1680
        return match;
2,153✔
1681
    }
1682

1683
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
102✔
1684
    {
102✔
1685
        const std::size_t saved_ip = context.ip;
102✔
1686
        const std::size_t saved_pp = context.pp;
102✔
1687
        const uint16_t saved_sp = context.sp;
102✔
1688

1689
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
102✔
1690
        if (maybe_location)
102✔
1691
        {
1692
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
102✔
1693

1694
            fmt::println(os, "In file {}", filename, maybe_location->line + 1);
102✔
1695

1696
            if (Utils::fileExists(filename))
102✔
1697
                Diagnostics::makeContext(
204✔
1698
                    os,
102✔
1699
                    Utils::readFile(filename),
102✔
1700
                    maybe_location->line,
102✔
1701
                    /* col_start= */ 0,
1702
                    /* sym_size= */ 0,
1703
                    /* whole_line= */ true,
1704
                    /* colorize= */ colorize);
102✔
1705
            fmt::println(os, "");
102✔
1706
        }
102✔
1707

1708
        if (context.fc > 1)
102✔
1709
        {
1710
            // display call stack trace
1711
            const ScopeView old_scope = context.locals.back();
3✔
1712

1713
            std::string previous_trace;
3✔
1714
            std::size_t displayed_traces = 0;
3✔
1715
            std::size_t consecutive_similar_traces = 0;
3✔
1716

1717
            while (context.fc != 0)
2,051✔
1718
            {
1719
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,051✔
1720
                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,051✔
1721

1722
                if (context.pp != 0)
2,051✔
1723
                {
1724
                    const uint16_t id = findNearestVariableIdWithValue(
2,048✔
1725
                        Value(static_cast<PageAddr_t>(context.pp)),
2,048✔
1726
                        context);
2,048✔
1727
                    const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,048✔
1728

1729
                    if (func_name + loc_as_text != previous_trace)
2,048✔
1730
                    {
1731
                        fmt::println(
6✔
1732
                            os,
3✔
1733
                            "[{:4}] In function `{}'{}",
3✔
1734
                            fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
3✔
1735
                            fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
3✔
1736
                            loc_as_text);
1737
                        previous_trace = func_name + loc_as_text;
3✔
1738
                        ++displayed_traces;
3✔
1739
                        consecutive_similar_traces = 0;
3✔
1740
                    }
3✔
1741
                    else if (consecutive_similar_traces == 0)
2,045✔
1742
                    {
1743
                        fmt::println(os, "       ...");
1✔
1744
                        ++consecutive_similar_traces;
1✔
1745
                    }
1✔
1746

1747
                    const Value* ip;
2,048✔
1748
                    do
2,054✔
1749
                    {
1750
                        ip = popAndResolveAsPtr(context);
2,054✔
1751
                    } while (ip->valueType() != ValueType::InstPtr);
2,054✔
1752

1753
                    context.ip = ip->pageAddr();
2,048✔
1754
                    context.pp = pop(context)->pageAddr();
2,048✔
1755
                    returnFromFuncCall(context);
2,048✔
1756
                }
2,048✔
1757
                else
1758
                {
1759
                    fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
3✔
1760
                    break;
3✔
1761
                }
1762

1763
                if (displayed_traces > 7)
2,048✔
1764
                {
UNCOV
1765
                    fmt::println(os, "...");
×
UNCOV
1766
                    break;
×
1767
                }
1768
            }
2,051✔
1769

1770
            // display variables values in the current scope
1771
            fmt::println(os, "\nCurrent scope variables values:");
3✔
1772
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
7✔
1773
            {
1774
                fmt::println(
8✔
1775
                    os,
4✔
1776
                    "{} = {}",
4✔
1777
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
4✔
1778
                    old_scope.atPos(i).second.toString(*this));
4✔
1779
            }
4✔
1780
        }
3✔
1781

1782
        fmt::println(
204✔
1783
            os,
102✔
1784
            "At IP: {}, PP: {}, SP: {}",
102✔
1785
            // dividing by 4 because the instructions are actually on 4 bytes
1786
            fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
102✔
1787
            fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
102✔
1788
            fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
102✔
1789
    }
102✔
1790
}
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