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

ArkScript-lang / Ark / 15237873748

25 May 2025 12:18PM UTC coverage: 86.737% (-0.03%) from 86.765%
15237873748

push

github

SuperFola
feat(compiler, vm): adding new APPEND_IN_PLACE_SYM and APPEND_IN_PLACE_SYM_INDEX super instructions to append to a list faster

22 of 23 new or added lines in 2 files covered. (95.65%)

6 existing lines in 2 files now uncovered.

7194 of 8294 relevant lines covered (86.74%)

84293.17 hits per line

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

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

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

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

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

22
namespace Ark
23
{
24
    using namespace internal;
25

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

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

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

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

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

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

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

88
            const auto num = static_cast<long>(index.number());
14,575✔
89

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

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

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

135
        context.sp = 0;
156✔
136
        context.fc = 1;
156✔
137

138
        m_shared_lib_objects.clear();
156✔
139
        context.stacked_closure_scopes.clear();
156✔
140
        context.stacked_closure_scopes.emplace_back(nullptr);
156✔
141

142
        context.saved_scope.reset();
156✔
143
        m_exit_code = 0;
156✔
144

145
        context.locals.clear();
156✔
146
        context.locals.emplace_back(context.scopes_storage.data(), 0);
156✔
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)
183✔
151
        {
152
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
22✔
153
            if (it != m_state.m_symbols.end())
22✔
154
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
5✔
155
        }
22✔
156
    }
156✔
157

158
    Value VM::getField(Value* closure, const uint16_t id, ExecutionContext& context)
1,646✔
159
    {
1,646✔
160
        if (closure->valueType() != ValueType::Closure)
1,646✔
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",
155✔
174
                                 types_to_str[static_cast<std::size_t>(closure->valueType())],
×
175
                                 m_state.m_symbols[id]));
×
176
        }
177

178
        if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
3,290✔
179
        {
180
            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
181
            if (m_state.m_pages[context.pp][context.ip] == CALL)
1,643✔
182
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
753✔
183
            else
184
                return *field;
890✔
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
    }
1,646✔
204

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

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

214
        return l;
950✔
215
    }
950✔
216

217
    void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
1,305✔
218
    {
1,305✔
219
        if (list->valueType() != ValueType::List)
1,305✔
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,608✔
231
            list->push_back(*popAndResolveAsPtr(context));
1,304✔
232
    }
1,305✔
233

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

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

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

255
        m_no_value = Builtins::nil;
×
256
        return m_no_value;
×
257
    }
28✔
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)
×
394
    {
×
395
        const std::lock_guard lock(m_mutex);
×
396

397
        const auto it =
×
398
            std::ranges::remove_if(
×
399
                m_futures,
1✔
400
                [f](const std::unique_ptr<Future>& future) {
×
401
                    return future.get() == f;
1✔
402
                })
403
                .begin();
1✔
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
            {
UNCOV
414
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
415
                // load the mapping data
UNCOV
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)
23✔
439
    {
23✔
440
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
23✔
441
    }
23✔
442

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

450
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
162✔
451
    {
162✔
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.m_pages[context.pp][context.ip];                                  \
469
        padding = m_state.m_pages[context.pp][context.ip + 1];                           \
470
        arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
471
                                    m_state.m_pages[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 = {
162✔
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_CALL,
501
                &&TARGET_CAPTURE,
502
                &&TARGET_BUILTIN,
503
                &&TARGET_DEL,
504
                &&TARGET_MAKE_CLOSURE,
505
                &&TARGET_GET_FIELD,
506
                &&TARGET_PLUGIN,
507
                &&TARGET_LIST,
508
                &&TARGET_APPEND,
509
                &&TARGET_CONCAT,
510
                &&TARGET_APPEND_IN_PLACE,
511
                &&TARGET_CONCAT_IN_PLACE,
512
                &&TARGET_POP_LIST,
513
                &&TARGET_POP_LIST_IN_PLACE,
514
                &&TARGET_SET_AT_INDEX,
515
                &&TARGET_SET_AT_2_INDEX,
516
                &&TARGET_POP,
517
                &&TARGET_SHORTCIRCUIT_AND,
518
                &&TARGET_SHORTCIRCUIT_OR,
519
                &&TARGET_CREATE_SCOPE,
520
                &&TARGET_RESET_SCOPE_JUMP,
521
                &&TARGET_POP_SCOPE,
522
                &&TARGET_ADD,
523
                &&TARGET_SUB,
524
                &&TARGET_MUL,
525
                &&TARGET_DIV,
526
                &&TARGET_GT,
527
                &&TARGET_LT,
528
                &&TARGET_LE,
529
                &&TARGET_GE,
530
                &&TARGET_NEQ,
531
                &&TARGET_EQ,
532
                &&TARGET_LEN,
533
                &&TARGET_EMPTY,
534
                &&TARGET_TAIL,
535
                &&TARGET_HEAD,
536
                &&TARGET_ISNIL,
537
                &&TARGET_ASSERT,
538
                &&TARGET_TO_NUM,
539
                &&TARGET_TO_STR,
540
                &&TARGET_AT,
541
                &&TARGET_AT_AT,
542
                &&TARGET_MOD,
543
                &&TARGET_TYPE,
544
                &&TARGET_HASFIELD,
545
                &&TARGET_NOT,
546
                &&TARGET_LOAD_CONST_LOAD_CONST,
547
                &&TARGET_LOAD_CONST_STORE,
548
                &&TARGET_LOAD_CONST_SET_VAL,
549
                &&TARGET_STORE_FROM,
550
                &&TARGET_STORE_FROM_INDEX,
551
                &&TARGET_SET_VAL_FROM,
552
                &&TARGET_SET_VAL_FROM_INDEX,
553
                &&TARGET_INCREMENT,
554
                &&TARGET_INCREMENT_BY_INDEX,
555
                &&TARGET_INCREMENT_STORE,
556
                &&TARGET_DECREMENT,
557
                &&TARGET_DECREMENT_BY_INDEX,
558
                &&TARGET_DECREMENT_STORE,
559
                &&TARGET_STORE_TAIL,
560
                &&TARGET_STORE_TAIL_BY_INDEX,
561
                &&TARGET_STORE_HEAD,
562
                &&TARGET_STORE_HEAD_BY_INDEX,
563
                &&TARGET_STORE_LIST,
564
                &&TARGET_SET_VAL_TAIL,
565
                &&TARGET_SET_VAL_TAIL_BY_INDEX,
566
                &&TARGET_SET_VAL_HEAD,
567
                &&TARGET_SET_VAL_HEAD_BY_INDEX,
568
                &&TARGET_CALL_BUILTIN,
569
                &&TARGET_LT_CONST_JUMP_IF_FALSE,
570
                &&TARGET_LT_CONST_JUMP_IF_TRUE,
571
                &&TARGET_LT_SYM_JUMP_IF_FALSE,
572
                &&TARGET_GT_CONST_JUMP_IF_TRUE,
573
                &&TARGET_GT_CONST_JUMP_IF_FALSE,
574
                &&TARGET_GT_SYM_JUMP_IF_FALSE,
575
                &&TARGET_EQ_CONST_JUMP_IF_TRUE,
576
                &&TARGET_EQ_SYM_INDEX_JUMP_IF_TRUE,
577
                &&TARGET_NEQ_CONST_JUMP_IF_TRUE,
578
                &&TARGET_NEQ_SYM_JUMP_IF_FALSE,
579
                &&TARGET_CALL_SYMBOL,
580
                &&TARGET_GET_FIELD_FROM_SYMBOL,
581
                &&TARGET_GET_FIELD_FROM_SYMBOL_INDEX,
582
                &&TARGET_AT_SYM_SYM,
583
                &&TARGET_AT_SYM_INDEX_SYM_INDEX,
584
                &&TARGET_CHECK_TYPE_OF,
585
                &&TARGET_CHECK_TYPE_OF_BY_INDEX,
586
                &&TARGET_APPEND_IN_PLACE_SYM,
587
                &&TARGET_APPEND_IN_PLACE_SYM_INDEX
588
            };
589

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

594
        try
595
        {
596
            uint8_t inst = 0;
162✔
597
            uint8_t padding = 0;
162✔
598
            uint16_t arg = 0;
162✔
599
            uint16_t primary_arg = 0;
162✔
600
            uint16_t secondary_arg = 0;
162✔
601

602
            m_running = true;
162✔
603

604
            DISPATCH();
162✔
605
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
606
            {
607
#if !ARK_USE_COMPUTED_GOTOS
608
            dispatch_opcode:
609
                switch (inst)
610
#endif
611
                {
×
612
#pragma region "Instructions"
613
                    TARGET(NOP)
614
                    {
615
                        DISPATCH();
×
616
                    }
63,962✔
617

618
                    TARGET(LOAD_SYMBOL)
619
                    {
620
                        push(loadSymbol(arg, context), context);
63,962✔
621
                        DISPATCH();
63,962✔
622
                    }
327,579✔
623

624
                    TARGET(LOAD_SYMBOL_BY_INDEX)
625
                    {
626
                        push(loadSymbolFromIndex(arg, context), context);
327,579✔
627
                        DISPATCH();
327,579✔
628
                    }
96,858✔
629

630
                    TARGET(LOAD_CONST)
631
                    {
632
                        push(loadConstAsPtr(arg), context);
96,858✔
633
                        DISPATCH();
96,858✔
634
                    }
11,466✔
635

636
                    TARGET(POP_JUMP_IF_TRUE)
637
                    {
638
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
17,171✔
639
                            context.ip = arg * 4;  // instructions are 4 bytes
5,705✔
640
                        DISPATCH();
11,466✔
641
                    }
394,731✔
642

643
                    TARGET(STORE)
644
                    {
645
                        store(arg, popAndResolveAsPtr(context), context);
394,731✔
646
                        DISPATCH();
394,731✔
647
                    }
20,561✔
648

649
                    TARGET(SET_VAL)
650
                    {
651
                        setVal(arg, popAndResolveAsPtr(context), context);
20,561✔
652
                        DISPATCH();
20,561✔
653
                    }
10,683✔
654

655
                    TARGET(POP_JUMP_IF_FALSE)
656
                    {
657
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
11,827✔
658
                            context.ip = arg * 4;  // instructions are 4 bytes
1,144✔
659
                        DISPATCH();
10,683✔
660
                    }
190,416✔
661

662
                    TARGET(JUMP)
663
                    {
664
                        context.ip = arg * 4;  // instructions are 4 bytes
190,416✔
665
                        DISPATCH();
190,416✔
666
                    }
120,837✔
667

668
                    TARGET(RET)
669
                    {
670
                        {
671
                            Value ip_or_val = *popAndResolveAsPtr(context);
120,837✔
672
                            // no return value on the stack
673
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
120,837✔
674
                            {
675
                                context.ip = ip_or_val.pageAddr();
1,433✔
676
                                // we always push PP then IP, thus the next value
677
                                // MUST be the page pointer
678
                                context.pp = pop(context)->pageAddr();
1,433✔
679

680
                                returnFromFuncCall(context);
1,433✔
681
                                push(Builtins::nil, context);
1,433✔
682
                            }
1,433✔
683
                            // value on the stack
684
                            else [[likely]]
685
                            {
686
                                const Value* ip = popAndResolveAsPtr(context);
119,404✔
687
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
119,404✔
688
                                context.ip = ip->pageAddr();
119,404✔
689
                                context.pp = pop(context)->pageAddr();
119,404✔
690

691
                                returnFromFuncCall(context);
119,404✔
692
                                push(std::move(ip_or_val), context);
119,404✔
693
                            }
694

695
                            if (context.fc <= untilFrameCount)
120,837✔
696
                                GOTO_HALT();
6✔
697
                        }
120,837✔
698

699
                        DISPATCH();
120,831✔
700
                    }
54✔
701

702
                    TARGET(HALT)
703
                    {
704
                        m_running = false;
54✔
705
                        GOTO_HALT();
54✔
706
                    }
1,290✔
707

708
                    TARGET(CALL)
709
                    {
710
                        call(context, arg);
1,290✔
711
                        if (!m_running)
1,285✔
712
                            GOTO_HALT();
×
713
                        DISPATCH();
1,285✔
714
                    }
459✔
715

716
                    TARGET(CAPTURE)
717
                    {
718
                        if (!context.saved_scope)
459✔
719
                            context.saved_scope = ClosureScope();
104✔
720

721
                        const Value* ptr = findNearestVariable(arg, context);
459✔
722
                        if (!ptr)
459✔
723
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
724
                        else
725
                        {
726
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
459✔
727
                            context.saved_scope.value().push_back(arg, *ptr);
459✔
728
                        }
729

730
                        DISPATCH();
459✔
731
                    }
430✔
732

733
                    TARGET(BUILTIN)
734
                    {
735
                        push(Builtins::builtins[arg].second, context);
430✔
736
                        DISPATCH();
430✔
737
                    }
1✔
738

739
                    TARGET(DEL)
740
                    {
741
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
742
                        {
743
                            if (var->valueType() == ValueType::User)
×
744
                                var->usertypeRef().del();
×
745
                            *var = Value();
×
746
                            DISPATCH();
×
747
                        }
748

749
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
750
                    }
104✔
751

752
                    TARGET(MAKE_CLOSURE)
753
                    {
754
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
104✔
755
                        context.saved_scope.reset();
104✔
756
                        DISPATCH();
104✔
757
                    }
6✔
758

759
                    TARGET(GET_FIELD)
760
                    {
761
                        Value* var = popAndResolveAsPtr(context);
6✔
762
                        push(getField(var, arg, context), context);
6✔
763
                        DISPATCH();
6✔
764
                    }
×
765

766
                    TARGET(PLUGIN)
767
                    {
768
                        loadPlugin(arg, context);
×
769
                        DISPATCH();
×
770
                    }
580✔
771

772
                    TARGET(LIST)
773
                    {
774
                        {
775
                            Value l = createList(arg, context);
580✔
776
                            push(std::move(l), context);
580✔
777
                        }
580✔
778
                        DISPATCH();
580✔
779
                    }
1,034✔
780

781
                    TARGET(APPEND)
782
                    {
783
                        {
784
                            Value* list = popAndResolveAsPtr(context);
1,034✔
785
                            if (list->valueType() != ValueType::List)
1,034✔
786
                            {
787
                                std::vector<Value> args = { *list };
1✔
788
                                for (uint16_t i = 0; i < arg; ++i)
2✔
789
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
790
                                throw types::TypeCheckingError(
2✔
791
                                    "append",
1✔
792
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
1✔
793
                                    args);
794
                            }
1✔
795

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

798
                            Value obj { *list };
1,033✔
799
                            obj.list().reserve(size + arg);
1,033✔
800

801
                            for (uint16_t i = 0; i < arg; ++i)
2,066✔
802
                                obj.push_back(*popAndResolveAsPtr(context));
1,033✔
803
                            push(std::move(obj), context);
1,033✔
804
                        }
1,033✔
805
                        DISPATCH();
1,033✔
806
                    }
12✔
807

808
                    TARGET(CONCAT)
809
                    {
810
                        {
811
                            Value* list = popAndResolveAsPtr(context);
12✔
812
                            Value obj { *list };
12✔
813

814
                            for (uint16_t i = 0; i < arg; ++i)
24✔
815
                            {
816
                                Value* next = popAndResolveAsPtr(context);
14✔
817

818
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
14✔
819
                                    throw types::TypeCheckingError(
4✔
820
                                        "concat",
2✔
821
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
822
                                        { *list, *next });
2✔
823

824
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
12✔
825
                            }
12✔
826
                            push(std::move(obj), context);
10✔
827
                        }
12✔
828
                        DISPATCH();
10✔
UNCOV
829
                    }
×
830

831
                    TARGET(APPEND_IN_PLACE)
832
                    {
UNCOV
833
                        Value* list = popAndResolveAsPtr(context);
×
NEW
834
                        listAppendInPlace(list, arg, context);
×
UNCOV
835
                        DISPATCH();
×
836
                    }
52✔
837

838
                    TARGET(CONCAT_IN_PLACE)
839
                    {
840
                        Value* list = popAndResolveAsPtr(context);
52✔
841

842
                        for (uint16_t i = 0; i < arg; ++i)
132✔
843
                        {
844
                            Value* next = popAndResolveAsPtr(context);
82✔
845

846
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
82✔
847
                                throw types::TypeCheckingError(
4✔
848
                                    "concat!",
2✔
849
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
850
                                    { *list, *next });
2✔
851

852
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
80✔
853
                        }
80✔
854
                        DISPATCH();
50✔
855
                    }
5✔
856

857
                    TARGET(POP_LIST)
858
                    {
859
                        {
860
                            Value list = *popAndResolveAsPtr(context);
5✔
861
                            Value number = *popAndResolveAsPtr(context);
5✔
862

863
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
5✔
864
                                throw types::TypeCheckingError(
2✔
865
                                    "pop",
1✔
866
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
867
                                    { list, number });
1✔
868

869
                            long idx = static_cast<long>(number.number());
4✔
870
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
871
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
872
                                throwVMError(
1✔
873
                                    ErrorKind::Index,
874
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
875

876
                            list.list().erase(list.list().begin() + idx);
3✔
877
                            push(list, context);
3✔
878
                        }
5✔
879
                        DISPATCH();
3✔
880
                    }
59✔
881

882
                    TARGET(POP_LIST_IN_PLACE)
883
                    {
884
                        {
885
                            Value* list = popAndResolveAsPtr(context);
59✔
886
                            Value number = *popAndResolveAsPtr(context);
59✔
887

888
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
59✔
889
                                throw types::TypeCheckingError(
2✔
890
                                    "pop!",
1✔
891
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
892
                                    { *list, number });
1✔
893

894
                            long idx = static_cast<long>(number.number());
58✔
895
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
58✔
896
                            if (std::cmp_greater_equal(idx, list->list().size()))
58✔
897
                                throwVMError(
1✔
898
                                    ErrorKind::Index,
899
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
1✔
900

901
                            list->list().erase(list->list().begin() + idx);
57✔
902
                        }
59✔
903
                        DISPATCH();
57✔
904
                    }
489✔
905

906
                    TARGET(SET_AT_INDEX)
907
                    {
908
                        {
909
                            Value* list = popAndResolveAsPtr(context);
489✔
910
                            Value number = *popAndResolveAsPtr(context);
489✔
911
                            Value new_value = *popAndResolveAsPtr(context);
489✔
912

913
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
489✔
914
                                throw types::TypeCheckingError(
2✔
915
                                    "@=",
1✔
916
                                    { { types::Contract {
3✔
917
                                          { types::Typedef("list", ValueType::List),
3✔
918
                                            types::Typedef("index", ValueType::Number),
1✔
919
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
920
                                      { types::Contract {
1✔
921
                                          { types::Typedef("string", ValueType::String),
3✔
922
                                            types::Typedef("index", ValueType::Number),
1✔
923
                                            types::Typedef("char", ValueType::String) } } } },
1✔
924
                                    { *list, number, new_value });
1✔
925

926
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
488✔
927
                            long idx = static_cast<long>(number.number());
488✔
928
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
488✔
929
                            if (std::cmp_greater_equal(idx, size))
488✔
930
                                throwVMError(
1✔
931
                                    ErrorKind::Index,
932
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
1✔
933

934
                            if (list->valueType() == ValueType::List)
487✔
935
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
936
                            else
937
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
938
                        }
489✔
939
                        DISPATCH();
487✔
940
                    }
10✔
941

942
                    TARGET(SET_AT_2_INDEX)
943
                    {
944
                        {
945
                            Value* list = popAndResolveAsPtr(context);
10✔
946
                            Value x = *popAndResolveAsPtr(context);
10✔
947
                            Value y = *popAndResolveAsPtr(context);
10✔
948
                            Value new_value = *popAndResolveAsPtr(context);
10✔
949

950
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
10✔
951
                                throw types::TypeCheckingError(
2✔
952
                                    "@@=",
1✔
953
                                    { { types::Contract {
2✔
954
                                        { types::Typedef("list", ValueType::List),
4✔
955
                                          types::Typedef("x", ValueType::Number),
1✔
956
                                          types::Typedef("y", ValueType::Number),
1✔
957
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
958
                                    { *list, x, y, new_value });
1✔
959

960
                            long idx_y = static_cast<long>(x.number());
9✔
961
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
9✔
962
                            if (std::cmp_greater_equal(idx_y, list->list().size()))
9✔
963
                                throwVMError(
1✔
964
                                    ErrorKind::Index,
965
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
1✔
966

967
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
12✔
968
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
7✔
969
                                throw types::TypeCheckingError(
2✔
970
                                    "@@=",
1✔
971
                                    { { types::Contract {
3✔
972
                                          { types::Typedef("list", ValueType::List),
4✔
973
                                            types::Typedef("x", ValueType::Number),
1✔
974
                                            types::Typedef("y", ValueType::Number),
1✔
975
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
976
                                      { types::Contract {
1✔
977
                                          { types::Typedef("string", ValueType::String),
4✔
978
                                            types::Typedef("x", ValueType::Number),
1✔
979
                                            types::Typedef("y", ValueType::Number),
1✔
980
                                            types::Typedef("char", ValueType::String) } } } },
1✔
981
                                    { *list, x, y, new_value });
1✔
982

983
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
7✔
984
                            const std::size_t size =
7✔
985
                                is_list
14✔
986
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
5✔
987
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
988

989
                            long idx_x = static_cast<long>(y.number());
7✔
990
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
7✔
991
                            if (std::cmp_greater_equal(idx_x, size))
7✔
992
                                throwVMError(
1✔
993
                                    ErrorKind::Index,
994
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
995

996
                            if (is_list)
6✔
997
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
998
                            else
999
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1000
                        }
10✔
1001
                        DISPATCH();
6✔
1002
                    }
1,741✔
1003

1004
                    TARGET(POP)
1005
                    {
1006
                        pop(context);
1,741✔
1007
                        DISPATCH();
1,741✔
1008
                    }
7,879✔
1009

1010
                    TARGET(SHORTCIRCUIT_AND)
1011
                    {
1012
                        if (!*peekAndResolveAsPtr(context))
7,879✔
1013
                            context.ip = arg * 4;
454✔
1014
                        else
1015
                            pop(context);
7,425✔
1016
                        DISPATCH();
7,879✔
1017
                    }
112✔
1018

1019
                    TARGET(SHORTCIRCUIT_OR)
1020
                    {
1021
                        if (!!*peekAndResolveAsPtr(context))
112✔
1022
                            context.ip = arg * 4;
7✔
1023
                        else
1024
                            pop(context);
105✔
1025
                        DISPATCH();
112✔
1026
                    }
1,791✔
1027

1028
                    TARGET(CREATE_SCOPE)
1029
                    {
1030
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
1,791✔
1031
                        DISPATCH();
1,791✔
1032
                    }
15,232✔
1033

1034
                    TARGET(RESET_SCOPE_JUMP)
1035
                    {
1036
                        context.locals.back().reset();
15,232✔
1037
                        context.ip = arg * 4;  // instructions are 4 bytes
15,232✔
1038
                        DISPATCH();
15,232✔
1039
                    }
1,791✔
1040

1041
                    TARGET(POP_SCOPE)
1042
                    {
1043
                        context.locals.pop_back();
1,791✔
1044
                        DISPATCH();
1,791✔
1045
                    }
25,207✔
1046

1047
#pragma endregion
1048

1049
#pragma region "Operators"
1050

1051
                    TARGET(ADD)
1052
                    {
1053
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
25,207✔
1054

1055
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
25,207✔
1056
                            push(Value(a->number() + b->number()), context);
17,962✔
1057
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
7,245✔
1058
                            push(Value(a->string() + b->string()), context);
7,244✔
1059
                        else
1060
                            throw types::TypeCheckingError(
2✔
1061
                                "+",
1✔
1062
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1063
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1064
                                { *a, *b });
1✔
1065
                        DISPATCH();
25,206✔
1066
                    }
118✔
1067

1068
                    TARGET(SUB)
1069
                    {
1070
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
118✔
1071

1072
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
118✔
1073
                            throw types::TypeCheckingError(
2✔
1074
                                "-",
1✔
1075
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1076
                                { *a, *b });
1✔
1077
                        push(Value(a->number() - b->number()), context);
117✔
1078
                        DISPATCH();
117✔
1079
                    }
1,363✔
1080

1081
                    TARGET(MUL)
1082
                    {
1083
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,363✔
1084

1085
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,363✔
1086
                            throw types::TypeCheckingError(
2✔
1087
                                "*",
1✔
1088
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1089
                                { *a, *b });
1✔
1090
                        push(Value(a->number() * b->number()), context);
1,362✔
1091
                        DISPATCH();
1,362✔
1092
                    }
1,060✔
1093

1094
                    TARGET(DIV)
1095
                    {
1096
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,060✔
1097

1098
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,060✔
1099
                            throw types::TypeCheckingError(
2✔
1100
                                "/",
1✔
1101
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1102
                                { *a, *b });
1✔
1103
                        auto d = b->number();
1,059✔
1104
                        if (d == 0)
1,059✔
1105
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1106

1107
                        push(Value(a->number() / d), context);
1,058✔
1108
                        DISPATCH();
1,058✔
1109
                    }
146✔
1110

1111
                    TARGET(GT)
1112
                    {
1113
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
146✔
1114
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
146✔
1115
                        DISPATCH();
146✔
1116
                    }
11,774✔
1117

1118
                    TARGET(LT)
1119
                    {
1120
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
11,774✔
1121
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
11,774✔
1122
                        DISPATCH();
11,774✔
1123
                    }
7,187✔
1124

1125
                    TARGET(LE)
1126
                    {
1127
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,187✔
1128
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,187✔
1129
                        DISPATCH();
7,187✔
1130
                    }
5,730✔
1131

1132
                    TARGET(GE)
1133
                    {
1134
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,730✔
1135
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,730✔
1136
                        DISPATCH();
5,730✔
1137
                    }
624✔
1138

1139
                    TARGET(NEQ)
1140
                    {
1141
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
624✔
1142
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
624✔
1143
                        DISPATCH();
624✔
1144
                    }
1,789✔
1145

1146
                    TARGET(EQ)
1147
                    {
1148
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,789✔
1149
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
1,789✔
1150
                        DISPATCH();
1,789✔
1151
                    }
10,851✔
1152

1153
                    TARGET(LEN)
1154
                    {
1155
                        const Value* a = popAndResolveAsPtr(context);
10,851✔
1156

1157
                        if (a->valueType() == ValueType::List)
10,851✔
1158
                            push(Value(static_cast<int>(a->constList().size())), context);
3,225✔
1159
                        else if (a->valueType() == ValueType::String)
7,626✔
1160
                            push(Value(static_cast<int>(a->string().size())), context);
7,625✔
1161
                        else
1162
                            throw types::TypeCheckingError(
2✔
1163
                                "len",
1✔
1164
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1165
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1166
                                { *a });
1✔
1167
                        DISPATCH();
10,850✔
1168
                    }
540✔
1169

1170
                    TARGET(EMPTY)
1171
                    {
1172
                        const Value* a = popAndResolveAsPtr(context);
540✔
1173

1174
                        if (a->valueType() == ValueType::List)
540✔
1175
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
86✔
1176
                        else if (a->valueType() == ValueType::String)
454✔
1177
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
453✔
1178
                        else
1179
                            throw types::TypeCheckingError(
2✔
1180
                                "empty?",
1✔
1181
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1182
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1183
                                { *a });
1✔
1184
                        DISPATCH();
539✔
1185
                    }
335✔
1186

1187
                    TARGET(TAIL)
1188
                    {
1189
                        Value* const a = popAndResolveAsPtr(context);
335✔
1190
                        push(helper::tail(a), context);
336✔
1191
                        DISPATCH();
334✔
1192
                    }
1,099✔
1193

1194
                    TARGET(HEAD)
1195
                    {
1196
                        Value* const a = popAndResolveAsPtr(context);
1,099✔
1197
                        push(helper::head(a), context);
1,100✔
1198
                        DISPATCH();
1,098✔
1199
                    }
1,268✔
1200

1201
                    TARGET(ISNIL)
1202
                    {
1203
                        const Value* a = popAndResolveAsPtr(context);
1,268✔
1204
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
1,268✔
1205
                        DISPATCH();
1,268✔
1206
                    }
574✔
1207

1208
                    TARGET(ASSERT)
1209
                    {
1210
                        Value* const b = popAndResolveAsPtr(context);
574✔
1211
                        Value* const a = popAndResolveAsPtr(context);
574✔
1212

1213
                        if (b->valueType() != ValueType::String)
574✔
1214
                            throw types::TypeCheckingError(
2✔
1215
                                "assert",
1✔
1216
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1217
                                { *a, *b });
1✔
1218

1219
                        if (*a == Builtins::falseSym)
573✔
1220
                            throw AssertionFailed(b->stringRef());
×
1221
                        DISPATCH();
573✔
1222
                    }
14✔
1223

1224
                    TARGET(TO_NUM)
1225
                    {
1226
                        const Value* a = popAndResolveAsPtr(context);
14✔
1227

1228
                        if (a->valueType() != ValueType::String)
14✔
1229
                            throw types::TypeCheckingError(
2✔
1230
                                "toNumber",
1✔
1231
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1232
                                { *a });
1✔
1233

1234
                        double val;
1235
                        if (Utils::isDouble(a->string(), &val))
13✔
1236
                            push(Value(val), context);
10✔
1237
                        else
1238
                            push(Builtins::nil, context);
3✔
1239
                        DISPATCH();
13✔
1240
                    }
16✔
1241

1242
                    TARGET(TO_STR)
1243
                    {
1244
                        const Value* a = popAndResolveAsPtr(context);
16✔
1245
                        push(Value(a->toString(*this)), context);
16✔
1246
                        DISPATCH();
16✔
1247
                    }
1,035✔
1248

1249
                    TARGET(AT)
1250
                    {
1251
                        Value& b = *popAndResolveAsPtr(context);
1,035✔
1252
                        Value& a = *popAndResolveAsPtr(context);
1,035✔
1253
                        push(helper::at(a, b, *this), context);
1,039✔
1254
                        DISPATCH();
1,031✔
1255
                    }
24✔
1256

1257
                    TARGET(AT_AT)
1258
                    {
1259
                        {
1260
                            const Value* x = popAndResolveAsPtr(context);
24✔
1261
                            const Value* y = popAndResolveAsPtr(context);
24✔
1262
                            Value& list = *popAndResolveAsPtr(context);
24✔
1263

1264
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
24✔
1265
                                list.valueType() != ValueType::List)
23✔
1266
                                throw types::TypeCheckingError(
2✔
1267
                                    "@@",
1✔
1268
                                    { { types::Contract {
2✔
1269
                                        { types::Typedef("src", ValueType::List),
3✔
1270
                                          types::Typedef("y", ValueType::Number),
1✔
1271
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1272
                                    { list, *y, *x });
1✔
1273

1274
                            long idx_y = static_cast<long>(y->number());
23✔
1275
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
23✔
1276
                            if (std::cmp_greater_equal(idx_y, list.list().size()))
23✔
1277
                                throwVMError(
1✔
1278
                                    ErrorKind::Index,
1279
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1✔
1280

1281
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
22✔
1282
                            const std::size_t size =
22✔
1283
                                is_list
44✔
1284
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
15✔
1285
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1286

1287
                            long idx_x = static_cast<long>(x->number());
22✔
1288
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
22✔
1289
                            if (std::cmp_greater_equal(idx_x, size))
22✔
1290
                                throwVMError(
1✔
1291
                                    ErrorKind::Index,
1292
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
1293

1294
                            if (is_list)
21✔
1295
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
14✔
1296
                            else
1297
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1298
                        }
1299
                        DISPATCH();
21✔
1300
                    }
793✔
1301

1302
                    TARGET(MOD)
1303
                    {
1304
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
793✔
1305
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
793✔
1306
                            throw types::TypeCheckingError(
2✔
1307
                                "mod",
1✔
1308
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1309
                                { *a, *b });
1✔
1310
                        push(Value(std::fmod(a->number(), b->number())), context);
792✔
1311
                        DISPATCH();
792✔
1312
                    }
22✔
1313

1314
                    TARGET(TYPE)
1315
                    {
1316
                        const Value* a = popAndResolveAsPtr(context);
22✔
1317
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
22✔
1318
                        DISPATCH();
22✔
1319
                    }
3✔
1320

1321
                    TARGET(HASFIELD)
1322
                    {
1323
                        {
1324
                            Value* const field = popAndResolveAsPtr(context);
3✔
1325
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1326
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1327
                                throw types::TypeCheckingError(
2✔
1328
                                    "hasField",
1✔
1329
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1330
                                    { *closure, *field });
1✔
1331

1332
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1333
                            if (it == m_state.m_symbols.end())
2✔
1334
                            {
1335
                                push(Builtins::falseSym, context);
1✔
1336
                                DISPATCH();
1✔
1337
                            }
1338

1339
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1340
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1341
                        }
1342
                        DISPATCH();
1✔
1343
                    }
2,364✔
1344

1345
                    TARGET(NOT)
1346
                    {
1347
                        const Value* a = popAndResolveAsPtr(context);
2,364✔
1348
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
2,364✔
1349
                        DISPATCH();
2,364✔
1350
                    }
5,448✔
1351

1352
#pragma endregion
1353

1354
#pragma region "Super Instructions"
1355
                    TARGET(LOAD_CONST_LOAD_CONST)
1356
                    {
1357
                        UNPACK_ARGS();
5,448✔
1358
                        push(loadConstAsPtr(primary_arg), context);
5,448✔
1359
                        push(loadConstAsPtr(secondary_arg), context);
5,448✔
1360
                        DISPATCH();
5,448✔
1361
                    }
5,515✔
1362

1363
                    TARGET(LOAD_CONST_STORE)
1364
                    {
1365
                        UNPACK_ARGS();
5,515✔
1366
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
5,515✔
1367
                        DISPATCH();
5,515✔
1368
                    }
217✔
1369

1370
                    TARGET(LOAD_CONST_SET_VAL)
1371
                    {
1372
                        UNPACK_ARGS();
217✔
1373
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
217✔
1374
                        DISPATCH();
216✔
1375
                    }
15✔
1376

1377
                    TARGET(STORE_FROM)
1378
                    {
1379
                        UNPACK_ARGS();
15✔
1380
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
15✔
1381
                        DISPATCH();
14✔
1382
                    }
581✔
1383

1384
                    TARGET(STORE_FROM_INDEX)
1385
                    {
1386
                        UNPACK_ARGS();
581✔
1387
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
581✔
1388
                        DISPATCH();
581✔
1389
                    }
37✔
1390

1391
                    TARGET(SET_VAL_FROM)
1392
                    {
1393
                        UNPACK_ARGS();
37✔
1394
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
37✔
1395
                        DISPATCH();
37✔
1396
                    }
135✔
1397

1398
                    TARGET(SET_VAL_FROM_INDEX)
1399
                    {
1400
                        UNPACK_ARGS();
135✔
1401
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
135✔
1402
                        DISPATCH();
135✔
1403
                    }
16✔
1404

1405
                    TARGET(INCREMENT)
1406
                    {
1407
                        UNPACK_ARGS();
16✔
1408
                        {
1409
                            Value* var = loadSymbol(primary_arg, context);
16✔
1410

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

1415
                            if (var->valueType() == ValueType::Number)
16✔
1416
                                push(Value(var->number() + secondary_arg), context);
16✔
1417
                            else
1418
                                throw types::TypeCheckingError(
×
1419
                                    "+",
×
1420
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1421
                                    { *var, Value(secondary_arg) });
×
1422
                        }
1423
                        DISPATCH();
16✔
1424
                    }
87,966✔
1425

1426
                    TARGET(INCREMENT_BY_INDEX)
1427
                    {
1428
                        UNPACK_ARGS();
87,966✔
1429
                        {
1430
                            Value* var = loadSymbolFromIndex(primary_arg, context);
87,966✔
1431

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

1436
                            if (var->valueType() == ValueType::Number)
87,966✔
1437
                                push(Value(var->number() + secondary_arg), context);
87,965✔
1438
                            else
1439
                                throw types::TypeCheckingError(
2✔
1440
                                    "+",
1✔
1441
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1442
                                    { *var, Value(secondary_arg) });
1✔
1443
                        }
1444
                        DISPATCH();
87,965✔
1445
                    }
15,075✔
1446

1447
                    TARGET(INCREMENT_STORE)
1448
                    {
1449
                        UNPACK_ARGS();
15,075✔
1450
                        {
1451
                            Value* var = loadSymbol(primary_arg, context);
15,075✔
1452

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

1457
                            if (var->valueType() == ValueType::Number)
15,075✔
1458
                            {
1459
                                Value val = Value(var->number() + secondary_arg);
15,075✔
1460
                                setVal(primary_arg, &val, context);
15,075✔
1461
                            }
15,075✔
1462
                            else
1463
                                throw types::TypeCheckingError(
×
1464
                                    "+",
×
1465
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1466
                                    { *var, Value(secondary_arg) });
×
1467
                        }
1468
                        DISPATCH();
15,075✔
1469
                    }
1,274✔
1470

1471
                    TARGET(DECREMENT)
1472
                    {
1473
                        UNPACK_ARGS();
1,274✔
1474
                        {
1475
                            Value* var = loadSymbol(primary_arg, context);
1,274✔
1476

1477
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1478
                            if (var->valueType() == ValueType::Reference)
1,274✔
1479
                                var = var->reference();
×
1480

1481
                            if (var->valueType() == ValueType::Number)
1,274✔
1482
                                push(Value(var->number() - secondary_arg), context);
1,274✔
1483
                            else
1484
                                throw types::TypeCheckingError(
×
1485
                                    "-",
×
1486
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1487
                                    { *var, Value(secondary_arg) });
×
1488
                        }
1489
                        DISPATCH();
1,274✔
1490
                    }
194,404✔
1491

1492
                    TARGET(DECREMENT_BY_INDEX)
1493
                    {
1494
                        UNPACK_ARGS();
194,404✔
1495
                        {
1496
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,404✔
1497

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

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

1513
                    TARGET(DECREMENT_STORE)
1514
                    {
1515
                        UNPACK_ARGS();
×
1516
                        {
1517
                            Value* var = loadSymbol(primary_arg, context);
×
1518

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

1523
                            if (var->valueType() == ValueType::Number)
×
1524
                            {
1525
                                Value val = Value(var->number() - secondary_arg);
×
1526
                                setVal(primary_arg, &val, context);
×
1527
                            }
×
1528
                            else
1529
                                throw types::TypeCheckingError(
×
1530
                                    "-",
×
1531
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1532
                                    { *var, Value(secondary_arg) });
×
1533
                        }
1534
                        DISPATCH();
×
1535
                    }
×
1536

1537
                    TARGET(STORE_TAIL)
1538
                    {
1539
                        UNPACK_ARGS();
×
1540
                        {
1541
                            Value* list = loadSymbol(primary_arg, context);
×
1542
                            Value tail = helper::tail(list);
×
1543
                            store(secondary_arg, &tail, context);
×
1544
                        }
×
1545
                        DISPATCH();
×
1546
                    }
1✔
1547

1548
                    TARGET(STORE_TAIL_BY_INDEX)
1549
                    {
1550
                        UNPACK_ARGS();
1✔
1551
                        {
1552
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1553
                            Value tail = helper::tail(list);
1✔
1554
                            store(secondary_arg, &tail, context);
1✔
1555
                        }
1✔
1556
                        DISPATCH();
1✔
1557
                    }
×
1558

1559
                    TARGET(STORE_HEAD)
1560
                    {
1561
                        UNPACK_ARGS();
×
1562
                        {
1563
                            Value* list = loadSymbol(primary_arg, context);
×
1564
                            Value head = helper::head(list);
×
1565
                            store(secondary_arg, &head, context);
×
1566
                        }
×
1567
                        DISPATCH();
×
1568
                    }
31✔
1569

1570
                    TARGET(STORE_HEAD_BY_INDEX)
1571
                    {
1572
                        UNPACK_ARGS();
31✔
1573
                        {
1574
                            Value* list = loadSymbolFromIndex(primary_arg, context);
31✔
1575
                            Value head = helper::head(list);
31✔
1576
                            store(secondary_arg, &head, context);
31✔
1577
                        }
31✔
1578
                        DISPATCH();
31✔
1579
                    }
370✔
1580

1581
                    TARGET(STORE_LIST)
1582
                    {
1583
                        UNPACK_ARGS();
370✔
1584
                        {
1585
                            Value l = createList(primary_arg, context);
370✔
1586
                            store(secondary_arg, &l, context);
370✔
1587
                        }
370✔
1588
                        DISPATCH();
370✔
1589
                    }
×
1590

1591
                    TARGET(SET_VAL_TAIL)
1592
                    {
1593
                        UNPACK_ARGS();
×
1594
                        {
1595
                            Value* list = loadSymbol(primary_arg, context);
×
1596
                            Value tail = helper::tail(list);
×
1597
                            setVal(secondary_arg, &tail, context);
×
1598
                        }
×
1599
                        DISPATCH();
×
1600
                    }
1✔
1601

1602
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1603
                    {
1604
                        UNPACK_ARGS();
1✔
1605
                        {
1606
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1607
                            Value tail = helper::tail(list);
1✔
1608
                            setVal(secondary_arg, &tail, context);
1✔
1609
                        }
1✔
1610
                        DISPATCH();
1✔
1611
                    }
×
1612

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

1624
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1625
                    {
1626
                        UNPACK_ARGS();
1✔
1627
                        {
1628
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1629
                            Value head = helper::head(list);
1✔
1630
                            setVal(secondary_arg, &head, context);
1✔
1631
                        }
1✔
1632
                        DISPATCH();
1✔
1633
                    }
10,704✔
1634

1635
                    TARGET(CALL_BUILTIN)
1636
                    {
1637
                        UNPACK_ARGS();
10,704✔
1638
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1639
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
10,704✔
1640
                        if (!m_running)
10,652✔
1641
                            GOTO_HALT();
×
1642
                        DISPATCH();
10,652✔
1643
                    }
857✔
1644

1645
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1646
                    {
1647
                        UNPACK_ARGS();
857✔
1648
                        const Value* sym = popAndResolveAsPtr(context);
857✔
1649
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
857✔
1650
                            context.ip = secondary_arg * 4;
122✔
1651
                        DISPATCH();
857✔
1652
                    }
21,902✔
1653

1654
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1655
                    {
1656
                        UNPACK_ARGS();
21,902✔
1657
                        const Value* sym = popAndResolveAsPtr(context);
21,902✔
1658
                        if (*sym < *loadConstAsPtr(primary_arg))
21,902✔
1659
                            context.ip = secondary_arg * 4;
10,950✔
1660
                        DISPATCH();
21,902✔
1661
                    }
5,453✔
1662

1663
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1664
                    {
1665
                        UNPACK_ARGS();
5,453✔
1666
                        const Value* sym = popAndResolveAsPtr(context);
5,453✔
1667
                        if (!(*sym < *loadSymbol(primary_arg, context)))
5,453✔
1668
                            context.ip = secondary_arg * 4;
517✔
1669
                        DISPATCH();
5,453✔
1670
                    }
172,494✔
1671

1672
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1673
                    {
1674
                        UNPACK_ARGS();
172,494✔
1675
                        const Value* sym = popAndResolveAsPtr(context);
172,494✔
1676
                        const Value* cst = loadConstAsPtr(primary_arg);
172,494✔
1677
                        if (*cst < *sym)
172,494✔
1678
                            context.ip = secondary_arg * 4;
86,583✔
1679
                        DISPATCH();
172,494✔
1680
                    }
15✔
1681

1682
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1683
                    {
1684
                        UNPACK_ARGS();
15✔
1685
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1686
                        const Value* cst = loadConstAsPtr(primary_arg);
15✔
1687
                        if (!(*cst < *sym))
15✔
1688
                            context.ip = secondary_arg * 4;
3✔
1689
                        DISPATCH();
15✔
1690
                    }
×
1691

1692
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1693
                    {
1694
                        UNPACK_ARGS();
×
1695
                        const Value* sym = popAndResolveAsPtr(context);
×
1696
                        const Value* rhs = loadSymbol(primary_arg, context);
×
1697
                        if (!(*rhs < *sym))
×
1698
                            context.ip = secondary_arg * 4;
×
1699
                        DISPATCH();
×
1700
                    }
1,171✔
1701

1702
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1703
                    {
1704
                        UNPACK_ARGS();
1,171✔
1705
                        const Value* sym = popAndResolveAsPtr(context);
1,171✔
1706
                        if (*sym == *loadConstAsPtr(primary_arg))
1,171✔
1707
                            context.ip = secondary_arg * 4;
184✔
1708
                        DISPATCH();
1,171✔
1709
                    }
86,423✔
1710

1711
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1712
                    {
1713
                        UNPACK_ARGS();
86,423✔
1714
                        const Value* sym = popAndResolveAsPtr(context);
86,423✔
1715
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
86,423✔
1716
                            context.ip = secondary_arg * 4;
528✔
1717
                        DISPATCH();
86,423✔
1718
                    }
1✔
1719

1720
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1721
                    {
1722
                        UNPACK_ARGS();
1✔
1723
                        const Value* sym = popAndResolveAsPtr(context);
1✔
1724
                        if (*sym != *loadConstAsPtr(primary_arg))
1✔
1725
                            context.ip = secondary_arg * 4;
1✔
1726
                        DISPATCH();
1✔
1727
                    }
15✔
1728

1729
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1730
                    {
1731
                        UNPACK_ARGS();
15✔
1732
                        const Value* sym = popAndResolveAsPtr(context);
15✔
1733
                        if (*sym == *loadSymbol(primary_arg, context))
15✔
1734
                            context.ip = secondary_arg * 4;
5✔
1735
                        DISPATCH();
15✔
1736
                    }
121,598✔
1737

1738
                    TARGET(CALL_SYMBOL)
1739
                    {
1740
                        UNPACK_ARGS();
121,598✔
1741
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
121,598✔
1742
                        if (!m_running)
121,597✔
1743
                            GOTO_HALT();
×
1744
                        DISPATCH();
121,597✔
1745
                    }
1,453✔
1746

1747
                    TARGET(GET_FIELD_FROM_SYMBOL)
1748
                    {
1749
                        UNPACK_ARGS();
1,453✔
1750
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
1,453✔
1751
                        DISPATCH();
1,453✔
1752
                    }
187✔
1753

1754
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1755
                    {
1756
                        UNPACK_ARGS();
187✔
1757
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
190✔
1758
                        DISPATCH();
184✔
1759
                    }
13,541✔
1760

1761
                    TARGET(AT_SYM_SYM)
1762
                    {
1763
                        UNPACK_ARGS();
13,541✔
1764
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
13,541✔
1765
                        DISPATCH();
13,541✔
1766
                    }
×
1767

1768
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1769
                    {
1770
                        UNPACK_ARGS();
×
1771
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
×
1772
                        DISPATCH();
×
1773
                    }
5✔
1774

1775
                    TARGET(CHECK_TYPE_OF)
1776
                    {
1777
                        UNPACK_ARGS();
5✔
1778
                        Value* sym = loadSymbol(primary_arg, context);
5✔
1779
                        Value* cst = loadConstAsPtr(secondary_arg);
5✔
1780
                        push(
5✔
1781
                            cst->valueType() == ValueType::String &&
10✔
1782
                                    types_to_str[static_cast<unsigned>(sym->valueType())] == cst->string()
5✔
1783
                                ? Builtins::trueSym
1784
                                : Builtins::falseSym,
1785
                            context);
5✔
1786
                        DISPATCH();
5✔
1787
                    }
68✔
1788

1789
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1790
                    {
1791
                        UNPACK_ARGS();
68✔
1792
                        Value* sym = loadSymbolFromIndex(primary_arg, context);
68✔
1793
                        Value* cst = loadConstAsPtr(secondary_arg);
68✔
1794
                        push(
68✔
1795
                            cst->valueType() == ValueType::String &&
136✔
1796
                                    types_to_str[static_cast<unsigned>(sym->valueType())] == cst->string()
68✔
1797
                                ? Builtins::trueSym
1798
                                : Builtins::falseSym,
1799
                            context);
68✔
1800
                        DISPATCH();
68✔
1801
                    }
1,293✔
1802

1803
                    TARGET(APPEND_IN_PLACE_SYM)
1804
                    {
1805
                        UNPACK_ARGS();
1,293✔
1806
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1,293✔
1807
                        DISPATCH();
1,293✔
1808
                    }
12✔
1809

1810
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1811
                    {
1812
                        UNPACK_ARGS();
12✔
1813
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
12✔
1814
                        DISPATCH();
11✔
1815
                    }
1816
#pragma endregion
1817
                }
60✔
1818
#if ARK_USE_COMPUTED_GOTOS
1819
            dispatch_end:
1820
                do
60✔
1821
                {
1822
                } while (false);
60✔
1823
#endif
1824
            }
1825
        }
162✔
1826
        catch (const Error& e)
1827
        {
1828
            if (fail_with_exception)
69✔
1829
            {
1830
                std::stringstream stream;
69✔
1831
                backtrace(context, stream, /* colorize= */ false);
69✔
1832
                // It's important we have an Ark::Error here, as the constructor for NestedError
1833
                // does more than just aggregate error messages, hence the code duplication.
1834
                throw NestedError(e, stream.str());
69✔
1835
            }
69✔
1836
            else
1837
                showBacktraceWithException(Error(e.details(/* colorize= */ true)), context);
×
1838
        }
129✔
1839
        catch (const std::exception& e)
1840
        {
1841
            if (fail_with_exception)
33✔
1842
            {
1843
                std::stringstream stream;
33✔
1844
                backtrace(context, stream, /* colorize= */ false);
33✔
1845
                throw NestedError(e, stream.str());
33✔
1846
            }
33✔
1847
            else
1848
                showBacktraceWithException(e, context);
×
1849
        }
102✔
1850
        catch (...)
1851
        {
1852
            if (fail_with_exception)
×
1853
                throw;
×
1854

1855
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1856
            throw;
1857
#endif
1858
            fmt::println("Unknown error");
×
1859
            backtrace(context);
×
1860
            m_exit_code = 1;
×
1861
        }
135✔
1862

1863
        return m_exit_code;
60✔
1864
    }
204✔
1865

1866
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,047✔
1867
    {
2,047✔
1868
        for (auto& local : std::ranges::reverse_view(context.locals))
4,094✔
1869
        {
1870
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,047✔
1871
                return id;
2,047✔
1872
        }
2,047✔
1873
        return std::numeric_limits<uint16_t>::max();
×
1874
    }
2,047✔
1875

1876
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
3✔
1877
    {
3✔
1878
        std::vector<std::string> arg_names;
3✔
1879
        arg_names.reserve(expected_arg_count + 1);
3✔
1880
        if (expected_arg_count > 0)
3✔
1881
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
3✔
1882

1883
        std::size_t index = 0;
3✔
1884
        while (m_state.m_pages[context.pp][index] == STORE)
10✔
1885
        {
1886
            const auto id = static_cast<uint16_t>((m_state.m_pages[context.pp][index + 2] << 8) + m_state.m_pages[context.pp][index + 3]);
7✔
1887
            arg_names.push_back(m_state.m_symbols[id]);
7✔
1888
            index += 4;
7✔
1889
        }
7✔
1890

1891
        std::vector<std::string> arg_vals;
3✔
1892
        arg_vals.reserve(passed_arg_count + 1);
3✔
1893
        if (passed_arg_count > 0)
3✔
1894
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
3✔
1895

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

1900
        // set ip/pp to the callee location so that the error can pin-point the line
1901
        // where the bad call happened
1902
        if (context.sp >= 2 + passed_arg_count)
3✔
1903
        {
1904
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
3✔
1905
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
3✔
1906
            returnFromFuncCall(context);
3✔
1907
        }
3✔
1908

1909
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
6✔
1910
            ? m_state.m_symbols[context.last_symbol]
3✔
1911
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
1912

1913
        throwVMError(
3✔
1914
            ErrorKind::Arity,
1915
            fmt::format(
6✔
1916
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
3✔
1917
                function_name,
1918
                fmt::join(arg_vals, " "),
3✔
1919
                passed_arg_count,
1920
                passed_arg_count > 1 ? "s" : "",
3✔
1921
                expected_arg_count,
1922
                function_name,
1923
                fmt::join(arg_names, " ")));
3✔
1924
    }
6✔
1925

1926
    void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
×
1927
    {
×
1928
        std::string text = e.what();
×
1929
        if (!text.empty() && text.back() != '\n')
×
1930
            text += '\n';
×
1931
        fmt::println("{}", text);
×
1932
        backtrace(context);
×
1933
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1934
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1935
        m_exit_code = 0;
1936
#else
1937
        m_exit_code = 1;
×
1938
#endif
1939
    }
×
1940

1941
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp)
2,150✔
1942
    {
2,150✔
1943
        std::optional<InstLoc> match = std::nullopt;
2,150✔
1944

1945
        for (const auto location : m_state.m_inst_locations)
8,435✔
1946
        {
1947
            if (location.page_pointer == pp && !match)
6,285✔
1948
                match = location;
2,150✔
1949

1950
            // select the best match: we want to find the location that's nearest our instruction pointer,
1951
            // but not equal to it as the IP will always be pointing to the next instruction,
1952
            // not yet executed. Thus, the erroneous instruction is the previous one.
1953
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
6,285✔
1954
                match = location;
2,181✔
1955

1956
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
1957
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
6,285✔
1958
                break;
8✔
1959
        }
6,285✔
1960

1961
        return match;
2,150✔
1962
    }
1963

1964
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
102✔
1965
    {
102✔
1966
        const std::size_t saved_ip = context.ip;
102✔
1967
        const std::size_t saved_pp = context.pp;
102✔
1968
        const uint16_t saved_sp = context.sp;
102✔
1969

1970
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
102✔
1971
        if (maybe_location)
102✔
1972
        {
1973
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
102✔
1974

1975
            if (Utils::fileExists(filename))
102✔
1976
                Diagnostics::makeContext(
204✔
1977
                    os,
102✔
1978
                    filename,
1979
                    /* expr= */ std::nullopt,
102✔
1980
                    /* sym_size= */ 0,
1981
                    maybe_location->line,
102✔
1982
                    /* col_start= */ 0,
1983
                    /* maybe_context= */ std::nullopt,
102✔
1984
                    /* whole_line= */ true,
1985
                    /* colorize= */ colorize);
102✔
1986
            fmt::println(os, "");
102✔
1987
        }
102✔
1988

1989
        if (context.fc > 1)
102✔
1990
        {
1991
            // display call stack trace
1992
            const ScopeView old_scope = context.locals.back();
1✔
1993

1994
            std::string previous_trace;
1✔
1995
            std::size_t displayed_traces = 0;
1✔
1996
            std::size_t consecutive_similar_traces = 0;
1✔
1997

1998
            while (context.fc != 0)
2,048✔
1999
            {
2000
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,048✔
2001
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
2,048✔
2002

2003
                if (context.pp != 0)
2,048✔
2004
                {
2005
                    const uint16_t id = findNearestVariableIdWithValue(
2,047✔
2006
                        Value(static_cast<PageAddr_t>(context.pp)),
2,047✔
2007
                        context);
2,047✔
2008
                    const auto func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,047✔
2009

2010
                    if (func_name + loc_as_text != previous_trace)
2,047✔
2011
                    {
2012
                        fmt::println(
2✔
2013
                            os,
1✔
2014
                            "[{:4}] In function `{}'{}",
1✔
2015
                            fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2016
                            fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
1✔
2017
                            loc_as_text);
2018
                        previous_trace = func_name + loc_as_text;
1✔
2019
                        ++displayed_traces;
1✔
2020
                        consecutive_similar_traces = 0;
1✔
2021
                    }
1✔
2022
                    else if (consecutive_similar_traces == 0)
2,046✔
2023
                    {
2024
                        fmt::println(os, "       ...");
1✔
2025
                        ++consecutive_similar_traces;
1✔
2026
                    }
1✔
2027

2028
                    const Value* ip;
2,047✔
2029
                    do
2,048✔
2030
                    {
2031
                        ip = popAndResolveAsPtr(context);
2,048✔
2032
                    } while (ip->valueType() != ValueType::InstPtr);
2,048✔
2033

2034
                    context.ip = ip->pageAddr();
2,047✔
2035
                    context.pp = pop(context)->pageAddr();
2,047✔
2036
                    returnFromFuncCall(context);
2,047✔
2037
                }
2,047✔
2038
                else
2039
                {
2040
                    fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
1✔
2041
                    break;
1✔
2042
                }
2043

2044
                if (displayed_traces > 7)
2,047✔
2045
                {
2046
                    fmt::println(os, "...");
×
2047
                    break;
×
2048
                }
2049
            }
2,048✔
2050

2051
            // display variables values in the current scope
2052
            fmt::println(os, "\nCurrent scope variables values:");
1✔
2053
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
3✔
2054
            {
2055
                fmt::println(
4✔
2056
                    os,
2✔
2057
                    "{} = {}",
2✔
2058
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
2✔
2059
                    old_scope.atPos(i).second.toString(*this));
2✔
2060
            }
2✔
2061
        }
1✔
2062

2063
        fmt::println(
204✔
2064
            os,
102✔
2065
            "At IP: {}, PP: {}, SP: {}",
102✔
2066
            // dividing by 4 because the instructions are actually on 4 bytes
2067
            fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
102✔
2068
            fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
102✔
2069
            fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
102✔
2070
    }
102✔
2071
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc