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

ArkScript-lang / Ark / 16238190823

12 Jul 2025 12:51PM UTC coverage: 86.717% (+0.04%) from 86.675%
16238190823

push

github

SuperFola
feat(compiler, vm): new GET_CURRENT_PAGE_ADDRESS instruction and CALL_CURRENT_PAGE super instruction to avoid a local variable lookup when performing a recursive non-tail call

97 of 101 new or added lines in 6 files covered. (96.04%)

3 existing lines in 2 files now uncovered.

7338 of 8462 relevant lines covered (86.72%)

116314.34 hits per line

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

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

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

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

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

21
namespace Ark
22
{
23
    using namespace internal;
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

211
        for (uint16_t i = 0; i < count; ++i)
2,901✔
212
            l.push_back(*popAndResolveAsPtr(context));
1,403✔
213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

309
        m_shared_lib_objects.emplace_back(lib);
×
310

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

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

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

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

347
        m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
6✔
348
        ExecutionContext* ctx = m_execution_contexts.back().get();
6✔
349
        ctx->stacked_closure_scopes.emplace_back(nullptr);
6✔
350

351
        ctx->locals.reserve(m_execution_contexts.front()->locals.size());
6✔
352
        ctx->scopes_storage = m_execution_contexts.front()->scopes_storage;
6✔
353
        for (const auto& local : m_execution_contexts.front()->locals)
20✔
354
        {
355
            auto& scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), local.m_start);
14✔
356
            scope.m_size = local.m_size;
14✔
357
            scope.m_min_id = local.m_min_id;
14✔
358
            scope.m_max_id = local.m_max_id;
14✔
359
        }
14✔
360

361
        return ctx;
6✔
362
    }
6✔
363

364
    void VM::deleteContext(ExecutionContext* ec)
5✔
365
    {
5✔
366
        const std::lock_guard lock(m_mutex);
5✔
367

368
        const auto it =
5✔
369
            std::ranges::remove_if(
10✔
370
                m_execution_contexts,
5✔
371
                [ec](const std::unique_ptr<ExecutionContext>& ctx) {
21✔
372
                    return ctx.get() == ec;
16✔
373
                })
374
                .begin();
5✔
375
        m_execution_contexts.erase(it);
5✔
376
    }
5✔
377

378
    Future* VM::createFuture(std::vector<Value>& args)
6✔
379
    {
6✔
380
        ExecutionContext* ctx = createAndGetContext();
6✔
381
        // so that we have access to the presumed symbol id of the function we are calling
382
        // assuming that the callee is always the global context
383
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
6✔
384

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

390
        return m_futures.back().get();
6✔
391
    }
6✔
392

393
    void VM::deleteFuture(Future* f)
1✔
UNCOV
394
    {
×
395
        const std::lock_guard lock(m_mutex);
1✔
396

397
        const auto it =
1✔
398
            std::ranges::remove_if(
×
399
                m_futures,
×
400
                [f](const std::unique_ptr<Future>& future) {
×
401
                    return future.get() == f;
×
402
                })
403
                .begin();
×
404
        m_futures.erase(it);
×
405
    }
×
406

407
    bool VM::forceReloadPlugins() const
×
408
    {
×
409
        // load the mapping from the dynamic library
410
        try
411
        {
412
            for (const auto& shared_lib : m_shared_lib_objects)
×
413
            {
414
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
415
                // load the mapping data
416
                std::size_t i = 0;
×
417
                while (map[i].name != nullptr)
×
418
                {
419
                    // put it in the global frame, aka the first one
420
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
421
                    if (it != m_state.m_symbols.end())
×
422
                        m_execution_contexts[0]->locals[0].push_back(
×
423
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
424
                            Value(map[i].value));
×
425

426
                    ++i;
×
427
                }
×
428
            }
×
429

430
            return true;
×
431
        }
×
432
        catch (const std::system_error&)
433
        {
434
            return false;
×
435
        }
×
436
    }
×
437

438
    void VM::throwVMError(ErrorKind kind, const std::string& message)
32✔
439
    {
32✔
440
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
32✔
441
    }
32✔
442

443
    int VM::run(const bool fail_with_exception)
172✔
444
    {
172✔
445
        init();
172✔
446
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
172✔
447
        return m_exit_code;
172✔
448
    }
449

450
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
181✔
451
    {
181✔
452
#if ARK_USE_COMPUTED_GOTOS
453
#    define TARGET(op) TARGET_##op:
454
#    define DISPATCH_GOTO()            \
455
        _Pragma("GCC diagnostic push") \
456
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
457
        _Pragma("GCC diagnostic pop")
458
#    define GOTO_HALT() goto dispatch_end
459
#else
460
#    define TARGET(op) case op:
461
#    define DISPATCH_GOTO() goto dispatch_opcode
462
#    define GOTO_HALT() break
463
#endif
464

465
#define NEXTOPARG()                                                                   \
466
    do                                                                                \
467
    {                                                                                 \
468
        inst = m_state.inst(context.pp, context.ip);                                  \
469
        padding = m_state.inst(context.pp, context.ip + 1);                           \
470
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) + \
471
                                    m_state.inst(context.pp, context.ip + 3));        \
472
        context.ip += 4;                                                              \
473
    } while (false)
474
#define DISPATCH() \
475
    NEXTOPARG();   \
476
    DISPATCH_GOTO();
477
#define UNPACK_ARGS()                                                                 \
478
    do                                                                                \
479
    {                                                                                 \
480
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
481
        primary_arg = arg & 0x0fff;                                                   \
482
    } while (false)
483

484
#if ARK_USE_COMPUTED_GOTOS
485
#    pragma GCC diagnostic push
486
#    pragma GCC diagnostic ignored "-Wpedantic"
487
            constexpr std::array opcode_targets = {
181✔
488
                // cppcheck-suppress syntaxError ; cppcheck do not know about labels addresses (GCC extension)
489
                &&TARGET_NOP,
490
                &&TARGET_LOAD_SYMBOL,
491
                &&TARGET_LOAD_SYMBOL_BY_INDEX,
492
                &&TARGET_LOAD_CONST,
493
                &&TARGET_POP_JUMP_IF_TRUE,
494
                &&TARGET_STORE,
495
                &&TARGET_SET_VAL,
496
                &&TARGET_POP_JUMP_IF_FALSE,
497
                &&TARGET_JUMP,
498
                &&TARGET_RET,
499
                &&TARGET_HALT,
500
                &&TARGET_PUSH_RETURN_ADDRESS,
501
                &&TARGET_CALL,
502
                &&TARGET_CAPTURE,
503
                &&TARGET_BUILTIN,
504
                &&TARGET_DEL,
505
                &&TARGET_MAKE_CLOSURE,
506
                &&TARGET_GET_FIELD,
507
                &&TARGET_PLUGIN,
508
                &&TARGET_LIST,
509
                &&TARGET_APPEND,
510
                &&TARGET_CONCAT,
511
                &&TARGET_APPEND_IN_PLACE,
512
                &&TARGET_CONCAT_IN_PLACE,
513
                &&TARGET_POP_LIST,
514
                &&TARGET_POP_LIST_IN_PLACE,
515
                &&TARGET_SET_AT_INDEX,
516
                &&TARGET_SET_AT_2_INDEX,
517
                &&TARGET_POP,
518
                &&TARGET_SHORTCIRCUIT_AND,
519
                &&TARGET_SHORTCIRCUIT_OR,
520
                &&TARGET_CREATE_SCOPE,
521
                &&TARGET_RESET_SCOPE_JUMP,
522
                &&TARGET_POP_SCOPE,
523
                &&TARGET_GET_CURRENT_PAGE_ADDR,
524
                &&TARGET_ADD,
525
                &&TARGET_SUB,
526
                &&TARGET_MUL,
527
                &&TARGET_DIV,
528
                &&TARGET_GT,
529
                &&TARGET_LT,
530
                &&TARGET_LE,
531
                &&TARGET_GE,
532
                &&TARGET_NEQ,
533
                &&TARGET_EQ,
534
                &&TARGET_LEN,
535
                &&TARGET_EMPTY,
536
                &&TARGET_TAIL,
537
                &&TARGET_HEAD,
538
                &&TARGET_ISNIL,
539
                &&TARGET_ASSERT,
540
                &&TARGET_TO_NUM,
541
                &&TARGET_TO_STR,
542
                &&TARGET_AT,
543
                &&TARGET_AT_AT,
544
                &&TARGET_MOD,
545
                &&TARGET_TYPE,
546
                &&TARGET_HASFIELD,
547
                &&TARGET_NOT,
548
                &&TARGET_LOAD_CONST_LOAD_CONST,
549
                &&TARGET_LOAD_CONST_STORE,
550
                &&TARGET_LOAD_CONST_SET_VAL,
551
                &&TARGET_STORE_FROM,
552
                &&TARGET_STORE_FROM_INDEX,
553
                &&TARGET_SET_VAL_FROM,
554
                &&TARGET_SET_VAL_FROM_INDEX,
555
                &&TARGET_INCREMENT,
556
                &&TARGET_INCREMENT_BY_INDEX,
557
                &&TARGET_INCREMENT_STORE,
558
                &&TARGET_DECREMENT,
559
                &&TARGET_DECREMENT_BY_INDEX,
560
                &&TARGET_DECREMENT_STORE,
561
                &&TARGET_STORE_TAIL,
562
                &&TARGET_STORE_TAIL_BY_INDEX,
563
                &&TARGET_STORE_HEAD,
564
                &&TARGET_STORE_HEAD_BY_INDEX,
565
                &&TARGET_STORE_LIST,
566
                &&TARGET_SET_VAL_TAIL,
567
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
568
                &&TARGET_SET_VAL_HEAD,
569
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
570
                &&TARGET_CALL_BUILTIN,
571
                &&TARGET_CALL_BUILTIN_WITHOUT_RETURN_ADDRESS,
572
                &&TARGET_LT_CONST_JUMP_IF_FALSE,
573
                &&TARGET_LT_CONST_JUMP_IF_TRUE,
574
                &&TARGET_LT_SYM_JUMP_IF_FALSE,
575
                &&TARGET_GT_CONST_JUMP_IF_TRUE,
576
                &&TARGET_GT_CONST_JUMP_IF_FALSE,
577
                &&TARGET_GT_SYM_JUMP_IF_FALSE,
578
                &&TARGET_EQ_CONST_JUMP_IF_TRUE,
579
                &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
580
                &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
581
                &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
582
                &&TARGET_CALL_SYMBOL,
583
                &&TARGET_CALL_CURRENT_PAGE,
584
                &&TARGET_GET_FIELD_FROM_SYMBOL,
585
                &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
586
                &&TARGET_AT_SYM_SYM,
587
                &&TARGET_AT_SYM_INDEX_SYM_INDEX,
588
                &&TARGET_CHECK_TYPE_OF,
589
                &&TARGET_CHECK_TYPE_OF_BY_INDEX,
590
                &&TARGET_APPEND_IN_PLACE_SYM,
591
                &&TARGET_APPEND_IN_PLACE_SYM_INDEX
592
            };
593

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

598
        try
599
        {
600
            uint8_t inst = 0;
181✔
601
            uint8_t padding = 0;
181✔
602
            uint16_t arg = 0;
181✔
603
            uint16_t primary_arg = 0;
181✔
604
            uint16_t secondary_arg = 0;
181✔
605

606
            m_running = true;
181✔
607

608
            DISPATCH();
181✔
609
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
610
            {
611
#if !ARK_USE_COMPUTED_GOTOS
612
            dispatch_opcode:
613
                switch (inst)
614
#endif
615
                {
×
616
#pragma region "Instructions"
617
                    TARGET(NOP)
618
                    {
619
                        DISPATCH();
×
620
                    }
146,102✔
621

622
                    TARGET(LOAD_SYMBOL)
623
                    {
624
                        push(loadSymbol(arg, context), context);
146,102✔
625
                        DISPATCH();
146,100✔
626
                    }
333,316✔
627

628
                    TARGET(LOAD_SYMBOL_BY_INDEX)
629
                    {
630
                        push(loadSymbolFromIndex(arg, context), context);
333,316✔
631
                        DISPATCH();
333,316✔
632
                    }
115,791✔
633

634
                    TARGET(LOAD_CONST)
635
                    {
636
                        push(loadConstAsPtr(arg), context);
115,791✔
637
                        DISPATCH();
115,791✔
638
                    }
28,404✔
639

640
                    TARGET(POP_JUMP_IF_TRUE)
641
                    {
642
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
35,325✔
643
                            context.ip = arg * 4;  // instructions are 4 bytes
6,921✔
644
                        DISPATCH();
28,404✔
645
                    }
400,859✔
646

647
                    TARGET(STORE)
648
                    {
649
                        store(arg, popAndResolveAsPtr(context), context);
400,859✔
650
                        DISPATCH();
400,859✔
651
                    }
22,473✔
652

653
                    TARGET(SET_VAL)
654
                    {
655
                        setVal(arg, popAndResolveAsPtr(context), context);
22,473✔
656
                        DISPATCH();
22,473✔
657
                    }
27,352✔
658

659
                    TARGET(POP_JUMP_IF_FALSE)
660
                    {
661
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
29,411✔
662
                            context.ip = arg * 4;  // instructions are 4 bytes
2,059✔
663
                        DISPATCH();
27,352✔
664
                    }
206,649✔
665

666
                    TARGET(JUMP)
667
                    {
668
                        context.ip = arg * 4;  // instructions are 4 bytes
206,649✔
669
                        DISPATCH();
206,649✔
670
                    }
136,040✔
671

672
                    TARGET(RET)
673
                    {
674
                        {
675
                            Value ip_or_val = *popAndResolveAsPtr(context);
136,040✔
676
                            // no return value on the stack
677
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
136,040✔
678
                            {
679
                                context.ip = ip_or_val.pageAddr();
2,622✔
680
                                // we always push PP then IP, thus the next value
681
                                // MUST be the page pointer
682
                                context.pp = pop(context)->pageAddr();
2,622✔
683

684
                                returnFromFuncCall(context);
2,622✔
685
                                push(Builtins::nil, context);
2,622✔
686
                            }
2,622✔
687
                            // value on the stack
688
                            else [[likely]]
689
                            {
690
                                const Value* ip = popAndResolveAsPtr(context);
133,418✔
691
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
133,418✔
692
                                context.ip = ip->pageAddr();
133,418✔
693
                                context.pp = pop(context)->pageAddr();
133,418✔
694

695
                                returnFromFuncCall(context);
133,418✔
696
                                push(std::move(ip_or_val), context);
133,418✔
697
                            }
698

699
                            if (context.fc <= untilFrameCount)
136,040✔
700
                                GOTO_HALT();
7✔
701
                        }
136,040✔
702

703
                        DISPATCH();
136,033✔
704
                    }
62✔
705

706
                    TARGET(HALT)
707
                    {
708
                        m_running = false;
62✔
709
                        GOTO_HALT();
62✔
710
                    }
138,872✔
711

712
                    TARGET(PUSH_RETURN_ADDRESS)
713
                    {
714
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
138,872✔
715
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
716
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
138,872✔
717
                        DISPATCH();
138,872✔
718
                    }
2,442✔
719

720
                    TARGET(CALL)
721
                    {
722
                        call(context, arg);
2,442✔
723
                        if (!m_running)
2,435✔
724
                            GOTO_HALT();
×
725
                        DISPATCH();
2,435✔
726
                    }
3,079✔
727

728
                    TARGET(CAPTURE)
729
                    {
730
                        if (!context.saved_scope)
3,079✔
731
                            context.saved_scope = ClosureScope();
616✔
732

733
                        const Value* ptr = findNearestVariable(arg, context);
3,079✔
734
                        if (!ptr)
3,079✔
735
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
736
                        else
737
                        {
738
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,079✔
739
                            context.saved_scope.value().push_back(arg, *ptr);
3,079✔
740
                        }
741

742
                        DISPATCH();
3,079✔
743
                    }
1,472✔
744

745
                    TARGET(BUILTIN)
746
                    {
747
                        push(Builtins::builtins[arg].second, context);
1,472✔
748
                        DISPATCH();
1,472✔
749
                    }
1✔
750

751
                    TARGET(DEL)
752
                    {
753
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
754
                        {
755
                            if (var->valueType() == ValueType::User)
×
756
                                var->usertypeRef().del();
×
757
                            *var = Value();
×
758
                            DISPATCH();
×
759
                        }
760

761
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
762
                    }
616✔
763

764
                    TARGET(MAKE_CLOSURE)
765
                    {
766
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
616✔
767
                        context.saved_scope.reset();
616✔
768
                        DISPATCH();
616✔
769
                    }
6✔
770

771
                    TARGET(GET_FIELD)
772
                    {
773
                        Value* var = popAndResolveAsPtr(context);
6✔
774
                        push(getField(var, arg, context), context);
6✔
775
                        DISPATCH();
6✔
776
                    }
×
777

778
                    TARGET(PLUGIN)
779
                    {
780
                        loadPlugin(arg, context);
×
781
                        DISPATCH();
×
782
                    }
601✔
783

784
                    TARGET(LIST)
785
                    {
786
                        {
787
                            Value l = createList(arg, context);
601✔
788
                            push(std::move(l), context);
601✔
789
                        }
601✔
790
                        DISPATCH();
601✔
791
                    }
1,552✔
792

793
                    TARGET(APPEND)
794
                    {
795
                        {
796
                            Value* list = popAndResolveAsPtr(context);
1,552✔
797
                            if (list->valueType() != ValueType::List)
1,552✔
798
                            {
799
                                std::vector<Value> args = { *list };
1✔
800
                                for (uint16_t i = 0; i < arg; ++i)
2✔
801
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
802
                                throw types::TypeCheckingError(
2✔
803
                                    "append",
1✔
804
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
1✔
805
                                    args);
806
                            }
1✔
807

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

810
                            Value obj { *list };
1,551✔
811
                            obj.list().reserve(size + arg);
1,551✔
812

813
                            for (uint16_t i = 0; i < arg; ++i)
3,102✔
814
                                obj.push_back(*popAndResolveAsPtr(context));
1,551✔
815
                            push(std::move(obj), context);
1,551✔
816
                        }
1,551✔
817
                        DISPATCH();
1,551✔
818
                    }
12✔
819

820
                    TARGET(CONCAT)
821
                    {
822
                        {
823
                            Value* list = popAndResolveAsPtr(context);
12✔
824
                            Value obj { *list };
12✔
825

826
                            for (uint16_t i = 0; i < arg; ++i)
24✔
827
                            {
828
                                Value* next = popAndResolveAsPtr(context);
14✔
829

830
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
14✔
831
                                    throw types::TypeCheckingError(
4✔
832
                                        "concat",
2✔
833
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
834
                                        { *list, *next });
2✔
835

836
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
12✔
837
                            }
12✔
838
                            push(std::move(obj), context);
10✔
839
                        }
12✔
840
                        DISPATCH();
10✔
841
                    }
×
842

843
                    TARGET(APPEND_IN_PLACE)
844
                    {
845
                        Value* list = popAndResolveAsPtr(context);
×
846
                        listAppendInPlace(list, arg, context);
×
847
                        DISPATCH();
×
848
                    }
562✔
849

850
                    TARGET(CONCAT_IN_PLACE)
851
                    {
852
                        Value* list = popAndResolveAsPtr(context);
562✔
853

854
                        for (uint16_t i = 0; i < arg; ++i)
1,152✔
855
                        {
856
                            Value* next = popAndResolveAsPtr(context);
592✔
857

858
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
592✔
859
                                throw types::TypeCheckingError(
4✔
860
                                    "concat!",
2✔
861
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
862
                                    { *list, *next });
2✔
863

864
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
590✔
865
                        }
590✔
866
                        DISPATCH();
560✔
867
                    }
6✔
868

869
                    TARGET(POP_LIST)
870
                    {
871
                        {
872
                            Value list = *popAndResolveAsPtr(context);
6✔
873
                            Value number = *popAndResolveAsPtr(context);
6✔
874

875
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
6✔
876
                                throw types::TypeCheckingError(
2✔
877
                                    "pop",
1✔
878
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
879
                                    { list, number });
1✔
880

881
                            long idx = static_cast<long>(number.number());
5✔
882
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
5✔
883
                            if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
5✔
884
                                throwVMError(
2✔
885
                                    ErrorKind::Index,
886
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
887

888
                            list.list().erase(list.list().begin() + idx);
3✔
889
                            push(list, context);
3✔
890
                        }
6✔
891
                        DISPATCH();
3✔
892
                    }
63✔
893

894
                    TARGET(POP_LIST_IN_PLACE)
895
                    {
896
                        {
897
                            Value* list = popAndResolveAsPtr(context);
63✔
898
                            Value number = *popAndResolveAsPtr(context);
63✔
899

900
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
63✔
901
                                throw types::TypeCheckingError(
2✔
902
                                    "pop!",
1✔
903
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
904
                                    { *list, number });
1✔
905

906
                            long idx = static_cast<long>(number.number());
62✔
907
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
62✔
908
                            if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
62✔
909
                                throwVMError(
2✔
910
                                    ErrorKind::Index,
911
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
912

913
                            list->list().erase(list->list().begin() + idx);
60✔
914
                        }
63✔
915
                        DISPATCH();
60✔
916
                    }
490✔
917

918
                    TARGET(SET_AT_INDEX)
919
                    {
920
                        {
921
                            Value* list = popAndResolveAsPtr(context);
490✔
922
                            Value number = *popAndResolveAsPtr(context);
490✔
923
                            Value new_value = *popAndResolveAsPtr(context);
490✔
924

925
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
490✔
926
                                throw types::TypeCheckingError(
2✔
927
                                    "@=",
1✔
928
                                    { { types::Contract {
3✔
929
                                          { types::Typedef("list", ValueType::List),
3✔
930
                                            types::Typedef("index", ValueType::Number),
1✔
931
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
932
                                      { types::Contract {
1✔
933
                                          { types::Typedef("string", ValueType::String),
3✔
934
                                            types::Typedef("index", ValueType::Number),
1✔
935
                                            types::Typedef("char", ValueType::String) } } } },
1✔
936
                                    { *list, number, new_value });
1✔
937

938
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
489✔
939
                            long idx = static_cast<long>(number.number());
489✔
940
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
489✔
941
                            if (std::cmp_greater_equal(idx, size) || idx < 0)
489✔
942
                                throwVMError(
2✔
943
                                    ErrorKind::Index,
944
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
945

946
                            if (list->valueType() == ValueType::List)
487✔
947
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
948
                            else
949
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
950
                        }
490✔
951
                        DISPATCH();
487✔
952
                    }
12✔
953

954
                    TARGET(SET_AT_2_INDEX)
955
                    {
956
                        {
957
                            Value* list = popAndResolveAsPtr(context);
12✔
958
                            Value x = *popAndResolveAsPtr(context);
12✔
959
                            Value y = *popAndResolveAsPtr(context);
12✔
960
                            Value new_value = *popAndResolveAsPtr(context);
12✔
961

962
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
12✔
963
                                throw types::TypeCheckingError(
2✔
964
                                    "@@=",
1✔
965
                                    { { types::Contract {
2✔
966
                                        { types::Typedef("list", ValueType::List),
4✔
967
                                          types::Typedef("x", ValueType::Number),
1✔
968
                                          types::Typedef("y", ValueType::Number),
1✔
969
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
970
                                    { *list, x, y, new_value });
1✔
971

972
                            long idx_y = static_cast<long>(x.number());
11✔
973
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
11✔
974
                            if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
11✔
975
                                throwVMError(
2✔
976
                                    ErrorKind::Index,
977
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
978

979
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
13✔
980
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
8✔
981
                                throw types::TypeCheckingError(
2✔
982
                                    "@@=",
1✔
983
                                    { { types::Contract {
3✔
984
                                          { types::Typedef("list", ValueType::List),
4✔
985
                                            types::Typedef("x", ValueType::Number),
1✔
986
                                            types::Typedef("y", ValueType::Number),
1✔
987
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
988
                                      { types::Contract {
1✔
989
                                          { types::Typedef("string", ValueType::String),
4✔
990
                                            types::Typedef("x", ValueType::Number),
1✔
991
                                            types::Typedef("y", ValueType::Number),
1✔
992
                                            types::Typedef("char", ValueType::String) } } } },
1✔
993
                                    { *list, x, y, new_value });
1✔
994

995
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
996
                            const std::size_t size =
8✔
997
                                is_list
16✔
998
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
999
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1000

1001
                            long idx_x = static_cast<long>(y.number());
8✔
1002
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
8✔
1003
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
8✔
1004
                                throwVMError(
2✔
1005
                                    ErrorKind::Index,
1006
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1007

1008
                            if (is_list)
6✔
1009
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1010
                            else
1011
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1012
                        }
12✔
1013
                        DISPATCH();
6✔
1014
                    }
2,431✔
1015

1016
                    TARGET(POP)
1017
                    {
1018
                        pop(context);
2,431✔
1019
                        DISPATCH();
2,431✔
1020
                    }
23,413✔
1021

1022
                    TARGET(SHORTCIRCUIT_AND)
1023
                    {
1024
                        if (!*peekAndResolveAsPtr(context))
23,413✔
1025
                            context.ip = arg * 4;
733✔
1026
                        else
1027
                            pop(context);
22,680✔
1028
                        DISPATCH();
23,413✔
1029
                    }
788✔
1030

1031
                    TARGET(SHORTCIRCUIT_OR)
1032
                    {
1033
                        if (!!*peekAndResolveAsPtr(context))
788✔
1034
                            context.ip = arg * 4;
210✔
1035
                        else
1036
                            pop(context);
578✔
1037
                        DISPATCH();
788✔
1038
                    }
2,724✔
1039

1040
                    TARGET(CREATE_SCOPE)
1041
                    {
1042
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
2,724✔
1043
                        DISPATCH();
2,724✔
1044
                    }
31,056✔
1045

1046
                    TARGET(RESET_SCOPE_JUMP)
1047
                    {
1048
                        context.locals.back().reset();
31,056✔
1049
                        context.ip = arg * 4;  // instructions are 4 bytes
31,056✔
1050
                        DISPATCH();
31,056✔
1051
                    }
2,724✔
1052

1053
                    TARGET(POP_SCOPE)
1054
                    {
1055
                        context.locals.pop_back();
2,724✔
1056
                        DISPATCH();
2,724✔
UNCOV
1057
                    }
×
1058

1059
                    TARGET(GET_CURRENT_PAGE_ADDR)
1060
                    {
NEW
1061
                        context.last_symbol = arg;
×
NEW
1062
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
NEW
1063
                        DISPATCH();
×
1064
                    }
26,893✔
1065

1066
#pragma endregion
1067

1068
#pragma region "Operators"
1069

1070
                    TARGET(ADD)
1071
                    {
1072
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
26,893✔
1073

1074
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
26,893✔
1075
                            push(Value(a->number() + b->number()), context);
19,448✔
1076
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
7,445✔
1077
                            push(Value(a->string() + b->string()), context);
7,444✔
1078
                        else
1079
                            throw types::TypeCheckingError(
2✔
1080
                                "+",
1✔
1081
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1082
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1083
                                { *a, *b });
1✔
1084
                        DISPATCH();
26,892✔
1085
                    }
327✔
1086

1087
                    TARGET(SUB)
1088
                    {
1089
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
327✔
1090

1091
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
327✔
1092
                            throw types::TypeCheckingError(
2✔
1093
                                "-",
1✔
1094
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1095
                                { *a, *b });
1✔
1096
                        push(Value(a->number() - b->number()), context);
326✔
1097
                        DISPATCH();
326✔
1098
                    }
2,212✔
1099

1100
                    TARGET(MUL)
1101
                    {
1102
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,212✔
1103

1104
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
2,212✔
1105
                            throw types::TypeCheckingError(
2✔
1106
                                "*",
1✔
1107
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1108
                                { *a, *b });
1✔
1109
                        push(Value(a->number() * b->number()), context);
2,211✔
1110
                        DISPATCH();
2,211✔
1111
                    }
1,060✔
1112

1113
                    TARGET(DIV)
1114
                    {
1115
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,060✔
1116

1117
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,060✔
1118
                            throw types::TypeCheckingError(
2✔
1119
                                "/",
1✔
1120
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1121
                                { *a, *b });
1✔
1122
                        auto d = b->number();
1,059✔
1123
                        if (d == 0)
1,059✔
1124
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1125

1126
                        push(Value(a->number() / d), context);
1,058✔
1127
                        DISPATCH();
1,058✔
1128
                    }
152✔
1129

1130
                    TARGET(GT)
1131
                    {
1132
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
152✔
1133
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
152✔
1134
                        DISPATCH();
152✔
1135
                    }
28,717✔
1136

1137
                    TARGET(LT)
1138
                    {
1139
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,717✔
1140
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
28,717✔
1141
                        DISPATCH();
28,717✔
1142
                    }
7,192✔
1143

1144
                    TARGET(LE)
1145
                    {
1146
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,192✔
1147
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,192✔
1148
                        DISPATCH();
7,192✔
1149
                    }
5,730✔
1150

1151
                    TARGET(GE)
1152
                    {
1153
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,730✔
1154
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,730✔
1155
                        DISPATCH();
5,730✔
1156
                    }
735✔
1157

1158
                    TARGET(NEQ)
1159
                    {
1160
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
735✔
1161
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
735✔
1162
                        DISPATCH();
735✔
1163
                    }
17,421✔
1164

1165
                    TARGET(EQ)
1166
                    {
1167
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17,421✔
1168
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
17,421✔
1169
                        DISPATCH();
17,421✔
1170
                    }
12,099✔
1171

1172
                    TARGET(LEN)
1173
                    {
1174
                        const Value* a = popAndResolveAsPtr(context);
12,099✔
1175

1176
                        if (a->valueType() == ValueType::List)
12,099✔
1177
                            push(Value(static_cast<int>(a->constList().size())), context);
4,473✔
1178
                        else if (a->valueType() == ValueType::String)
7,626✔
1179
                            push(Value(static_cast<int>(a->string().size())), context);
7,625✔
1180
                        else
1181
                            throw types::TypeCheckingError(
2✔
1182
                                "len",
1✔
1183
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1184
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1185
                                { *a });
1✔
1186
                        DISPATCH();
12,098✔
1187
                    }
540✔
1188

1189
                    TARGET(EMPTY)
1190
                    {
1191
                        const Value* a = popAndResolveAsPtr(context);
540✔
1192

1193
                        if (a->valueType() == ValueType::List)
540✔
1194
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
86✔
1195
                        else if (a->valueType() == ValueType::String)
454✔
1196
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
453✔
1197
                        else
1198
                            throw types::TypeCheckingError(
2✔
1199
                                "empty?",
1✔
1200
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1201
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1202
                                { *a });
1✔
1203
                        DISPATCH();
539✔
1204
                    }
335✔
1205

1206
                    TARGET(TAIL)
1207
                    {
1208
                        Value* const a = popAndResolveAsPtr(context);
335✔
1209
                        push(helper::tail(a), context);
336✔
1210
                        DISPATCH();
334✔
1211
                    }
1,099✔
1212

1213
                    TARGET(HEAD)
1214
                    {
1215
                        Value* const a = popAndResolveAsPtr(context);
1,099✔
1216
                        push(helper::head(a), context);
1,100✔
1217
                        DISPATCH();
1,098✔
1218
                    }
2,299✔
1219

1220
                    TARGET(ISNIL)
1221
                    {
1222
                        const Value* a = popAndResolveAsPtr(context);
2,299✔
1223
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,299✔
1224
                        DISPATCH();
2,299✔
1225
                    }
595✔
1226

1227
                    TARGET(ASSERT)
1228
                    {
1229
                        Value* const b = popAndResolveAsPtr(context);
595✔
1230
                        Value* const a = popAndResolveAsPtr(context);
595✔
1231

1232
                        if (b->valueType() != ValueType::String)
595✔
1233
                            throw types::TypeCheckingError(
2✔
1234
                                "assert",
1✔
1235
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1236
                                { *a, *b });
1✔
1237

1238
                        if (*a == Builtins::falseSym)
594✔
1239
                            throw AssertionFailed(b->stringRef());
×
1240
                        DISPATCH();
594✔
1241
                    }
12✔
1242

1243
                    TARGET(TO_NUM)
1244
                    {
1245
                        const Value* a = popAndResolveAsPtr(context);
12✔
1246

1247
                        if (a->valueType() != ValueType::String)
12✔
1248
                            throw types::TypeCheckingError(
2✔
1249
                                "toNumber",
1✔
1250
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1251
                                { *a });
1✔
1252

1253
                        double val;
1254
                        if (Utils::isDouble(a->string(), &val))
11✔
1255
                            push(Value(val), context);
8✔
1256
                        else
1257
                            push(Builtins::nil, context);
3✔
1258
                        DISPATCH();
11✔
1259
                    }
116✔
1260

1261
                    TARGET(TO_STR)
1262
                    {
1263
                        const Value* a = popAndResolveAsPtr(context);
116✔
1264
                        push(Value(a->toString(*this)), context);
116✔
1265
                        DISPATCH();
116✔
1266
                    }
1,041✔
1267

1268
                    TARGET(AT)
1269
                    {
1270
                        Value& b = *popAndResolveAsPtr(context);
1,041✔
1271
                        Value& a = *popAndResolveAsPtr(context);
1,041✔
1272
                        push(helper::at(a, b, *this), context);
1,045✔
1273
                        DISPATCH();
1,037✔
1274
                    }
26✔
1275

1276
                    TARGET(AT_AT)
1277
                    {
1278
                        {
1279
                            const Value* x = popAndResolveAsPtr(context);
26✔
1280
                            const Value* y = popAndResolveAsPtr(context);
26✔
1281
                            Value& list = *popAndResolveAsPtr(context);
26✔
1282

1283
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
26✔
1284
                                list.valueType() != ValueType::List)
25✔
1285
                                throw types::TypeCheckingError(
2✔
1286
                                    "@@",
1✔
1287
                                    { { types::Contract {
2✔
1288
                                        { types::Typedef("src", ValueType::List),
3✔
1289
                                          types::Typedef("y", ValueType::Number),
1✔
1290
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1291
                                    { list, *y, *x });
1✔
1292

1293
                            long idx_y = static_cast<long>(y->number());
25✔
1294
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
25✔
1295
                            if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
25✔
1296
                                throwVMError(
2✔
1297
                                    ErrorKind::Index,
1298
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
2✔
1299

1300
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
23✔
1301
                            const std::size_t size =
23✔
1302
                                is_list
46✔
1303
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
16✔
1304
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1305

1306
                            long idx_x = static_cast<long>(x->number());
23✔
1307
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
23✔
1308
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
23✔
1309
                                throwVMError(
2✔
1310
                                    ErrorKind::Index,
1311
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1312

1313
                            if (is_list)
21✔
1314
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
14✔
1315
                            else
1316
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1317
                        }
1318
                        DISPATCH();
21✔
1319
                    }
16,351✔
1320

1321
                    TARGET(MOD)
1322
                    {
1323
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
16,351✔
1324
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
16,351✔
1325
                            throw types::TypeCheckingError(
2✔
1326
                                "mod",
1✔
1327
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1328
                                { *a, *b });
1✔
1329
                        push(Value(std::fmod(a->number(), b->number())), context);
16,350✔
1330
                        DISPATCH();
16,350✔
1331
                    }
22✔
1332

1333
                    TARGET(TYPE)
1334
                    {
1335
                        const Value* a = popAndResolveAsPtr(context);
22✔
1336
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
22✔
1337
                        DISPATCH();
22✔
1338
                    }
3✔
1339

1340
                    TARGET(HASFIELD)
1341
                    {
1342
                        {
1343
                            Value* const field = popAndResolveAsPtr(context);
3✔
1344
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1345
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1346
                                throw types::TypeCheckingError(
2✔
1347
                                    "hasField",
1✔
1348
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1349
                                    { *closure, *field });
1✔
1350

1351
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1352
                            if (it == m_state.m_symbols.end())
2✔
1353
                            {
1354
                                push(Builtins::falseSym, context);
1✔
1355
                                DISPATCH();
1✔
1356
                            }
1357

1358
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1359
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1360
                        }
1361
                        DISPATCH();
1✔
1362
                    }
3,562✔
1363

1364
                    TARGET(NOT)
1365
                    {
1366
                        const Value* a = popAndResolveAsPtr(context);
3,562✔
1367
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,562✔
1368
                        DISPATCH();
3,562✔
1369
                    }
5,968✔
1370

1371
#pragma endregion
1372

1373
#pragma region "Super Instructions"
1374
                    TARGET(LOAD_CONST_LOAD_CONST)
1375
                    {
1376
                        UNPACK_ARGS();
5,968✔
1377
                        push(loadConstAsPtr(primary_arg), context);
5,968✔
1378
                        push(loadConstAsPtr(secondary_arg), context);
5,968✔
1379
                        DISPATCH();
5,968✔
1380
                    }
7,808✔
1381

1382
                    TARGET(LOAD_CONST_STORE)
1383
                    {
1384
                        UNPACK_ARGS();
7,808✔
1385
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
7,808✔
1386
                        DISPATCH();
7,808✔
1387
                    }
220✔
1388

1389
                    TARGET(LOAD_CONST_SET_VAL)
1390
                    {
1391
                        UNPACK_ARGS();
220✔
1392
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
220✔
1393
                        DISPATCH();
219✔
1394
                    }
19✔
1395

1396
                    TARGET(STORE_FROM)
1397
                    {
1398
                        UNPACK_ARGS();
19✔
1399
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
19✔
1400
                        DISPATCH();
18✔
1401
                    }
1,096✔
1402

1403
                    TARGET(STORE_FROM_INDEX)
1404
                    {
1405
                        UNPACK_ARGS();
1,096✔
1406
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,096✔
1407
                        DISPATCH();
1,096✔
1408
                    }
547✔
1409

1410
                    TARGET(SET_VAL_FROM)
1411
                    {
1412
                        UNPACK_ARGS();
547✔
1413
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
547✔
1414
                        DISPATCH();
547✔
1415
                    }
179✔
1416

1417
                    TARGET(SET_VAL_FROM_INDEX)
1418
                    {
1419
                        UNPACK_ARGS();
179✔
1420
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
179✔
1421
                        DISPATCH();
179✔
1422
                    }
16✔
1423

1424
                    TARGET(INCREMENT)
1425
                    {
1426
                        UNPACK_ARGS();
16✔
1427
                        {
1428
                            Value* var = loadSymbol(primary_arg, context);
16✔
1429

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

1434
                            if (var->valueType() == ValueType::Number)
16✔
1435
                                push(Value(var->number() + secondary_arg), context);
16✔
1436
                            else
1437
                                throw types::TypeCheckingError(
×
1438
                                    "+",
×
1439
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1440
                                    { *var, Value(secondary_arg) });
×
1441
                        }
1442
                        DISPATCH();
16✔
1443
                    }
87,973✔
1444

1445
                    TARGET(INCREMENT_BY_INDEX)
1446
                    {
1447
                        UNPACK_ARGS();
87,973✔
1448
                        {
1449
                            Value* var = loadSymbolFromIndex(primary_arg, context);
87,973✔
1450

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

1455
                            if (var->valueType() == ValueType::Number)
87,973✔
1456
                                push(Value(var->number() + secondary_arg), context);
87,972✔
1457
                            else
1458
                                throw types::TypeCheckingError(
2✔
1459
                                    "+",
1✔
1460
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1461
                                    { *var, Value(secondary_arg) });
1✔
1462
                        }
1463
                        DISPATCH();
87,972✔
1464
                    }
30,960✔
1465

1466
                    TARGET(INCREMENT_STORE)
1467
                    {
1468
                        UNPACK_ARGS();
30,960✔
1469
                        {
1470
                            Value* var = loadSymbol(primary_arg, context);
30,960✔
1471

1472
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1473
                            if (var->valueType() == ValueType::Reference)
30,960✔
1474
                                var = var->reference();
×
1475

1476
                            if (var->valueType() == ValueType::Number)
30,960✔
1477
                            {
1478
                                Value val = Value(var->number() + secondary_arg);
30,960✔
1479
                                setVal(primary_arg, &val, context);
30,960✔
1480
                            }
30,960✔
1481
                            else
1482
                                throw types::TypeCheckingError(
×
1483
                                    "+",
×
1484
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1485
                                    { *var, Value(secondary_arg) });
×
1486
                        }
1487
                        DISPATCH();
30,960✔
1488
                    }
1,776✔
1489

1490
                    TARGET(DECREMENT)
1491
                    {
1492
                        UNPACK_ARGS();
1,776✔
1493
                        {
1494
                            Value* var = loadSymbol(primary_arg, context);
1,776✔
1495

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

1500
                            if (var->valueType() == ValueType::Number)
1,776✔
1501
                                push(Value(var->number() - secondary_arg), context);
1,776✔
1502
                            else
1503
                                throw types::TypeCheckingError(
×
1504
                                    "-",
×
1505
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1506
                                    { *var, Value(secondary_arg) });
×
1507
                        }
1508
                        DISPATCH();
1,776✔
1509
                    }
194,408✔
1510

1511
                    TARGET(DECREMENT_BY_INDEX)
1512
                    {
1513
                        UNPACK_ARGS();
194,408✔
1514
                        {
1515
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,408✔
1516

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

1521
                            if (var->valueType() == ValueType::Number)
194,408✔
1522
                                push(Value(var->number() - secondary_arg), context);
194,407✔
1523
                            else
1524
                                throw types::TypeCheckingError(
2✔
1525
                                    "-",
1✔
1526
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1527
                                    { *var, Value(secondary_arg) });
1✔
1528
                        }
1529
                        DISPATCH();
194,407✔
1530
                    }
×
1531

1532
                    TARGET(DECREMENT_STORE)
1533
                    {
1534
                        UNPACK_ARGS();
×
1535
                        {
1536
                            Value* var = loadSymbol(primary_arg, context);
×
1537

1538
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1539
                            if (var->valueType() == ValueType::Reference)
×
1540
                                var = var->reference();
×
1541

1542
                            if (var->valueType() == ValueType::Number)
×
1543
                            {
1544
                                Value val = Value(var->number() - secondary_arg);
×
1545
                                setVal(primary_arg, &val, context);
×
1546
                            }
×
1547
                            else
1548
                                throw types::TypeCheckingError(
×
1549
                                    "-",
×
1550
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1551
                                    { *var, Value(secondary_arg) });
×
1552
                        }
1553
                        DISPATCH();
×
1554
                    }
×
1555

1556
                    TARGET(STORE_TAIL)
1557
                    {
1558
                        UNPACK_ARGS();
×
1559
                        {
1560
                            Value* list = loadSymbol(primary_arg, context);
×
1561
                            Value tail = helper::tail(list);
×
1562
                            store(secondary_arg, &tail, context);
×
1563
                        }
×
1564
                        DISPATCH();
×
1565
                    }
1✔
1566

1567
                    TARGET(STORE_TAIL_BY_INDEX)
1568
                    {
1569
                        UNPACK_ARGS();
1✔
1570
                        {
1571
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1572
                            Value tail = helper::tail(list);
1✔
1573
                            store(secondary_arg, &tail, context);
1✔
1574
                        }
1✔
1575
                        DISPATCH();
1✔
1576
                    }
×
1577

1578
                    TARGET(STORE_HEAD)
1579
                    {
1580
                        UNPACK_ARGS();
×
1581
                        {
1582
                            Value* list = loadSymbol(primary_arg, context);
×
1583
                            Value head = helper::head(list);
×
1584
                            store(secondary_arg, &head, context);
×
1585
                        }
×
1586
                        DISPATCH();
×
1587
                    }
31✔
1588

1589
                    TARGET(STORE_HEAD_BY_INDEX)
1590
                    {
1591
                        UNPACK_ARGS();
31✔
1592
                        {
1593
                            Value* list = loadSymbolFromIndex(primary_arg, context);
31✔
1594
                            Value head = helper::head(list);
31✔
1595
                            store(secondary_arg, &head, context);
31✔
1596
                        }
31✔
1597
                        DISPATCH();
31✔
1598
                    }
897✔
1599

1600
                    TARGET(STORE_LIST)
1601
                    {
1602
                        UNPACK_ARGS();
897✔
1603
                        {
1604
                            Value l = createList(primary_arg, context);
897✔
1605
                            store(secondary_arg, &l, context);
897✔
1606
                        }
897✔
1607
                        DISPATCH();
897✔
1608
                    }
×
1609

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

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

1632
                    TARGET(SET_VAL_HEAD)
1633
                    {
1634
                        UNPACK_ARGS();
×
1635
                        {
1636
                            Value* list = loadSymbol(primary_arg, context);
×
1637
                            Value head = helper::head(list);
×
1638
                            setVal(secondary_arg, &head, context);
×
1639
                        }
×
1640
                        DISPATCH();
×
1641
                    }
1✔
1642

1643
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1644
                    {
1645
                        UNPACK_ARGS();
1✔
1646
                        {
1647
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1648
                            Value head = helper::head(list);
1✔
1649
                            setVal(secondary_arg, &head, context);
1✔
1650
                        }
1✔
1651
                        DISPATCH();
1✔
1652
                    }
776✔
1653

1654
                    TARGET(CALL_BUILTIN)
1655
                    {
1656
                        UNPACK_ARGS();
776✔
1657
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1658
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
776✔
1659
                        if (!m_running)
725✔
1660
                            GOTO_HALT();
×
1661
                        DISPATCH();
725✔
1662
                    }
11,045✔
1663

1664
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1665
                    {
1666
                        UNPACK_ARGS();
11,045✔
1667
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1668
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,045✔
1669
                        if (!m_running)
11,045✔
1670
                            GOTO_HALT();
×
1671
                        DISPATCH();
11,045✔
1672
                    }
857✔
1673

1674
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1675
                    {
1676
                        UNPACK_ARGS();
857✔
1677
                        const Value* sym = popAndResolveAsPtr(context);
857✔
1678
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
857✔
1679
                            context.ip = secondary_arg * 4;
122✔
1680
                        DISPATCH();
857✔
1681
                    }
21,902✔
1682

1683
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1684
                    {
1685
                        UNPACK_ARGS();
21,902✔
1686
                        const Value* sym = popAndResolveAsPtr(context);
21,902✔
1687
                        if (*sym < *loadConstAsPtr(primary_arg))
21,902✔
1688
                            context.ip = secondary_arg * 4;
10,950✔
1689
                        DISPATCH();
21,902✔
1690
                    }
5,541✔
1691

1692
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1693
                    {
1694
                        UNPACK_ARGS();
5,541✔
1695
                        const Value* sym = popAndResolveAsPtr(context);
5,541✔
1696
                        if (!(*sym < *loadSymbol(primary_arg, context)))
5,541✔
1697
                            context.ip = secondary_arg * 4;
535✔
1698
                        DISPATCH();
5,541✔
1699
                    }
172,498✔
1700

1701
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1702
                    {
1703
                        UNPACK_ARGS();
172,498✔
1704
                        const Value* sym = popAndResolveAsPtr(context);
172,498✔
1705
                        const Value* cst = loadConstAsPtr(primary_arg);
172,498✔
1706
                        if (*cst < *sym)
172,498✔
1707
                            context.ip = secondary_arg * 4;
86,585✔
1708
                        DISPATCH();
172,498✔
1709
                    }
15✔
1710

1711
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1712
                    {
1713
                        UNPACK_ARGS();
15✔
1714
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1715
                        const Value* cst = loadConstAsPtr(primary_arg);
15✔
1716
                        if (!(*cst < *sym))
15✔
1717
                            context.ip = secondary_arg * 4;
3✔
1718
                        DISPATCH();
15✔
1719
                    }
×
1720

1721
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1722
                    {
1723
                        UNPACK_ARGS();
×
1724
                        const Value* sym = popAndResolveAsPtr(context);
×
1725
                        const Value* rhs = loadSymbol(primary_arg, context);
×
1726
                        if (!(*rhs < *sym))
×
1727
                            context.ip = secondary_arg * 4;
×
1728
                        DISPATCH();
×
1729
                    }
1,182✔
1730

1731
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1732
                    {
1733
                        UNPACK_ARGS();
1,182✔
1734
                        const Value* sym = popAndResolveAsPtr(context);
1,182✔
1735
                        if (*sym == *loadConstAsPtr(primary_arg))
1,182✔
1736
                            context.ip = secondary_arg * 4;
195✔
1737
                        DISPATCH();
1,182✔
1738
                    }
86,933✔
1739

1740
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1741
                    {
1742
                        UNPACK_ARGS();
86,933✔
1743
                        const Value* sym = popAndResolveAsPtr(context);
86,933✔
1744
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
86,933✔
1745
                            context.ip = secondary_arg * 4;
529✔
1746
                        DISPATCH();
86,933✔
1747
                    }
1✔
1748

1749
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1750
                    {
1751
                        UNPACK_ARGS();
1✔
1752
                        const Value* sym = popAndResolveAsPtr(context);
1✔
1753
                        if (*sym != *loadConstAsPtr(primary_arg))
1✔
1754
                            context.ip = secondary_arg * 4;
1✔
1755
                        DISPATCH();
1✔
1756
                    }
15✔
1757

1758
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1759
                    {
1760
                        UNPACK_ARGS();
15✔
1761
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1762
                        if (*sym == *loadSymbol(primary_arg, context))
15✔
1763
                            context.ip = secondary_arg * 4;
5✔
1764
                        DISPATCH();
15✔
1765
                    }
25,790✔
1766

1767
                    TARGET(CALL_SYMBOL)
1768
                    {
1769
                        UNPACK_ARGS();
25,790✔
1770
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
25,790✔
1771
                        if (!m_running)
25,790✔
1772
                            GOTO_HALT();
×
1773
                        DISPATCH();
25,790✔
1774
                    }
109,861✔
1775

1776
                    TARGET(CALL_CURRENT_PAGE)
1777
                    {
1778
                        UNPACK_ARGS();
109,861✔
1779
                        context.last_symbol = primary_arg;
109,861✔
1780
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,861✔
1781
                        if (!m_running)
109,860✔
NEW
1782
                            GOTO_HALT();
×
1783
                        DISPATCH();
109,860✔
1784
                    }
1,637✔
1785

1786
                    TARGET(GET_FIELD_FROM_SYMBOL)
1787
                    {
1788
                        UNPACK_ARGS();
1,637✔
1789
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1,637✔
1790
                        DISPATCH();
1,637✔
1791
                    }
714✔
1792

1793
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1794
                    {
1795
                        UNPACK_ARGS();
714✔
1796
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
717✔
1797
                        DISPATCH();
711✔
1798
                    }
14,372✔
1799

1800
                    TARGET(AT_SYM_SYM)
1801
                    {
1802
                        UNPACK_ARGS();
14,372✔
1803
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
14,372✔
1804
                        DISPATCH();
14,372✔
1805
                    }
×
1806

1807
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1808
                    {
1809
                        UNPACK_ARGS();
×
1810
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
×
1811
                        DISPATCH();
×
1812
                    }
6✔
1813

1814
                    TARGET(CHECK_TYPE_OF)
1815
                    {
1816
                        UNPACK_ARGS();
6✔
1817
                        const Value* sym = loadSymbol(primary_arg, context);
6✔
1818
                        const Value* cst = loadConstAsPtr(secondary_arg);
6✔
1819
                        push(
6✔
1820
                            cst->valueType() == ValueType::String &&
12✔
1821
                                    types_to_str[static_cast<unsigned>(sym->valueType())] == cst->string()
6✔
1822
                                ? Builtins::trueSym
1823
                                : Builtins::falseSym,
1824
                            context);
6✔
1825
                        DISPATCH();
6✔
1826
                    }
68✔
1827

1828
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1829
                    {
1830
                        UNPACK_ARGS();
68✔
1831
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
68✔
1832
                        const Value* cst = loadConstAsPtr(secondary_arg);
68✔
1833
                        push(
68✔
1834
                            cst->valueType() == ValueType::String &&
136✔
1835
                                    types_to_str[static_cast<unsigned>(sym->valueType())] == cst->string()
68✔
1836
                                ? Builtins::trueSym
1837
                                : Builtins::falseSym,
1838
                            context);
68✔
1839
                        DISPATCH();
68✔
1840
                    }
1,424✔
1841

1842
                    TARGET(APPEND_IN_PLACE_SYM)
1843
                    {
1844
                        UNPACK_ARGS();
1,424✔
1845
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1,424✔
1846
                        DISPATCH();
1,424✔
1847
                    }
12✔
1848

1849
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1850
                    {
1851
                        UNPACK_ARGS();
12✔
1852
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
12✔
1853
                        DISPATCH();
11✔
1854
                    }
1855
#pragma endregion
1856
                }
69✔
1857
#if ARK_USE_COMPUTED_GOTOS
1858
            dispatch_end:
1859
                do
69✔
1860
                {
1861
                } while (false);
69✔
1862
#endif
1863
            }
1864
        }
179✔
1865
        catch (const Error& e)
1866
        {
1867
            if (fail_with_exception)
68✔
1868
            {
1869
                std::stringstream stream;
68✔
1870
                backtrace(context, stream, /* colorize= */ false);
68✔
1871
                // It's important we have an Ark::Error here, as the constructor for NestedError
1872
                // does more than just aggregate error messages, hence the code duplication.
1873
                throw NestedError(e, stream.str());
68✔
1874
            }
68✔
1875
            else
1876
                showBacktraceWithException(Error(e.details(/* colorize= */ true)), context);
×
1877
        }
137✔
1878
        catch (const std::exception& e)
1879
        {
1880
            if (fail_with_exception)
42✔
1881
            {
1882
                std::stringstream stream;
42✔
1883
                backtrace(context, stream, /* colorize= */ false);
42✔
1884
                throw NestedError(e, stream.str());
42✔
1885
            }
42✔
1886
            else
1887
                showBacktraceWithException(e, context);
×
1888
        }
110✔
1889
        catch (...)
1890
        {
1891
            if (fail_with_exception)
×
1892
                throw;
×
1893

1894
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1895
            throw;
1896
#endif
1897
            fmt::println("Unknown error");
×
1898
            backtrace(context);
×
1899
            m_exit_code = 1;
×
1900
        }
152✔
1901

1902
        return m_exit_code;
69✔
1903
    }
222✔
1904

1905
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,047✔
1906
    {
2,047✔
1907
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,175✔
1908
        {
1909
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,128✔
1910
                return id;
2,047✔
1911
        }
2,096,128✔
1912
        return std::numeric_limits<uint16_t>::max();
×
1913
    }
2,047✔
1914

1915
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
5✔
1916
    {
5✔
1917
        std::vector<std::string> arg_names;
5✔
1918
        arg_names.reserve(expected_arg_count + 1);
5✔
1919
        if (expected_arg_count > 0)
5✔
1920
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
1921

1922
        std::size_t index = 0;
5✔
1923
        while (m_state.inst(context.pp, index) == STORE)
12✔
1924
        {
1925
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
7✔
1926
            arg_names.push_back(m_state.m_symbols[id]);
7✔
1927
            index += 4;
7✔
1928
        }
7✔
1929
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
1930
        if (arg_names.size() == 1 && index == 0)
5✔
1931
        {
1932
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
1933
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
1934
                arg_names.push_back(std::string(1, static_cast<char>('a' + i)));
2✔
1935
        }
2✔
1936

1937
        std::vector<std::string> arg_vals;
5✔
1938
        arg_vals.reserve(passed_arg_count + 1);
5✔
1939
        if (passed_arg_count > 0)
5✔
1940
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
4✔
1941

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

1946
        // set ip/pp to the callee location so that the error can pinpoint the line
1947
        // where the bad call happened
1948
        if (context.sp >= 2 + passed_arg_count)
5✔
1949
        {
1950
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
5✔
1951
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
5✔
1952
            returnFromFuncCall(context);
5✔
1953
        }
5✔
1954

1955
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
10✔
1956
            ? m_state.m_symbols[context.last_symbol]
5✔
1957
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
1958

1959
        throwVMError(
5✔
1960
            ErrorKind::Arity,
1961
            fmt::format(
10✔
1962
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
5✔
1963
                function_name,
1964
                fmt::join(arg_vals, " "),
5✔
1965
                passed_arg_count,
1966
                passed_arg_count > 1 ? "s" : "",
5✔
1967
                expected_arg_count,
1968
                function_name,
1969
                fmt::join(arg_names, " ")));
5✔
1970
    }
10✔
1971

1972
    void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
×
1973
    {
×
1974
        std::string text = e.what();
×
1975
        if (!text.empty() && text.back() != '\n')
×
1976
            text += '\n';
×
1977
        fmt::println("{}", text);
×
1978
        backtrace(context);
×
1979
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1980
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1981
        m_exit_code = 0;
1982
#else
1983
        m_exit_code = 1;
×
1984
#endif
1985
    }
×
1986

1987
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp)
2,158✔
1988
    {
2,158✔
1989
        std::optional<InstLoc> match = std::nullopt;
2,158✔
1990

1991
        for (const auto location : m_state.m_inst_locations)
8,461✔
1992
        {
1993
            if (location.page_pointer == pp && !match)
6,303✔
1994
                match = location;
2,158✔
1995

1996
            // select the best match: we want to find the location that's nearest our instruction pointer,
1997
            // but not equal to it as the IP will always be pointing to the next instruction,
1998
            // not yet executed. Thus, the erroneous instruction is the previous one.
1999
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
6,303✔
2000
                match = location;
2,197✔
2001

2002
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2003
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
6,303✔
2004
                break;
10✔
2005
        }
6,303✔
2006

2007
        return match;
2,158✔
2008
    }
2009

2010
    std::string VM::debugShowSource()
×
2011
    {
×
2012
        const auto& context = m_execution_contexts.front();
×
2013
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2014
        if (maybe_source_loc)
×
2015
        {
2016
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2017
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2018
        }
×
2019
        return "No source location found";
×
2020
    }
×
2021

2022
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
110✔
2023
    {
110✔
2024
        const std::size_t saved_ip = context.ip;
110✔
2025
        const std::size_t saved_pp = context.pp;
110✔
2026
        const uint16_t saved_sp = context.sp;
110✔
2027
        const std::size_t max_consecutive_traces = 7;
110✔
2028

2029
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
110✔
2030
        if (maybe_location)
110✔
2031
        {
2032
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
110✔
2033

2034
            if (Utils::fileExists(filename))
110✔
2035
                Diagnostics::makeContext(
220✔
2036
                    os,
110✔
2037
                    filename,
2038
                    /* expr= */ std::nullopt,
110✔
2039
                    /* sym_size= */ 0,
2040
                    maybe_location->line,
110✔
2041
                    /* col_start= */ 0,
2042
                    /* maybe_context= */ std::nullopt,
110✔
2043
                    /* whole_line= */ true,
2044
                    /* colorize= */ colorize);
110✔
2045
            fmt::println(os, "");
110✔
2046
        }
110✔
2047

2048
        if (context.fc > 1)
110✔
2049
        {
2050
            // display call stack trace
2051
            const ScopeView old_scope = context.locals.back();
1✔
2052

2053
            std::string previous_trace;
1✔
2054
            std::size_t displayed_traces = 0;
1✔
2055
            std::size_t consecutive_similar_traces = 0;
1✔
2056

2057
            while (context.fc != 0)
2,048✔
2058
            {
2059
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,047✔
2060
                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,047✔
2061

2062
                if (context.pp != 0)
2,047✔
2063
                {
2064
                    const uint16_t id = findNearestVariableIdWithValue(
2,047✔
2065
                        Value(static_cast<PageAddr_t>(context.pp)),
2,047✔
2066
                        context);
2,047✔
2067
                    const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,047✔
2068

2069
                    if (func_name + loc_as_text != previous_trace)
2,047✔
2070
                    {
2071
                        fmt::println(
2✔
2072
                            os,
1✔
2073
                            "[{:4}] In function `{}'{}",
1✔
2074
                            fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2075
                            fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
1✔
2076
                            loc_as_text);
2077
                        previous_trace = func_name + loc_as_text;
1✔
2078
                        ++displayed_traces;
1✔
2079
                        consecutive_similar_traces = 0;
1✔
2080
                    }
1✔
2081
                    else if (consecutive_similar_traces == 0)
2,046✔
2082
                    {
2083
                        fmt::println(os, "       ...");
1✔
2084
                        ++consecutive_similar_traces;
1✔
2085
                    }
1✔
2086

2087
                    const Value* ip;
2,047✔
2088
                    do
2,048✔
2089
                    {
2090
                        ip = popAndResolveAsPtr(context);
2,048✔
2091
                    } while (ip->valueType() != ValueType::InstPtr);
2,048✔
2092

2093
                    context.ip = ip->pageAddr();
2,047✔
2094
                    context.pp = pop(context)->pageAddr();
2,047✔
2095
                    returnFromFuncCall(context);
2,047✔
2096
                }
2,047✔
2097

2098
                if (displayed_traces > max_consecutive_traces)
2,047✔
2099
                {
2100
                    fmt::println(os, "       ...");
×
2101
                    break;
×
2102
                }
2103
            }
2,047✔
2104

2105
            if (context.pp == 0)
1✔
2106
            {
2107
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
1✔
2108
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
1✔
2109
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
1✔
2110
            }
1✔
2111

2112
            // display variables values in the current scope
2113
            fmt::println(os, "\nCurrent scope variables values:");
1✔
2114
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
2✔
2115
            {
2116
                fmt::println(
2✔
2117
                    os,
1✔
2118
                    "{} = {}",
1✔
2119
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2120
                    old_scope.atPos(i).second.toString(*this));
1✔
2121
            }
1✔
2122
        }
1✔
2123

2124
        fmt::println(
220✔
2125
            os,
110✔
2126
            "At IP: {}, PP: {}, SP: {}",
110✔
2127
            // dividing by 4 because the instructions are actually on 4 bytes
2128
            fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
110✔
2129
            fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
110✔
2130
            fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
110✔
2131
    }
110✔
2132
}
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

© 2025 Coveralls, Inc