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

ArkScript-lang / Ark / 20376531327

19 Dec 2025 04:48PM UTC coverage: 90.499% (-0.2%) from 90.661%
20376531327

Pull #623

github

web-flow
Merge ba2705da1 into c2be10ac3
Pull Request #623: feat(iroptimizer, vm): …

64 of 87 new or added lines in 7 files covered. (73.56%)

1 existing line in 1 file now uncovered.

8258 of 9125 relevant lines covered (90.5%)

244847.38 hits per line

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

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

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

9
#include <Ark/Utils/Files.hpp>
10
#include <Ark/Utils/Utils.hpp>
11
#include <Ark/Error/Diagnostics.hpp>
12
#include <Ark/TypeChecker.hpp>
13
#include <Ark/VM/ModuleMapping.hpp>
14
#include <Ark/Compiler/Instructions.hpp>
15

16
namespace Ark
17
{
18
    using namespace internal;
19

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

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

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

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

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

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

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

82
            const auto num = static_cast<long>(index.number());
17,319✔
83

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

112
        inline double doMath(double a, double b, const Instruction op)
2,244✔
113
        {
2,244✔
114
            if (op == ADD)
2,244✔
115
                a += b;
69✔
116
            else if (op == SUB)
2,175✔
117
                a -= b;
39✔
118
            else if (op == MUL)
2,136✔
119
                a *= b;
1,127✔
120
            else if (op == DIV)
1,009✔
121
            {
122
                if (b == 0)
1,009✔
NEW
123
                    Ark::VM::throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a, b));
×
124
                a /= b;
1,009✔
125
            }
1,009✔
126

127
            return a;
2,244✔
NEW
128
        }
×
129
    }
130

131
    VM::VM(State& state) noexcept :
636✔
132
        m_state(state), m_exit_code(0), m_running(false)
212✔
133
    {
212✔
134
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
212✔
135
    }
212✔
136

137
    void VM::init() noexcept
206✔
138
    {
206✔
139
        ExecutionContext& context = *m_execution_contexts.back();
206✔
140
        for (const auto& c : m_execution_contexts)
412✔
141
        {
142
            c->ip = 0;
206✔
143
            c->pp = 0;
206✔
144
            c->sp = 0;
206✔
145
        }
206✔
146

147
        context.sp = 0;
206✔
148
        context.fc = 1;
206✔
149

150
        m_shared_lib_objects.clear();
206✔
151
        context.stacked_closure_scopes.clear();
206✔
152
        context.stacked_closure_scopes.emplace_back(nullptr);
206✔
153

154
        context.saved_scope.reset();
206✔
155
        m_exit_code = 0;
206✔
156

157
        context.locals.clear();
206✔
158
        context.locals.reserve(128);
206✔
159
        context.locals.emplace_back(context.scopes_storage.data(), 0);
206✔
160

161
        // loading bound stuff
162
        // put them in the global frame if we can, aka the first one
163
        for (const auto& [sym_id, value] : m_state.m_binded)
644✔
164
        {
165
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
420✔
166
            if (it != m_state.m_symbols.end())
420✔
167
                context.locals[0].pushBack(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
18✔
168
        }
420✔
169
    }
206✔
170

171
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context)
3,759✔
172
    {
3,759✔
173
        if (closure->valueType() != ValueType::Closure)
3,759✔
174
        {
175
            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
176
                throwVMError(
2✔
177
                    ErrorKind::Type,
178
                    fmt::format(
3✔
179
                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
213✔
180
                        m_state.m_symbols[context.last_symbol],
1✔
181
                        std::to_string(closure->valueType()),
1✔
182
                        m_state.m_symbols[id]));
1✔
183
            else
184
                throwVMError(ErrorKind::Type,
×
185
                             fmt::format(
×
186
                                 "{} is not a Closure, can not get the field `{}' from it",
×
187
                                 std::to_string(closure->valueType()),
×
188
                                 m_state.m_symbols[id]));
×
189
        }
190

191
        if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
7,516✔
192
        {
193
            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
194
            if (m_state.inst(context.pp, context.ip) == CALL)
3,757✔
195
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
2,109✔
196
            else
197
                return *field;
1,648✔
198
        }
199
        else
200
        {
201
            if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
1✔
202
                throwVMError(
1✔
203
                    ErrorKind::Scope,
204
                    fmt::format(
2✔
205
                        "`{0}' isn't in the closure environment: {1}",
1✔
206
                        m_state.m_symbols[id],
1✔
207
                        closure->refClosure().toString(*this)));
1✔
208
            throwVMError(
×
209
                ErrorKind::Scope,
210
                fmt::format(
×
211
                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
×
212
                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
213
                    m_state.m_symbols[id],
×
214
                    closure->refClosure().toString(*this)));
×
215
        }
216
    }
3,759✔
217

218
    Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
1,755✔
219
    {
1,755✔
220
        Value l(ValueType::List);
1,755✔
221
        if (count != 0)
1,755✔
222
            l.list().reserve(count);
689✔
223

224
        for (std::size_t i = 0; i < count; ++i)
3,538✔
225
            l.push_back(*popAndResolveAsPtr(context));
1,783✔
226

227
        return l;
1,755✔
228
    }
1,755✔
229

230
    void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
1,871✔
231
    {
1,871✔
232
        if (list->valueType() != ValueType::List)
1,871✔
233
        {
234
            std::vector<Value> args = { *list };
1✔
235
            for (std::size_t i = 0; i < count; ++i)
2✔
236
                args.push_back(*popAndResolveAsPtr(context));
1✔
237
            throw types::TypeCheckingError(
2✔
238
                "append!",
1✔
239
                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
240
                args);
241
        }
1✔
242

243
        for (std::size_t i = 0; i < count; ++i)
3,740✔
244
            list->push_back(*popAndResolveAsPtr(context));
1,870✔
245
    }
1,871✔
246

247
    Value& VM::operator[](const std::string& name) noexcept
38✔
248
    {
38✔
249
        // find id of object
250
        const auto it = std::ranges::find(m_state.m_symbols, name);
38✔
251
        if (it == m_state.m_symbols.end())
38✔
252
        {
253
            m_no_value = Builtins::nil;
3✔
254
            return m_no_value;
3✔
255
        }
256

257
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
35✔
258
        if (std::cmp_less(dist, MaxValue16Bits))
35✔
259
        {
260
            ExecutionContext& context = *m_execution_contexts.front();
35✔
261

262
            const auto id = static_cast<uint16_t>(dist);
35✔
263
            Value* var = findNearestVariable(id, context);
35✔
264
            if (var != nullptr)
35✔
265
                return *var;
35✔
266
        }
35✔
267

268
        m_no_value = Builtins::nil;
×
269
        return m_no_value;
×
270
    }
38✔
271

272
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
1✔
273
    {
1✔
274
        namespace fs = std::filesystem;
275

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

278
        std::string path = file;
1✔
279
        // bytecode loaded from file
280
        if (m_state.m_filename != ARK_NO_NAME_FILE)
1✔
281
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
1✔
282

283
        std::shared_ptr<SharedLibrary> lib;
1✔
284
        // if it exists alongside the .arkc file
285
        if (Utils::fileExists(path))
1✔
286
            lib = std::make_shared<SharedLibrary>(path);
×
287
        else
288
        {
289
            for (auto const& v : m_state.m_libenv)
3✔
290
            {
291
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
2✔
292

293
                // if it's already loaded don't do anything
294
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
2✔
295
                        return (val->path() == path || val->path() == lib_path);
×
296
                    }) != m_shared_lib_objects.end())
2✔
297
                    return;
×
298

299
                // check in lib_path
300
                if (Utils::fileExists(lib_path))
2✔
301
                {
302
                    lib = std::make_shared<SharedLibrary>(lib_path);
1✔
303
                    break;
1✔
304
                }
305
            }
2✔
306
        }
307

308
        if (!lib)
1✔
309
        {
310
            auto lib_path = std::accumulate(
×
311
                std::next(m_state.m_libenv.begin()),
×
312
                m_state.m_libenv.end(),
×
313
                m_state.m_libenv[0].string(),
×
314
                [](const std::string& a, const fs::path& b) -> std::string {
×
315
                    return a + "\n\t- " + b.string();
×
316
                });
×
317
            throwVMError(
×
318
                ErrorKind::Module,
319
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
320
        }
×
321

322
        m_shared_lib_objects.emplace_back(lib);
1✔
323

324
        // load the mapping from the dynamic library
325
        try
326
        {
327
            std::vector<ScopeView::pair_t> data;
1✔
328
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
1✔
329

330
            std::size_t i = 0;
1✔
331
            while (map[i].name != nullptr)
2✔
332
            {
333
                const auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
1✔
334
                if (it != m_state.m_symbols.end())
1✔
335
                    data.emplace_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
1✔
336

337
                ++i;
1✔
338
            }
1✔
339

340
            context.locals.back().insertFront(data);
1✔
341
        }
1✔
342
        catch (const std::system_error& e)
343
        {
344
            throwVMError(
×
345
                ErrorKind::Module,
346
                fmt::format(
×
347
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
348
                    file, e.what()));
×
349
        }
1✔
350
    }
1✔
351

352
    void VM::exit(const int code) noexcept
×
353
    {
×
354
        m_exit_code = code;
×
355
        m_running = false;
×
356
    }
×
357

358
    ExecutionContext* VM::createAndGetContext()
17✔
359
    {
17✔
360
        const std::lock_guard lock(m_mutex);
17✔
361

362
        ExecutionContext* ctx = nullptr;
17✔
363

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

369
        if (m_execution_contexts.size() > 1)
17✔
370
        {
371
            const auto it = std::ranges::find_if(
28✔
372
                m_execution_contexts,
14✔
373
                [](const std::unique_ptr<ExecutionContext>& context) -> bool {
38✔
374
                    return !context->primary && context->isFree();
38✔
375
                });
376

377
            if (it != m_execution_contexts.end())
14✔
378
            {
379
                ctx = it->get();
10✔
380
                ctx->setActive(true);
10✔
381
                // reset the context before using it
382
                ctx->sp = 0;
10✔
383
                ctx->saved_scope.reset();
10✔
384
                ctx->stacked_closure_scopes.clear();
10✔
385
                ctx->locals.clear();
10✔
386
            }
10✔
387
        }
14✔
388

389
        if (ctx == nullptr)
17✔
390
            ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
7✔
391

392
        assert(!ctx->primary && "The new context shouldn't be marked as primary!");
17✔
393
        assert(ctx != m_execution_contexts.front().get() && "The new context isn't really new!");
17✔
394

395
        const ExecutionContext& primary_ctx = *m_execution_contexts.front();
17✔
396
        ctx->locals.reserve(primary_ctx.locals.size());
17✔
397
        ctx->scopes_storage = primary_ctx.scopes_storage;
17✔
398
        ctx->stacked_closure_scopes.emplace_back(nullptr);
17✔
399
        ctx->fc = 1;
17✔
400

401
        for (const auto& scope_view : primary_ctx.locals)
62✔
402
        {
403
            auto& new_scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), scope_view.m_start);
45✔
404
            for (std::size_t i = 0; i < scope_view.size(); ++i)
2,951✔
405
            {
406
                const auto& [id, val] = scope_view.atPos(i);
2,906✔
407
                new_scope.pushBack(id, val);
2,906✔
408
            }
2,906✔
409
        }
45✔
410

411
        return ctx;
17✔
412
    }
17✔
413

414
    void VM::deleteContext(ExecutionContext* ec)
16✔
415
    {
16✔
416
        const std::lock_guard lock(m_mutex);
16✔
417

418
        // 1 + 4 additional contexts, it's a bit much (~600kB per context) to have in memory
419
        if (m_execution_contexts.size() > 5)
16✔
420
        {
421
            const auto it =
1✔
422
                std::ranges::remove_if(
2✔
423
                    m_execution_contexts,
1✔
424
                    [ec](const std::unique_ptr<ExecutionContext>& ctx) {
7✔
425
                        return ctx.get() == ec;
6✔
426
                    })
427
                    .begin();
1✔
428
            m_execution_contexts.erase(it);
1✔
429
        }
1✔
430
        else
431
        {
432
            // mark the used context as ready to be used again
433
            for (std::size_t i = 1; i < m_execution_contexts.size(); ++i)
40✔
434
            {
435
                if (m_execution_contexts[i].get() == ec)
25✔
436
                {
437
                    ec->setActive(false);
15✔
438
                    break;
15✔
439
                }
440
            }
10✔
441
        }
442
    }
16✔
443

444
    Future* VM::createFuture(std::vector<Value>& args)
17✔
445
    {
17✔
446
        const std::lock_guard lock(m_mutex_futures);
17✔
447

448
        ExecutionContext* ctx = createAndGetContext();
17✔
449
        // so that we have access to the presumed symbol id of the function we are calling
450
        // assuming that the callee is always the global context
451
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
17✔
452

453
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
17✔
454
        return m_futures.back().get();
17✔
455
    }
17✔
456

457
    void VM::deleteFuture(Future* f)
1✔
458
    {
1✔
459
        const std::lock_guard lock(m_mutex_futures);
1✔
460

461
        std::erase_if(
1✔
462
            m_futures,
1✔
463
            [f](const std::unique_ptr<Future>& future) {
3✔
464
                return future.get() == f;
2✔
465
            });
466
    }
1✔
467

468
    bool VM::forceReloadPlugins() const
×
469
    {
×
470
        // load the mapping from the dynamic library
471
        try
472
        {
473
            for (const auto& shared_lib : m_shared_lib_objects)
×
474
            {
475
                const mapping* map = shared_lib->get<mapping* (*)()>("getFunctionsMapping")();
×
476
                // load the mapping data
477
                std::size_t i = 0;
×
478
                while (map[i].name != nullptr)
×
479
                {
480
                    // put it in the global frame, aka the first one
481
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
482
                    if (it != m_state.m_symbols.end())
×
483
                        m_execution_contexts[0]->locals[0].pushBack(
×
484
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
485
                            Value(map[i].value));
×
486

487
                    ++i;
×
488
                }
×
489
            }
×
490

491
            return true;
×
492
        }
×
493
        catch (const std::system_error&)
494
        {
495
            return false;
×
496
        }
×
497
    }
×
498

499
    void VM::throwVMError(ErrorKind kind, const std::string& message)
32✔
500
    {
32✔
501
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
32✔
502
    }
32✔
503

504
    int VM::run(const bool fail_with_exception)
206✔
505
    {
206✔
506
        init();
206✔
507
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
206✔
508
        return m_exit_code;
206✔
509
    }
510

511
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
226✔
512
    {
226✔
513
#if ARK_USE_COMPUTED_GOTOS
514
#    define TARGET(op) TARGET_##op:
515
#    define DISPATCH_GOTO()            \
516
        _Pragma("GCC diagnostic push") \
517
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
518
        _Pragma("GCC diagnostic pop")
519
#    define GOTO_HALT() goto dispatch_end
520
#else
521
#    define TARGET(op) case op:
522
#    define DISPATCH_GOTO() goto dispatch_opcode
523
#    define GOTO_HALT() break
524
#endif
525

526
#define NEXTOPARG()                                                                                                               \
527
    do                                                                                                                            \
528
    {                                                                                                                             \
529
        inst = m_state.inst(context.pp, context.ip);                                                                              \
530
        padding = m_state.inst(context.pp, context.ip + 1);                                                                       \
531
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) +                                             \
532
                                    m_state.inst(context.pp, context.ip + 3));                                                    \
533
        context.ip += 4;                                                                                                          \
534
        context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize;                                       \
535
        if (context.inst_exec_counter < 2 && context.sp >= VMStackSize)                                                           \
536
        {                                                                                                                         \
537
            if (context.pp != 0)                                                                                                  \
538
                throw Error("Stack overflow. You could consider rewriting your function to make use of tail-call optimization."); \
539
            else                                                                                                                  \
540
                throw Error("Stack overflow. Are you trying to call a function with too many arguments?");                        \
541
        }                                                                                                                         \
542
    } while (false)
543
#define DISPATCH() \
544
    NEXTOPARG();   \
545
    DISPATCH_GOTO();
546
#define UNPACK_ARGS()                                                                 \
547
    do                                                                                \
548
    {                                                                                 \
549
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
550
        primary_arg = arg & 0x0fff;                                                   \
551
    } while (false)
552

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

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

676
        try
677
        {
678
            uint8_t inst = 0;
226✔
679
            uint8_t padding = 0;
226✔
680
            uint16_t arg = 0;
226✔
681
            uint16_t primary_arg = 0;
226✔
682
            uint16_t secondary_arg = 0;
226✔
683

684
            m_running = true;
226✔
685

686
            DISPATCH();
226✔
687
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
688
            {
689
#if !ARK_USE_COMPUTED_GOTOS
690
            dispatch_opcode:
691
                switch (inst)
692
#endif
693
                {
×
694
#pragma region "Instructions"
695
                    TARGET(NOP)
696
                    {
697
                        DISPATCH();
×
698
                    }
149,699✔
699

700
                    TARGET(LOAD_SYMBOL)
701
                    {
702
                        push(loadSymbol(arg, context), context);
149,699✔
703
                        DISPATCH();
149,697✔
704
                    }
335,446✔
705

706
                    TARGET(LOAD_SYMBOL_BY_INDEX)
707
                    {
708
                        push(loadSymbolFromIndex(arg, context), context);
335,446✔
709
                        DISPATCH();
335,446✔
710
                    }
116,291✔
711

712
                    TARGET(LOAD_CONST)
713
                    {
714
                        push(loadConstAsPtr(arg), context);
116,291✔
715
                        DISPATCH();
116,291✔
716
                    }
30,663✔
717

718
                    TARGET(POP_JUMP_IF_TRUE)
719
                    {
720
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
38,950✔
721
                            jump(arg, context);
8,287✔
722
                        DISPATCH();
30,663✔
723
                    }
403,085✔
724

725
                    TARGET(STORE)
726
                    {
727
                        store(arg, popAndResolveAsPtr(context), context);
403,085✔
728
                        DISPATCH();
403,085✔
729
                    }
406✔
730

731
                    TARGET(STORE_REF)
732
                    {
733
                        // Not resolving a potential ref is on purpose!
734
                        // This instruction is only used by functions when storing arguments
735
                        Value* tmp = pop(context);
406✔
736
                        store(arg, tmp, context);
406✔
737
                        DISPATCH();
406✔
738
                    }
25,127✔
739

740
                    TARGET(SET_VAL)
741
                    {
742
                        setVal(arg, popAndResolveAsPtr(context), context);
25,127✔
743
                        DISPATCH();
25,127✔
744
                    }
18,965✔
745

746
                    TARGET(POP_JUMP_IF_FALSE)
747
                    {
748
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
20,029✔
749
                            jump(arg, context);
1,064✔
750
                        DISPATCH();
18,965✔
751
                    }
208,800✔
752

753
                    TARGET(JUMP)
754
                    {
755
                        jump(arg, context);
208,800✔
756
                        DISPATCH();
208,800✔
757
                    }
138,851✔
758

759
                    TARGET(RET)
760
                    {
761
                        {
762
                            Value ip_or_val = *popAndResolveAsPtr(context);
138,851✔
763
                            // no return value on the stack
764
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
138,851✔
765
                            {
766
                                context.ip = ip_or_val.pageAddr();
3,708✔
767
                                // we always push PP then IP, thus the next value
768
                                // MUST be the page pointer
769
                                context.pp = pop(context)->pageAddr();
3,708✔
770

771
                                returnFromFuncCall(context);
3,708✔
772
                                push(Builtins::nil, context);
3,708✔
773
                            }
3,708✔
774
                            // value on the stack
775
                            else [[likely]]
776
                            {
777
                                const Value* ip = popAndResolveAsPtr(context);
135,143✔
778
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
135,143✔
779
                                context.ip = ip->pageAddr();
135,143✔
780
                                context.pp = pop(context)->pageAddr();
135,143✔
781

782
                                returnFromFuncCall(context);
135,143✔
783
                                push(std::move(ip_or_val), context);
135,143✔
784
                            }
785

786
                            if (context.fc <= untilFrameCount)
138,851✔
787
                                GOTO_HALT();
18✔
788
                        }
138,851✔
789

790
                        DISPATCH();
138,833✔
791
                    }
72✔
792

793
                    TARGET(HALT)
794
                    {
795
                        m_running = false;
72✔
796
                        GOTO_HALT();
72✔
797
                    }
141,901✔
798

799
                    TARGET(PUSH_RETURN_ADDRESS)
800
                    {
801
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
141,901✔
802
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
803
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
141,901✔
804
                        context.inst_exec_counter++;
141,901✔
805
                        DISPATCH();
141,901✔
806
                    }
3,240✔
807

808
                    TARGET(CALL)
809
                    {
810
                        call(context, arg);
3,240✔
811
                        if (!m_running)
3,233✔
812
                            GOTO_HALT();
×
813
                        DISPATCH();
3,233✔
814
                    }
3,191✔
815

816
                    TARGET(CAPTURE)
817
                    {
818
                        if (!context.saved_scope)
3,191✔
819
                            context.saved_scope = ClosureScope();
631✔
820

821
                        const Value* ptr = findNearestVariable(arg, context);
3,191✔
822
                        if (!ptr)
3,191✔
823
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
824
                        else
825
                        {
826
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,191✔
827
                            uint16_t id = context.capture_rename_id.value_or(arg);
3,191✔
828
                            context.saved_scope.value().push_back(id, *ptr);
3,191✔
829
                            context.capture_rename_id.reset();
3,191✔
830
                        }
831

832
                        DISPATCH();
3,191✔
833
                    }
13✔
834

835
                    TARGET(RENAME_NEXT_CAPTURE)
836
                    {
837
                        context.capture_rename_id = arg;
13✔
838
                        DISPATCH();
13✔
839
                    }
1,802✔
840

841
                    TARGET(BUILTIN)
842
                    {
843
                        push(Builtins::builtins[arg].second, context);
1,802✔
844
                        DISPATCH();
1,802✔
845
                    }
2✔
846

847
                    TARGET(DEL)
848
                    {
849
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
2✔
850
                        {
851
                            if (var->valueType() == ValueType::User)
1✔
852
                                var->usertypeRef().del();
1✔
853
                            *var = Value();
1✔
854
                            DISPATCH();
1✔
855
                        }
856

857
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
858
                    }
631✔
859

860
                    TARGET(MAKE_CLOSURE)
861
                    {
862
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
631✔
863
                        context.saved_scope.reset();
631✔
864
                        DISPATCH();
631✔
865
                    }
6✔
866

867
                    TARGET(GET_FIELD)
868
                    {
869
                        Value* var = popAndResolveAsPtr(context);
6✔
870
                        push(getField(var, arg, context), context);
6✔
871
                        DISPATCH();
6✔
872
                    }
1✔
873

874
                    TARGET(PLUGIN)
875
                    {
876
                        loadPlugin(arg, context);
1✔
877
                        DISPATCH();
1✔
878
                    }
773✔
879

880
                    TARGET(LIST)
881
                    {
882
                        {
883
                            Value l = createList(arg, context);
773✔
884
                            push(std::move(l), context);
773✔
885
                        }
773✔
886
                        DISPATCH();
773✔
887
                    }
1,552✔
888

889
                    TARGET(APPEND)
890
                    {
891
                        {
892
                            Value* list = popAndResolveAsPtr(context);
1,552✔
893
                            if (list->valueType() != ValueType::List)
1,552✔
894
                            {
895
                                std::vector<Value> args = { *list };
1✔
896
                                for (uint16_t i = 0; i < arg; ++i)
2✔
897
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
898
                                throw types::TypeCheckingError(
2✔
899
                                    "append",
1✔
900
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* variadic= */ true) } } } },
1✔
901
                                    args);
902
                            }
1✔
903

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

906
                            Value obj { *list };
1,551✔
907
                            obj.list().reserve(size + arg);
1,551✔
908

909
                            for (uint16_t i = 0; i < arg; ++i)
3,102✔
910
                                obj.push_back(*popAndResolveAsPtr(context));
1,551✔
911
                            push(std::move(obj), context);
1,551✔
912
                        }
1,551✔
913
                        DISPATCH();
1,551✔
914
                    }
15✔
915

916
                    TARGET(CONCAT)
917
                    {
918
                        {
919
                            Value* list = popAndResolveAsPtr(context);
15✔
920
                            Value obj { *list };
15✔
921

922
                            for (uint16_t i = 0; i < arg; ++i)
30✔
923
                            {
924
                                Value* next = popAndResolveAsPtr(context);
17✔
925

926
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
17✔
927
                                    throw types::TypeCheckingError(
4✔
928
                                        "concat",
2✔
929
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
930
                                        { *list, *next });
2✔
931

932
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
15✔
933
                            }
15✔
934
                            push(std::move(obj), context);
13✔
935
                        }
15✔
936
                        DISPATCH();
13✔
937
                    }
1✔
938

939
                    TARGET(APPEND_IN_PLACE)
940
                    {
941
                        Value* list = popAndResolveAsPtr(context);
1✔
942
                        listAppendInPlace(list, arg, context);
1✔
943
                        DISPATCH();
1✔
944
                    }
570✔
945

946
                    TARGET(CONCAT_IN_PLACE)
947
                    {
948
                        Value* list = popAndResolveAsPtr(context);
570✔
949

950
                        for (uint16_t i = 0; i < arg; ++i)
1,175✔
951
                        {
952
                            Value* next = popAndResolveAsPtr(context);
607✔
953

954
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
607✔
955
                                throw types::TypeCheckingError(
4✔
956
                                    "concat!",
2✔
957
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
958
                                    { *list, *next });
2✔
959

960
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
605✔
961
                        }
605✔
962
                        DISPATCH();
568✔
963
                    }
6✔
964

965
                    TARGET(POP_LIST)
966
                    {
967
                        {
968
                            Value list = *popAndResolveAsPtr(context);
6✔
969
                            Value number = *popAndResolveAsPtr(context);
6✔
970

971
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
6✔
972
                                throw types::TypeCheckingError(
2✔
973
                                    "pop",
1✔
974
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
975
                                    { list, number });
1✔
976

977
                            long idx = static_cast<long>(number.number());
5✔
978
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
5✔
979
                            if (std::cmp_greater_equal(idx, list.list().size()) || idx < 0)
5✔
980
                                throwVMError(
2✔
981
                                    ErrorKind::Index,
982
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
2✔
983

984
                            list.list().erase(list.list().begin() + idx);
3✔
985
                            push(list, context);
3✔
986
                        }
6✔
987
                        DISPATCH();
3✔
988
                    }
202✔
989

990
                    TARGET(POP_LIST_IN_PLACE)
991
                    {
992
                        {
993
                            Value* list = popAndResolveAsPtr(context);
202✔
994
                            Value number = *popAndResolveAsPtr(context);
202✔
995

996
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
202✔
997
                                throw types::TypeCheckingError(
2✔
998
                                    "pop!",
1✔
999
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
1000
                                    { *list, number });
1✔
1001

1002
                            long idx = static_cast<long>(number.number());
201✔
1003
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
201✔
1004
                            if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
201✔
1005
                                throwVMError(
2✔
1006
                                    ErrorKind::Index,
1007
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
1008

1009
                            list->list().erase(list->list().begin() + idx);
199✔
1010
                        }
202✔
1011
                        DISPATCH();
199✔
1012
                    }
490✔
1013

1014
                    TARGET(SET_AT_INDEX)
1015
                    {
1016
                        {
1017
                            Value* list = popAndResolveAsPtr(context);
490✔
1018
                            Value number = *popAndResolveAsPtr(context);
490✔
1019
                            Value new_value = *popAndResolveAsPtr(context);
490✔
1020

1021
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
490✔
1022
                                throw types::TypeCheckingError(
2✔
1023
                                    "@=",
1✔
1024
                                    { { types::Contract {
3✔
1025
                                          { types::Typedef("list", ValueType::List),
3✔
1026
                                            types::Typedef("index", ValueType::Number),
1✔
1027
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
1028
                                      { types::Contract {
1✔
1029
                                          { types::Typedef("string", ValueType::String),
3✔
1030
                                            types::Typedef("index", ValueType::Number),
1✔
1031
                                            types::Typedef("char", ValueType::String) } } } },
1✔
1032
                                    { *list, number, new_value });
1✔
1033

1034
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
489✔
1035
                            long idx = static_cast<long>(number.number());
489✔
1036
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
489✔
1037
                            if (std::cmp_greater_equal(idx, size) || idx < 0)
489✔
1038
                                throwVMError(
2✔
1039
                                    ErrorKind::Index,
1040
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
1041

1042
                            if (list->valueType() == ValueType::List)
487✔
1043
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
1044
                            else
1045
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
1046
                        }
490✔
1047
                        DISPATCH();
487✔
1048
                    }
12✔
1049

1050
                    TARGET(SET_AT_2_INDEX)
1051
                    {
1052
                        {
1053
                            Value* list = popAndResolveAsPtr(context);
12✔
1054
                            Value x = *popAndResolveAsPtr(context);
12✔
1055
                            Value y = *popAndResolveAsPtr(context);
12✔
1056
                            Value new_value = *popAndResolveAsPtr(context);
12✔
1057

1058
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
12✔
1059
                                throw types::TypeCheckingError(
2✔
1060
                                    "@@=",
1✔
1061
                                    { { types::Contract {
2✔
1062
                                        { types::Typedef("list", ValueType::List),
4✔
1063
                                          types::Typedef("x", ValueType::Number),
1✔
1064
                                          types::Typedef("y", ValueType::Number),
1✔
1065
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
1066
                                    { *list, x, y, new_value });
1✔
1067

1068
                            long idx_y = static_cast<long>(x.number());
11✔
1069
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
11✔
1070
                            if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
11✔
1071
                                throwVMError(
2✔
1072
                                    ErrorKind::Index,
1073
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
1074

1075
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
13✔
1076
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
8✔
1077
                                throw types::TypeCheckingError(
2✔
1078
                                    "@@=",
1✔
1079
                                    { { types::Contract {
3✔
1080
                                          { types::Typedef("list", ValueType::List),
4✔
1081
                                            types::Typedef("x", ValueType::Number),
1✔
1082
                                            types::Typedef("y", ValueType::Number),
1✔
1083
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
1084
                                      { types::Contract {
1✔
1085
                                          { types::Typedef("string", ValueType::String),
4✔
1086
                                            types::Typedef("x", ValueType::Number),
1✔
1087
                                            types::Typedef("y", ValueType::Number),
1✔
1088
                                            types::Typedef("char", ValueType::String) } } } },
1✔
1089
                                    { *list, x, y, new_value });
1✔
1090

1091
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
1092
                            const std::size_t size =
8✔
1093
                                is_list
16✔
1094
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
1095
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1096

1097
                            long idx_x = static_cast<long>(y.number());
8✔
1098
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
8✔
1099
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
8✔
1100
                                throwVMError(
2✔
1101
                                    ErrorKind::Index,
1102
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1103

1104
                            if (is_list)
6✔
1105
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1106
                            else
1107
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1108
                        }
12✔
1109
                        DISPATCH();
6✔
1110
                    }
3,584✔
1111

1112
                    TARGET(POP)
1113
                    {
1114
                        pop(context);
3,584✔
1115
                        DISPATCH();
3,584✔
1116
                    }
23,890✔
1117

1118
                    TARGET(SHORTCIRCUIT_AND)
1119
                    {
1120
                        if (!*peekAndResolveAsPtr(context))
23,890✔
1121
                            jump(arg, context);
820✔
1122
                        else
1123
                            pop(context);
23,070✔
1124
                        DISPATCH();
23,890✔
1125
                    }
851✔
1126

1127
                    TARGET(SHORTCIRCUIT_OR)
1128
                    {
1129
                        if (!!*peekAndResolveAsPtr(context))
851✔
1130
                            jump(arg, context);
219✔
1131
                        else
1132
                            pop(context);
632✔
1133
                        DISPATCH();
851✔
1134
                    }
3,071✔
1135

1136
                    TARGET(CREATE_SCOPE)
1137
                    {
1138
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
3,071✔
1139
                        DISPATCH();
3,071✔
1140
                    }
33,061✔
1141

1142
                    TARGET(RESET_SCOPE_JUMP)
1143
                    {
1144
                        context.locals.back().reset();
33,061✔
1145
                        jump(arg, context);
33,061✔
1146
                        DISPATCH();
33,061✔
1147
                    }
3,070✔
1148

1149
                    TARGET(POP_SCOPE)
1150
                    {
1151
                        context.locals.pop_back();
3,070✔
1152
                        DISPATCH();
3,070✔
1153
                    }
×
1154

1155
                    TARGET(GET_CURRENT_PAGE_ADDR)
1156
                    {
1157
                        context.last_symbol = arg;
×
1158
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1159
                        DISPATCH();
×
1160
                    }
28,217✔
1161

1162
#pragma endregion
1163

1164
#pragma region "Operators"
1165

1166
                    TARGET(ADD)
1167
                    {
1168
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,217✔
1169

1170
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
28,217✔
1171
                            push(Value(a->number() + b->number()), context);
19,520✔
1172
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
8,697✔
1173
                            push(Value(a->string() + b->string()), context);
8,696✔
1174
                        else
1175
                            throw types::TypeCheckingError(
2✔
1176
                                "+",
1✔
1177
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1178
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1179
                                { *a, *b });
1✔
1180
                        DISPATCH();
28,216✔
1181
                    }
386✔
1182

1183
                    TARGET(SUB)
1184
                    {
1185
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
386✔
1186

1187
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
386✔
1188
                            throw types::TypeCheckingError(
2✔
1189
                                "-",
1✔
1190
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1191
                                { *a, *b });
1✔
1192
                        push(Value(a->number() - b->number()), context);
385✔
1193
                        DISPATCH();
385✔
1194
                    }
825✔
1195

1196
                    TARGET(MUL)
1197
                    {
1198
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
825✔
1199

1200
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
825✔
1201
                            throw types::TypeCheckingError(
2✔
1202
                                "*",
1✔
1203
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1204
                                { *a, *b });
1✔
1205
                        push(Value(a->number() * b->number()), context);
824✔
1206
                        DISPATCH();
824✔
1207
                    }
141✔
1208

1209
                    TARGET(DIV)
1210
                    {
1211
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
141✔
1212

1213
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
141✔
1214
                            throw types::TypeCheckingError(
2✔
1215
                                "/",
1✔
1216
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1217
                                { *a, *b });
1✔
1218
                        auto d = b->number();
140✔
1219
                        if (d == 0)
140✔
1220
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1221

1222
                        push(Value(a->number() / d), context);
139✔
1223
                        DISPATCH();
139✔
1224
                    }
179✔
1225

1226
                    TARGET(GT)
1227
                    {
1228
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
179✔
1229
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
179✔
1230
                        DISPATCH();
179✔
1231
                    }
20,963✔
1232

1233
                    TARGET(LT)
1234
                    {
1235
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
20,963✔
1236
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
20,963✔
1237
                        DISPATCH();
20,963✔
1238
                    }
7,286✔
1239

1240
                    TARGET(LE)
1241
                    {
1242
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,286✔
1243
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,286✔
1244
                        DISPATCH();
7,286✔
1245
                    }
5,931✔
1246

1247
                    TARGET(GE)
1248
                    {
1249
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,931✔
1250
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,931✔
1251
                        DISPATCH();
5,931✔
1252
                    }
1,197✔
1253

1254
                    TARGET(NEQ)
1255
                    {
1256
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,197✔
1257
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1,197✔
1258
                        DISPATCH();
1,197✔
1259
                    }
18,165✔
1260

1261
                    TARGET(EQ)
1262
                    {
1263
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
18,165✔
1264
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
18,165✔
1265
                        DISPATCH();
18,165✔
1266
                    }
3,911✔
1267

1268
                    TARGET(LEN)
1269
                    {
1270
                        const Value* a = popAndResolveAsPtr(context);
3,911✔
1271

1272
                        if (a->valueType() == ValueType::List)
3,911✔
1273
                            push(Value(static_cast<int>(a->constList().size())), context);
1,527✔
1274
                        else if (a->valueType() == ValueType::String)
2,384✔
1275
                            push(Value(static_cast<int>(a->string().size())), context);
2,383✔
1276
                        else
1277
                            throw types::TypeCheckingError(
2✔
1278
                                "len",
1✔
1279
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1280
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1281
                                { *a });
1✔
1282
                        DISPATCH();
3,910✔
1283
                    }
625✔
1284

1285
                    TARGET(EMPTY)
1286
                    {
1287
                        const Value* a = popAndResolveAsPtr(context);
625✔
1288

1289
                        if (a->valueType() == ValueType::List)
625✔
1290
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
126✔
1291
                        else if (a->valueType() == ValueType::String)
499✔
1292
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
498✔
1293
                        else
1294
                            throw types::TypeCheckingError(
2✔
1295
                                "empty?",
1✔
1296
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1297
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1298
                                { *a });
1✔
1299
                        DISPATCH();
624✔
1300
                    }
335✔
1301

1302
                    TARGET(TAIL)
1303
                    {
1304
                        Value* const a = popAndResolveAsPtr(context);
335✔
1305
                        push(helper::tail(a), context);
335✔
1306
                        DISPATCH();
334✔
1307
                    }
1,128✔
1308

1309
                    TARGET(HEAD)
1310
                    {
1311
                        Value* const a = popAndResolveAsPtr(context);
1,128✔
1312
                        push(helper::head(a), context);
1,128✔
1313
                        DISPATCH();
1,127✔
1314
                    }
2,377✔
1315

1316
                    TARGET(ISNIL)
1317
                    {
1318
                        const Value* a = popAndResolveAsPtr(context);
2,377✔
1319
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,377✔
1320
                        DISPATCH();
2,377✔
1321
                    }
668✔
1322

1323
                    TARGET(ASSERT)
1324
                    {
1325
                        Value* const b = popAndResolveAsPtr(context);
668✔
1326
                        Value* const a = popAndResolveAsPtr(context);
668✔
1327

1328
                        if (b->valueType() != ValueType::String)
668✔
1329
                            throw types::TypeCheckingError(
2✔
1330
                                "assert",
1✔
1331
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
1✔
1332
                                { *a, *b });
1✔
1333

1334
                        if (*a == Builtins::falseSym)
667✔
1335
                            throw AssertionFailed(b->stringRef());
1✔
1336
                        DISPATCH();
666✔
1337
                    }
15✔
1338

1339
                    TARGET(TO_NUM)
1340
                    {
1341
                        const Value* a = popAndResolveAsPtr(context);
15✔
1342

1343
                        if (a->valueType() != ValueType::String)
15✔
1344
                            throw types::TypeCheckingError(
2✔
1345
                                "toNumber",
1✔
1346
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1347
                                { *a });
1✔
1348

1349
                        double val;
1350
                        if (Utils::isDouble(a->string(), &val))
14✔
1351
                            push(Value(val), context);
11✔
1352
                        else
1353
                            push(Builtins::nil, context);
3✔
1354
                        DISPATCH();
14✔
1355
                    }
145✔
1356

1357
                    TARGET(TO_STR)
1358
                    {
1359
                        const Value* a = popAndResolveAsPtr(context);
145✔
1360
                        push(Value(a->toString(*this)), context);
145✔
1361
                        DISPATCH();
145✔
1362
                    }
128✔
1363

1364
                    TARGET(AT)
1365
                    {
1366
                        Value& b = *popAndResolveAsPtr(context);
128✔
1367
                        Value& a = *popAndResolveAsPtr(context);
128✔
1368
                        push(helper::at(a, b, *this), context);
128✔
1369
                        DISPATCH();
126✔
1370
                    }
74✔
1371

1372
                    TARGET(AT_AT)
1373
                    {
1374
                        {
1375
                            const Value* x = popAndResolveAsPtr(context);
74✔
1376
                            const Value* y = popAndResolveAsPtr(context);
74✔
1377
                            Value& list = *popAndResolveAsPtr(context);
74✔
1378

1379
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
74✔
1380
                                list.valueType() != ValueType::List)
73✔
1381
                                throw types::TypeCheckingError(
2✔
1382
                                    "@@",
1✔
1383
                                    { { types::Contract {
2✔
1384
                                        { types::Typedef("src", ValueType::List),
3✔
1385
                                          types::Typedef("y", ValueType::Number),
1✔
1386
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1387
                                    { list, *y, *x });
1✔
1388

1389
                            long idx_y = static_cast<long>(y->number());
73✔
1390
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
73✔
1391
                            if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
73✔
1392
                                throwVMError(
2✔
1393
                                    ErrorKind::Index,
1394
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
2✔
1395

1396
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
71✔
1397
                            const std::size_t size =
71✔
1398
                                is_list
142✔
1399
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
42✔
1400
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
29✔
1401

1402
                            long idx_x = static_cast<long>(x->number());
71✔
1403
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
71✔
1404
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
71✔
1405
                                throwVMError(
2✔
1406
                                    ErrorKind::Index,
1407
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1408

1409
                            if (is_list)
69✔
1410
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
40✔
1411
                            else
1412
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
29✔
1413
                        }
1414
                        DISPATCH();
69✔
1415
                    }
16,403✔
1416

1417
                    TARGET(MOD)
1418
                    {
1419
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
16,403✔
1420
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
16,403✔
1421
                            throw types::TypeCheckingError(
2✔
1422
                                "mod",
1✔
1423
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1424
                                { *a, *b });
1✔
1425
                        push(Value(std::fmod(a->number(), b->number())), context);
16,402✔
1426
                        DISPATCH();
16,402✔
1427
                    }
26✔
1428

1429
                    TARGET(TYPE)
1430
                    {
1431
                        const Value* a = popAndResolveAsPtr(context);
26✔
1432
                        push(Value(std::to_string(a->valueType())), context);
26✔
1433
                        DISPATCH();
26✔
1434
                    }
3✔
1435

1436
                    TARGET(HASFIELD)
1437
                    {
1438
                        {
1439
                            Value* const field = popAndResolveAsPtr(context);
3✔
1440
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1441
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1442
                                throw types::TypeCheckingError(
2✔
1443
                                    "hasField",
1✔
1444
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1445
                                    { *closure, *field });
1✔
1446

1447
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1448
                            if (it == m_state.m_symbols.end())
2✔
1449
                            {
1450
                                push(Builtins::falseSym, context);
1✔
1451
                                DISPATCH();
1✔
1452
                            }
1453

1454
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1455
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1456
                        }
1457
                        DISPATCH();
1✔
1458
                    }
3,698✔
1459

1460
                    TARGET(NOT)
1461
                    {
1462
                        const Value* a = popAndResolveAsPtr(context);
3,698✔
1463
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,698✔
1464
                        DISPATCH();
3,698✔
1465
                    }
8,256✔
1466

1467
#pragma endregion
1468

1469
#pragma region "Super Instructions"
1470
                    TARGET(LOAD_CONST_LOAD_CONST)
1471
                    {
1472
                        UNPACK_ARGS();
8,256✔
1473
                        push(loadConstAsPtr(primary_arg), context);
8,256✔
1474
                        push(loadConstAsPtr(secondary_arg), context);
8,256✔
1475
                        context.inst_exec_counter++;
8,256✔
1476
                        DISPATCH();
8,256✔
1477
                    }
9,865✔
1478

1479
                    TARGET(LOAD_CONST_STORE)
1480
                    {
1481
                        UNPACK_ARGS();
9,865✔
1482
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
9,865✔
1483
                        DISPATCH();
9,865✔
1484
                    }
889✔
1485

1486
                    TARGET(LOAD_CONST_SET_VAL)
1487
                    {
1488
                        UNPACK_ARGS();
889✔
1489
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
889✔
1490
                        DISPATCH();
888✔
1491
                    }
25✔
1492

1493
                    TARGET(STORE_FROM)
1494
                    {
1495
                        UNPACK_ARGS();
25✔
1496
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
25✔
1497
                        DISPATCH();
24✔
1498
                    }
1,215✔
1499

1500
                    TARGET(STORE_FROM_INDEX)
1501
                    {
1502
                        UNPACK_ARGS();
1,215✔
1503
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,215✔
1504
                        DISPATCH();
1,215✔
1505
                    }
627✔
1506

1507
                    TARGET(SET_VAL_FROM)
1508
                    {
1509
                        UNPACK_ARGS();
627✔
1510
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
627✔
1511
                        DISPATCH();
627✔
1512
                    }
505✔
1513

1514
                    TARGET(SET_VAL_FROM_INDEX)
1515
                    {
1516
                        UNPACK_ARGS();
505✔
1517
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
505✔
1518
                        DISPATCH();
505✔
1519
                    }
50✔
1520

1521
                    TARGET(INCREMENT)
1522
                    {
1523
                        UNPACK_ARGS();
50✔
1524
                        {
1525
                            Value* var = loadSymbol(primary_arg, context);
50✔
1526

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

1531
                            if (var->valueType() == ValueType::Number)
50✔
1532
                                push(Value(var->number() + secondary_arg), context);
49✔
1533
                            else
1534
                                throw types::TypeCheckingError(
2✔
1535
                                    "+",
1✔
1536
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1537
                                    { *var, Value(secondary_arg) });
1✔
1538
                        }
1539
                        DISPATCH();
49✔
1540
                    }
88,017✔
1541

1542
                    TARGET(INCREMENT_BY_INDEX)
1543
                    {
1544
                        UNPACK_ARGS();
88,017✔
1545
                        {
1546
                            Value* var = loadSymbolFromIndex(primary_arg, context);
88,017✔
1547

1548
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1549
                            if (var->valueType() == ValueType::Reference)
88,017✔
1550
                                var = var->reference();
×
1551

1552
                            if (var->valueType() == ValueType::Number)
88,017✔
1553
                                push(Value(var->number() + secondary_arg), context);
88,016✔
1554
                            else
1555
                                throw types::TypeCheckingError(
2✔
1556
                                    "+",
1✔
1557
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1558
                                    { *var, Value(secondary_arg) });
1✔
1559
                        }
1560
                        DISPATCH();
88,016✔
1561
                    }
33,038✔
1562

1563
                    TARGET(INCREMENT_STORE)
1564
                    {
1565
                        UNPACK_ARGS();
33,038✔
1566
                        {
1567
                            Value* var = loadSymbol(primary_arg, context);
33,038✔
1568

1569
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1570
                            if (var->valueType() == ValueType::Reference)
33,038✔
1571
                                var = var->reference();
×
1572

1573
                            if (var->valueType() == ValueType::Number)
33,038✔
1574
                            {
1575
                                auto val = Value(var->number() + secondary_arg);
33,037✔
1576
                                setVal(primary_arg, &val, context);
33,037✔
1577
                            }
33,037✔
1578
                            else
1579
                                throw types::TypeCheckingError(
2✔
1580
                                    "+",
1✔
1581
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1582
                                    { *var, Value(secondary_arg) });
1✔
1583
                        }
1584
                        DISPATCH();
33,037✔
1585
                    }
1,840✔
1586

1587
                    TARGET(DECREMENT)
1588
                    {
1589
                        UNPACK_ARGS();
1,840✔
1590
                        {
1591
                            Value* var = loadSymbol(primary_arg, context);
1,840✔
1592

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

1597
                            if (var->valueType() == ValueType::Number)
1,840✔
1598
                                push(Value(var->number() - secondary_arg), context);
1,839✔
1599
                            else
1600
                                throw types::TypeCheckingError(
2✔
1601
                                    "-",
1✔
1602
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1603
                                    { *var, Value(secondary_arg) });
1✔
1604
                        }
1605
                        DISPATCH();
1,839✔
1606
                    }
194,412✔
1607

1608
                    TARGET(DECREMENT_BY_INDEX)
1609
                    {
1610
                        UNPACK_ARGS();
194,412✔
1611
                        {
1612
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,412✔
1613

1614
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1615
                            if (var->valueType() == ValueType::Reference)
194,412✔
1616
                                var = var->reference();
×
1617

1618
                            if (var->valueType() == ValueType::Number)
194,412✔
1619
                                push(Value(var->number() - secondary_arg), context);
194,411✔
1620
                            else
1621
                                throw types::TypeCheckingError(
2✔
1622
                                    "-",
1✔
1623
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1624
                                    { *var, Value(secondary_arg) });
1✔
1625
                        }
1626
                        DISPATCH();
194,411✔
1627
                    }
957✔
1628

1629
                    TARGET(DECREMENT_STORE)
1630
                    {
1631
                        UNPACK_ARGS();
957✔
1632
                        {
1633
                            Value* var = loadSymbol(primary_arg, context);
957✔
1634

1635
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1636
                            if (var->valueType() == ValueType::Reference)
957✔
1637
                                var = var->reference();
×
1638

1639
                            if (var->valueType() == ValueType::Number)
957✔
1640
                            {
1641
                                auto val = Value(var->number() - secondary_arg);
956✔
1642
                                setVal(primary_arg, &val, context);
956✔
1643
                            }
956✔
1644
                            else
1645
                                throw types::TypeCheckingError(
2✔
1646
                                    "-",
1✔
1647
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1648
                                    { *var, Value(secondary_arg) });
1✔
1649
                        }
1650
                        DISPATCH();
956✔
1651
                    }
1✔
1652

1653
                    TARGET(STORE_TAIL)
1654
                    {
1655
                        UNPACK_ARGS();
1✔
1656
                        {
1657
                            Value* list = loadSymbol(primary_arg, context);
1✔
1658
                            Value tail = helper::tail(list);
1✔
1659
                            store(secondary_arg, &tail, context);
1✔
1660
                        }
1✔
1661
                        DISPATCH();
1✔
1662
                    }
8✔
1663

1664
                    TARGET(STORE_TAIL_BY_INDEX)
1665
                    {
1666
                        UNPACK_ARGS();
8✔
1667
                        {
1668
                            Value* list = loadSymbolFromIndex(primary_arg, context);
8✔
1669
                            Value tail = helper::tail(list);
8✔
1670
                            store(secondary_arg, &tail, context);
8✔
1671
                        }
8✔
1672
                        DISPATCH();
8✔
1673
                    }
4✔
1674

1675
                    TARGET(STORE_HEAD)
1676
                    {
1677
                        UNPACK_ARGS();
4✔
1678
                        {
1679
                            Value* list = loadSymbol(primary_arg, context);
4✔
1680
                            Value head = helper::head(list);
4✔
1681
                            store(secondary_arg, &head, context);
4✔
1682
                        }
4✔
1683
                        DISPATCH();
4✔
1684
                    }
38✔
1685

1686
                    TARGET(STORE_HEAD_BY_INDEX)
1687
                    {
1688
                        UNPACK_ARGS();
38✔
1689
                        {
1690
                            Value* list = loadSymbolFromIndex(primary_arg, context);
38✔
1691
                            Value head = helper::head(list);
38✔
1692
                            store(secondary_arg, &head, context);
38✔
1693
                        }
38✔
1694
                        DISPATCH();
38✔
1695
                    }
982✔
1696

1697
                    TARGET(STORE_LIST)
1698
                    {
1699
                        UNPACK_ARGS();
982✔
1700
                        {
1701
                            Value l = createList(primary_arg, context);
982✔
1702
                            store(secondary_arg, &l, context);
982✔
1703
                        }
982✔
1704
                        DISPATCH();
982✔
1705
                    }
3✔
1706

1707
                    TARGET(SET_VAL_TAIL)
1708
                    {
1709
                        UNPACK_ARGS();
3✔
1710
                        {
1711
                            Value* list = loadSymbol(primary_arg, context);
3✔
1712
                            Value tail = helper::tail(list);
3✔
1713
                            setVal(secondary_arg, &tail, context);
3✔
1714
                        }
3✔
1715
                        DISPATCH();
3✔
1716
                    }
1✔
1717

1718
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1719
                    {
1720
                        UNPACK_ARGS();
1✔
1721
                        {
1722
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1723
                            Value tail = helper::tail(list);
1✔
1724
                            setVal(secondary_arg, &tail, context);
1✔
1725
                        }
1✔
1726
                        DISPATCH();
1✔
1727
                    }
1✔
1728

1729
                    TARGET(SET_VAL_HEAD)
1730
                    {
1731
                        UNPACK_ARGS();
1✔
1732
                        {
1733
                            Value* list = loadSymbol(primary_arg, context);
1✔
1734
                            Value head = helper::head(list);
1✔
1735
                            setVal(secondary_arg, &head, context);
1✔
1736
                        }
1✔
1737
                        DISPATCH();
1✔
1738
                    }
1✔
1739

1740
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1741
                    {
1742
                        UNPACK_ARGS();
1✔
1743
                        {
1744
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1745
                            Value head = helper::head(list);
1✔
1746
                            setVal(secondary_arg, &head, context);
1✔
1747
                        }
1✔
1748
                        DISPATCH();
1✔
1749
                    }
990✔
1750

1751
                    TARGET(CALL_BUILTIN)
1752
                    {
1753
                        UNPACK_ARGS();
990✔
1754
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1755
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
990✔
1756
                        if (!m_running)
929✔
1757
                            GOTO_HALT();
×
1758
                        DISPATCH();
929✔
1759
                    }
11,688✔
1760

1761
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1762
                    {
1763
                        UNPACK_ARGS();
11,688✔
1764
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1765
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,688✔
1766
                        if (!m_running)
11,687✔
1767
                            GOTO_HALT();
×
1768
                        DISPATCH();
11,687✔
1769
                    }
857✔
1770

1771
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1772
                    {
1773
                        UNPACK_ARGS();
857✔
1774
                        const Value* sym = popAndResolveAsPtr(context);
857✔
1775
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
857✔
1776
                            jump(secondary_arg, context);
122✔
1777
                        DISPATCH();
857✔
1778
                    }
21,988✔
1779

1780
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1781
                    {
1782
                        UNPACK_ARGS();
21,988✔
1783
                        const Value* sym = popAndResolveAsPtr(context);
21,988✔
1784
                        if (*sym < *loadConstAsPtr(primary_arg))
21,988✔
1785
                            jump(secondary_arg, context);
10,960✔
1786
                        DISPATCH();
21,988✔
1787
                    }
6,885✔
1788

1789
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1790
                    {
1791
                        UNPACK_ARGS();
6,885✔
1792
                        const Value* sym = popAndResolveAsPtr(context);
6,885✔
1793
                        if (!(*sym < *loadSymbol(primary_arg, context)))
6,885✔
1794
                            jump(secondary_arg, context);
653✔
1795
                        DISPATCH();
6,885✔
1796
                    }
172,506✔
1797

1798
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1799
                    {
1800
                        UNPACK_ARGS();
172,506✔
1801
                        const Value* sym = popAndResolveAsPtr(context);
172,506✔
1802
                        const Value* cst = loadConstAsPtr(primary_arg);
172,506✔
1803
                        if (*cst < *sym)
172,506✔
1804
                            jump(secondary_arg, context);
86,589✔
1805
                        DISPATCH();
172,506✔
1806
                    }
292✔
1807

1808
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1809
                    {
1810
                        UNPACK_ARGS();
292✔
1811
                        const Value* sym = popAndResolveAsPtr(context);
292✔
1812
                        const Value* cst = loadConstAsPtr(primary_arg);
292✔
1813
                        if (!(*cst < *sym))
292✔
1814
                            jump(secondary_arg, context);
56✔
1815
                        DISPATCH();
292✔
1816
                    }
6✔
1817

1818
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1819
                    {
1820
                        UNPACK_ARGS();
6✔
1821
                        const Value* sym = popAndResolveAsPtr(context);
6✔
1822
                        const Value* rhs = loadSymbol(primary_arg, context);
6✔
1823
                        if (!(*rhs < *sym))
6✔
1824
                            jump(secondary_arg, context);
1✔
1825
                        DISPATCH();
6✔
1826
                    }
1,096✔
1827

1828
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1829
                    {
1830
                        UNPACK_ARGS();
1,096✔
1831
                        const Value* sym = popAndResolveAsPtr(context);
1,096✔
1832
                        if (*sym == *loadConstAsPtr(primary_arg))
1,096✔
1833
                            jump(secondary_arg, context);
38✔
1834
                        DISPATCH();
1,096✔
1835
                    }
87,351✔
1836

1837
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1838
                    {
1839
                        UNPACK_ARGS();
87,351✔
1840
                        const Value* sym = popAndResolveAsPtr(context);
87,351✔
1841
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
87,351✔
1842
                            jump(secondary_arg, context);
548✔
1843
                        DISPATCH();
87,351✔
1844
                    }
11✔
1845

1846
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1847
                    {
1848
                        UNPACK_ARGS();
11✔
1849
                        const Value* sym = popAndResolveAsPtr(context);
11✔
1850
                        if (*sym != *loadConstAsPtr(primary_arg))
11✔
1851
                            jump(secondary_arg, context);
2✔
1852
                        DISPATCH();
11✔
1853
                    }
30✔
1854

1855
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1856
                    {
1857
                        UNPACK_ARGS();
30✔
1858
                        const Value* sym = popAndResolveAsPtr(context);
30✔
1859
                        if (*sym == *loadSymbol(primary_arg, context))
30✔
1860
                            jump(secondary_arg, context);
10✔
1861
                        DISPATCH();
30✔
1862
                    }
27,791✔
1863

1864
                    TARGET(CALL_SYMBOL)
1865
                    {
1866
                        UNPACK_ARGS();
27,791✔
1867
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
27,791✔
1868
                        if (!m_running)
27,789✔
1869
                            GOTO_HALT();
×
1870
                        DISPATCH();
27,789✔
1871
                    }
109,875✔
1872

1873
                    TARGET(CALL_CURRENT_PAGE)
1874
                    {
1875
                        UNPACK_ARGS();
109,875✔
1876
                        context.last_symbol = primary_arg;
109,875✔
1877
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,875✔
1878
                        if (!m_running)
109,874✔
1879
                            GOTO_HALT();
×
1880
                        DISPATCH();
109,874✔
1881
                    }
2,911✔
1882

1883
                    TARGET(GET_FIELD_FROM_SYMBOL)
1884
                    {
1885
                        UNPACK_ARGS();
2,911✔
1886
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
2,911✔
1887
                        DISPATCH();
2,911✔
1888
                    }
842✔
1889

1890
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1891
                    {
1892
                        UNPACK_ARGS();
842✔
1893
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
842✔
1894
                        DISPATCH();
840✔
1895
                    }
16,099✔
1896

1897
                    TARGET(AT_SYM_SYM)
1898
                    {
1899
                        UNPACK_ARGS();
16,099✔
1900
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
16,099✔
1901
                        DISPATCH();
16,099✔
1902
                    }
49✔
1903

1904
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1905
                    {
1906
                        UNPACK_ARGS();
49✔
1907
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
49✔
1908
                        DISPATCH();
49✔
1909
                    }
1,044✔
1910

1911
                    TARGET(AT_SYM_INDEX_CONST)
1912
                    {
1913
                        UNPACK_ARGS();
1,044✔
1914
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
1,044✔
1915
                        DISPATCH();
1,042✔
1916
                    }
2✔
1917

1918
                    TARGET(CHECK_TYPE_OF)
1919
                    {
1920
                        UNPACK_ARGS();
2✔
1921
                        const Value* sym = loadSymbol(primary_arg, context);
2✔
1922
                        const Value* cst = loadConstAsPtr(secondary_arg);
2✔
1923
                        push(
2✔
1924
                            cst->valueType() == ValueType::String &&
4✔
1925
                                    std::to_string(sym->valueType()) == cst->string()
2✔
1926
                                ? Builtins::trueSym
1927
                                : Builtins::falseSym,
1928
                            context);
2✔
1929
                        DISPATCH();
2✔
1930
                    }
81✔
1931

1932
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1933
                    {
1934
                        UNPACK_ARGS();
81✔
1935
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
81✔
1936
                        const Value* cst = loadConstAsPtr(secondary_arg);
81✔
1937
                        push(
81✔
1938
                            cst->valueType() == ValueType::String &&
162✔
1939
                                    std::to_string(sym->valueType()) == cst->string()
81✔
1940
                                ? Builtins::trueSym
1941
                                : Builtins::falseSym,
1942
                            context);
81✔
1943
                        DISPATCH();
81✔
1944
                    }
1,856✔
1945

1946
                    TARGET(APPEND_IN_PLACE_SYM)
1947
                    {
1948
                        UNPACK_ARGS();
1,856✔
1949
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
1,856✔
1950
                        DISPATCH();
1,856✔
1951
                    }
14✔
1952

1953
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1954
                    {
1955
                        UNPACK_ARGS();
14✔
1956
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
14✔
1957
                        DISPATCH();
13✔
1958
                    }
116✔
1959

1960
                    TARGET(STORE_LEN)
1961
                    {
1962
                        UNPACK_ARGS();
116✔
1963
                        {
1964
                            Value* a = loadSymbolFromIndex(primary_arg, context);
116✔
1965
                            Value len;
116✔
1966
                            if (a->valueType() == ValueType::List)
116✔
1967
                                len = Value(static_cast<int>(a->constList().size()));
40✔
1968
                            else if (a->valueType() == ValueType::String)
76✔
1969
                                len = Value(static_cast<int>(a->string().size()));
75✔
1970
                            else
1971
                                throw types::TypeCheckingError(
2✔
1972
                                    "len",
1✔
1973
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1974
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1975
                                    { *a });
1✔
1976
                            store(secondary_arg, &len, context);
115✔
1977
                        }
116✔
1978
                        DISPATCH();
115✔
1979
                    }
9,097✔
1980

1981
                    TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
1982
                    {
1983
                        UNPACK_ARGS();
9,097✔
1984
                        {
1985
                            const Value* sym = loadSymbol(primary_arg, context);
9,097✔
1986
                            Value size;
9,097✔
1987

1988
                            if (sym->valueType() == ValueType::List)
9,097✔
1989
                                size = Value(static_cast<int>(sym->constList().size()));
3,426✔
1990
                            else if (sym->valueType() == ValueType::String)
5,671✔
1991
                                size = Value(static_cast<int>(sym->string().size()));
5,670✔
1992
                            else
1993
                                throw types::TypeCheckingError(
2✔
1994
                                    "len",
1✔
1995
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1996
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1997
                                    { *sym });
1✔
1998

1999
                            if (!(*popAndResolveAsPtr(context) < size))
9,096✔
2000
                                jump(secondary_arg, context);
1,164✔
2001
                        }
9,097✔
2002
                        DISPATCH();
9,096✔
2003
                    }
503✔
2004

2005
                    TARGET(MUL_BY)
2006
                    {
2007
                        UNPACK_ARGS();
503✔
2008
                        {
2009
                            Value* var = loadSymbol(primary_arg, context);
503✔
2010
                            const int other = static_cast<int>(secondary_arg) - 2048;
503✔
2011

2012
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
2013
                            if (var->valueType() == ValueType::Reference)
503✔
2014
                                var = var->reference();
×
2015

2016
                            if (var->valueType() == ValueType::Number)
503✔
2017
                                push(Value(var->number() * other), context);
502✔
2018
                            else
2019
                                throw types::TypeCheckingError(
2✔
2020
                                    "*",
1✔
2021
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2022
                                    { *var, Value(other) });
1✔
2023
                        }
2024
                        DISPATCH();
502✔
2025
                    }
36✔
2026

2027
                    TARGET(MUL_BY_INDEX)
2028
                    {
2029
                        UNPACK_ARGS();
36✔
2030
                        {
2031
                            Value* var = loadSymbolFromIndex(primary_arg, context);
36✔
2032
                            const int other = static_cast<int>(secondary_arg) - 2048;
36✔
2033

2034
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
2035
                            if (var->valueType() == ValueType::Reference)
36✔
2036
                                var = var->reference();
×
2037

2038
                            if (var->valueType() == ValueType::Number)
36✔
2039
                                push(Value(var->number() * other), context);
35✔
2040
                            else
2041
                                throw types::TypeCheckingError(
2✔
2042
                                    "*",
1✔
2043
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2044
                                    { *var, Value(other) });
1✔
2045
                        }
2046
                        DISPATCH();
35✔
2047
                    }
1✔
2048

2049
                    TARGET(MUL_SET_VAL)
2050
                    {
2051
                        UNPACK_ARGS();
1✔
2052
                        {
2053
                            Value* var = loadSymbol(primary_arg, context);
1✔
2054
                            const int other = static_cast<int>(secondary_arg) - 2048;
1✔
2055

2056
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
2057
                            if (var->valueType() == ValueType::Reference)
1✔
2058
                                var = var->reference();
×
2059

2060
                            if (var->valueType() == ValueType::Number)
1✔
2061
                            {
2062
                                auto val = Value(var->number() * other);
×
2063
                                setVal(primary_arg, &val, context);
×
2064
                            }
×
2065
                            else
2066
                                throw types::TypeCheckingError(
2✔
2067
                                    "*",
1✔
2068
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2069
                                    { *var, Value(other) });
1✔
2070
                        }
2071
                        DISPATCH();
×
2072
                    }
1,104✔
2073

2074
                    TARGET(FUSED_MATH)
2075
                    {
2076
                        const auto op1 = static_cast<Instruction>(padding),
1,104✔
2077
                                   op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
1,104✔
2078
                                   op3 = static_cast<Instruction>(arg & 0x00ff);
1,104✔
2079
                        const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
1,104✔
2080

2081
                        const Value* d = popAndResolveAsPtr(context);
1,104✔
2082
                        const Value* c = popAndResolveAsPtr(context);
1,104✔
2083
                        const Value* b = popAndResolveAsPtr(context);
1,104✔
2084

2085
                        if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
1,104✔
NEW
2086
                            throw types::TypeCheckingError(
×
NEW
2087
                                InstructionNames[op1],
×
NEW
2088
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
NEW
2089
                                { *c, *d });
×
2090

2091
                        double temp = helper::doMath(c->number(), d->number(), op1);
1,104✔
2092
                        if (b->valueType() != ValueType::Number)
1,104✔
NEW
2093
                            throw types::TypeCheckingError(
×
NEW
2094
                                InstructionNames[op2],
×
NEW
2095
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
NEW
2096
                                { *b, Value(temp) });
×
2097
                        temp = helper::doMath(b->number(), temp, op2);
1,104✔
2098

2099
                        if (arg_count == 2)
1,104✔
2100
                            push(Value(temp), context);
1,068✔
2101
                        else if (arg_count == 3)
36✔
2102
                        {
2103
                            const Value* a = popAndResolveAsPtr(context);
36✔
2104
                            if (a->valueType() != ValueType::Number)
36✔
NEW
2105
                                throw types::TypeCheckingError(
×
NEW
2106
                                    InstructionNames[op3],
×
NEW
2107
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
NEW
2108
                                    { *a, Value(temp) });
×
2109

2110
                            temp = helper::doMath(a->number(), temp, op3);
36✔
2111
                            push(Value(temp), context);
36✔
2112
                        }
36✔
2113
                        else
NEW
2114
                            throw Error(
×
NEW
2115
                                fmt::format(
×
NEW
2116
                                    "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
×
NEW
2117
                                    arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
×
2118
                        DISPATCH();
1,104✔
2119
                    }
2120
#pragma endregion
2121
                }
90✔
2122
#if ARK_USE_COMPUTED_GOTOS
2123
            dispatch_end:
2124
                do
90✔
2125
                {
2126
                } while (false);
90✔
2127
#endif
2128
            }
2129
        }
224✔
2130
        catch (const Error& e)
2131
        {
2132
            if (fail_with_exception)
92✔
2133
            {
2134
                std::stringstream stream;
92✔
2135
                backtrace(context, stream, /* colorize= */ false);
92✔
2136
                // It's important we have an Ark::Error here, as the constructor for NestedError
2137
                // does more than just aggregate error messages, hence the code duplication.
2138
                throw NestedError(e, stream.str(), *this);
92✔
2139
            }
92✔
2140
            else
2141
                showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context);
×
2142
        }
182✔
2143
        catch (const std::exception& e)
2144
        {
2145
            if (fail_with_exception)
42✔
2146
            {
2147
                std::stringstream stream;
42✔
2148
                backtrace(context, stream, /* colorize= */ false);
42✔
2149
                throw NestedError(e, stream.str());
42✔
2150
            }
42✔
2151
            else
2152
                showBacktraceWithException(e, context);
×
2153
        }
134✔
2154
        catch (...)
2155
        {
2156
            if (fail_with_exception)
×
2157
                throw;
×
2158

2159
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2160
            throw;
2161
#endif
2162
            fmt::println("Unknown error");
×
2163
            backtrace(context);
×
2164
            m_exit_code = 1;
×
2165
        }
176✔
2166

2167
        return m_exit_code;
90✔
2168
    }
272✔
2169

2170
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,056✔
2171
    {
2,056✔
2172
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,202✔
2173
        {
2174
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,146✔
2175
                return id;
2,050✔
2176
        }
2,096,146✔
2177
        return MaxValue16Bits;
6✔
2178
    }
2,056✔
2179

2180
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, internal::ExecutionContext& context)
5✔
2181
    {
5✔
2182
        std::vector<std::string> arg_names;
5✔
2183
        arg_names.reserve(expected_arg_count + 1);
5✔
2184
        if (expected_arg_count > 0)
5✔
2185
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
2186

2187
        std::size_t index = 0;
5✔
2188
        while (m_state.inst(context.pp, index) == STORE ||
10✔
2189
               m_state.inst(context.pp, index) == STORE_REF)
5✔
2190
        {
2191
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
2192
            arg_names.push_back(m_state.m_symbols[id]);
×
2193
            index += 4;
×
2194
        }
×
2195
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2196
        if (arg_names.size() == 1 && index == 0)
5✔
2197
        {
2198
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
2199
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
2200
                arg_names.push_back(std::string(1, static_cast<char>('a' + i)));
2✔
2201
        }
2✔
2202

2203
        std::vector<std::string> arg_vals;
5✔
2204
        arg_vals.reserve(passed_arg_count + 1);
5✔
2205
        if (passed_arg_count > 0)
5✔
2206
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
4✔
2207

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

2212
        // set ip/pp to the callee location so that the error can pinpoint the line
2213
        // where the bad call happened
2214
        if (context.sp >= 2 + passed_arg_count)
5✔
2215
        {
2216
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
5✔
2217
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
5✔
2218
            returnFromFuncCall(context);
5✔
2219
        }
5✔
2220

2221
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
10✔
2222
            ? m_state.m_symbols[context.last_symbol]
5✔
2223
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2224

2225
        throwVMError(
5✔
2226
            ErrorKind::Arity,
2227
            fmt::format(
10✔
2228
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
5✔
2229
                function_name,
2230
                fmt::join(arg_vals, " "),
5✔
2231
                passed_arg_count,
2232
                passed_arg_count > 1 ? "s" : "",
5✔
2233
                expected_arg_count,
2234
                function_name,
2235
                fmt::join(arg_names, " ")));
5✔
2236
    }
10✔
2237

2238
    void VM::showBacktraceWithException(const std::exception& e, internal::ExecutionContext& context)
×
2239
    {
×
2240
        std::string text = e.what();
×
2241
        if (!text.empty() && text.back() != '\n')
×
2242
            text += '\n';
×
2243
        fmt::println("{}", text);
×
2244
        backtrace(context);
×
2245
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2246
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2247
        m_exit_code = 0;
2248
#else
2249
        m_exit_code = 1;
×
2250
#endif
2251
    }
×
2252

2253
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,199✔
2254
    {
2,199✔
2255
        std::optional<InstLoc> match = std::nullopt;
2,199✔
2256

2257
        for (const auto location : m_state.m_inst_locations)
10,988✔
2258
        {
2259
            if (location.page_pointer == pp && !match)
8,789✔
2260
                match = location;
2,199✔
2261

2262
            // select the best match: we want to find the location that's nearest our instruction pointer,
2263
            // but not equal to it as the IP will always be pointing to the next instruction,
2264
            // not yet executed. Thus, the erroneous instruction is the previous one.
2265
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
8,789✔
2266
                match = location;
2,356✔
2267

2268
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2269
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
8,789✔
2270
                break;
2,071✔
2271
        }
8,789✔
2272

2273
        return match;
2,199✔
2274
    }
2275

2276
    std::string VM::debugShowSource() const
×
2277
    {
×
2278
        const auto& context = m_execution_contexts.front();
×
2279
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2280
        if (maybe_source_loc)
×
2281
        {
2282
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2283
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2284
        }
×
2285
        return "No source location found";
×
2286
    }
×
2287

2288
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
134✔
2289
    {
134✔
2290
        const std::size_t saved_ip = context.ip;
134✔
2291
        const std::size_t saved_pp = context.pp;
134✔
2292
        const uint16_t saved_sp = context.sp;
134✔
2293
        constexpr std::size_t max_consecutive_traces = 7;
134✔
2294

2295
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
134✔
2296
        if (maybe_location)
134✔
2297
        {
2298
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
134✔
2299

2300
            if (Utils::fileExists(filename))
134✔
2301
                Diagnostics::makeContext(
264✔
2302
                    Diagnostics::ErrorLocation {
264✔
2303
                        .filename = filename,
132✔
2304
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
132✔
2305
                        .end = std::nullopt },
132✔
2306
                    os,
132✔
2307
                    /* maybe_context= */ std::nullopt,
132✔
2308
                    /* colorize= */ colorize);
132✔
2309
            fmt::println(os, "");
134✔
2310
        }
134✔
2311

2312
        if (context.fc > 1)
134✔
2313
        {
2314
            // display call stack trace
2315
            const ScopeView old_scope = context.locals.back();
9✔
2316

2317
            std::string previous_trace;
9✔
2318
            std::size_t displayed_traces = 0;
9✔
2319
            std::size_t consecutive_similar_traces = 0;
9✔
2320

2321
            while (context.fc != 0 && context.pp != 0)
2,065✔
2322
            {
2323
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,056✔
2324
                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,056✔
2325

2326
                const uint16_t id = findNearestVariableIdWithValue(
2,056✔
2327
                    Value(static_cast<PageAddr_t>(context.pp)),
2,056✔
2328
                    context);
2,056✔
2329
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,056✔
2330

2331
                if (func_name + loc_as_text != previous_trace)
2,056✔
2332
                {
2333
                    fmt::println(
20✔
2334
                        os,
10✔
2335
                        "[{:4}] In function `{}'{}",
10✔
2336
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
10✔
2337
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
10✔
2338
                        loc_as_text);
2339
                    previous_trace = func_name + loc_as_text;
10✔
2340
                    ++displayed_traces;
10✔
2341
                    consecutive_similar_traces = 0;
10✔
2342
                }
10✔
2343
                else if (consecutive_similar_traces == 0)
2,046✔
2344
                {
2345
                    fmt::println(os, "       ...");
1✔
2346
                    ++consecutive_similar_traces;
1✔
2347
                }
1✔
2348

2349
                const Value* ip;
2,056✔
2350
                do
6,261✔
2351
                {
2352
                    ip = popAndResolveAsPtr(context);
6,261✔
2353
                } while (ip->valueType() != ValueType::InstPtr);
6,261✔
2354

2355
                context.ip = ip->pageAddr();
2,056✔
2356
                context.pp = pop(context)->pageAddr();
2,056✔
2357
                returnFromFuncCall(context);
2,056✔
2358

2359
                if (displayed_traces > max_consecutive_traces)
2,056✔
2360
                {
2361
                    fmt::println(os, "       ...");
×
2362
                    break;
×
2363
                }
2364
            }
2,056✔
2365

2366
            if (context.pp == 0)
9✔
2367
            {
2368
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
9✔
2369
                const auto loc_as_text = maybe_call_loc ? fmt::format(" ({}:{})", m_state.m_filenames[maybe_call_loc->filename_id], maybe_call_loc->line + 1) : "";
9✔
2370
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
9✔
2371
            }
9✔
2372

2373
            // display variables values in the current scope
2374
            fmt::println(os, "\nCurrent scope variables values:");
9✔
2375
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
10✔
2376
            {
2377
                fmt::println(
2✔
2378
                    os,
1✔
2379
                    "{} = {}",
1✔
2380
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2381
                    old_scope.atPos(i).second.toString(*this));
1✔
2382
            }
1✔
2383
        }
9✔
2384

2385
        fmt::println(
268✔
2386
            os,
134✔
2387
            "At IP: {}, PP: {}, SP: {}",
134✔
2388
            // dividing by 4 because the instructions are actually on 4 bytes
2389
            fmt::styled(saved_ip / 4, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
134✔
2390
            fmt::styled(saved_pp, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
134✔
2391
            fmt::styled(saved_sp, colorize ? fmt::fg(fmt::color::yellow) : fmt::text_style()));
134✔
2392
    }
134✔
2393
}
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