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

ArkScript-lang / Ark / 21683557079

04 Feb 2026 06:31PM UTC coverage: 93.413% (+0.006%) from 93.407%
21683557079

push

github

SuperFola
chore: release ArkScript 4.2.0

8849 of 9473 relevant lines covered (93.41%)

274089.53 hits per line

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

92.85
/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
#include <Ark/VM/Value/Dict.hpp>
16

17
namespace Ark
18
{
19
    using namespace internal;
20

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

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

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

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

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

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

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

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

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

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

128
            return a;
2,249✔
129
        }
1✔
130

131
        inline std::string mathInstToStr(const Instruction op)
4✔
132
        {
4✔
133
            if (op == ADD)
4✔
134
                return "+";
1✔
135
            if (op == SUB)
3✔
136
                return "-";
1✔
137
            if (op == MUL)
2✔
138
                return "*";
1✔
139
            if (op == DIV)
1✔
140
                return "/";
1✔
141
            return "???";
×
142
        }
4✔
143
    }
144

145
    VM::VM(State& state) noexcept :
675✔
146
        m_state(state), m_exit_code(0), m_running(false)
225✔
147
    {
225✔
148
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>());
225✔
149
    }
225✔
150

151
    void VM::init() noexcept
219✔
152
    {
219✔
153
        ExecutionContext& context = *m_execution_contexts.back();
219✔
154
        for (const auto& c : m_execution_contexts)
438✔
155
        {
156
            c->ip = 0;
219✔
157
            c->pp = 0;
219✔
158
            c->sp = 0;
219✔
159
        }
219✔
160

161
        context.sp = 0;
219✔
162
        context.fc = 1;
219✔
163

164
        m_shared_lib_objects.clear();
219✔
165
        context.stacked_closure_scopes.clear();
219✔
166
        context.stacked_closure_scopes.emplace_back(nullptr);
219✔
167

168
        context.saved_scope.reset();
219✔
169
        m_exit_code = 0;
219✔
170

171
        context.locals.clear();
219✔
172
        context.locals.reserve(128);
219✔
173
        context.locals.emplace_back(context.scopes_storage.data(), 0);
219✔
174

175
        // loading bound stuff
176
        // put them in the global frame if we can, aka the first one
177
        for (const auto& [sym_id, value] : m_state.m_bound)
693✔
178
        {
179
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
451✔
180
            if (it != m_state.m_symbols.end())
451✔
181
                context.locals[0].pushBack(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
23✔
182
        }
451✔
183
    }
219✔
184

185
    Value VM::getField(Value* closure, const uint16_t id, const ExecutionContext& context)
3,851✔
186
    {
3,851✔
187
        if (closure->valueType() != ValueType::Closure)
3,851✔
188
        {
225✔
189
            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
190
                throwVMError(
2✔
191
                    ErrorKind::Type,
225✔
192
                    fmt::format(
3✔
193
                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
194
                        m_state.m_symbols[context.last_symbol],
1✔
195
                        std::to_string(closure->valueType()),
1✔
196
                        m_state.m_symbols[id]));
1✔
197
            else
198
                throwVMError(
×
199
                    ErrorKind::Type,
200
                    fmt::format(
×
201
                        "{} is not a Closure, can not get the field `{}' from it",
×
202
                        std::to_string(closure->valueType()),
×
203
                        m_state.m_symbols[id]));
×
204
        }
205

206
        if (Value* field = closure->refClosure().refScope()[id]; field != nullptr)
7,700✔
207
        {
208
            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
209
            if (m_state.inst(context.pp, context.ip) == CALL)
3,849✔
210
                return Value(Closure(closure->refClosure().scopePtr(), field->pageAddr()));
2,162✔
211
            else
212
                return *field;
1,687✔
213
        }
214
        else
215
        {
216
            if (!closure->refClosure().hasFieldEndingWith(m_state.m_symbols[id], *this))
1✔
217
                throwVMError(
1✔
218
                    ErrorKind::Scope,
219
                    fmt::format(
2✔
220
                        "`{0}' isn't in the closure environment: {1}",
1✔
221
                        m_state.m_symbols[id],
1✔
222
                        closure->refClosure().toString(*this)));
1✔
223
            throwVMError(
×
224
                ErrorKind::Scope,
225
                fmt::format(
×
226
                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
×
227
                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
228
                    m_state.m_symbols[id],
×
229
                    closure->refClosure().toString(*this)));
×
230
        }
231
    }
3,851✔
232

233
    Value VM::createList(const std::size_t count, internal::ExecutionContext& context)
1,837✔
234
    {
1,837✔
235
        Value l(ValueType::List);
1,837✔
236
        if (count != 0)
1,837✔
237
            l.list().reserve(count);
727✔
238

239
        for (std::size_t i = 0; i < count; ++i)
3,723✔
240
            l.push_back(*popAndResolveAsPtr(context));
1,886✔
241

242
        return l;
1,837✔
243
    }
1,837✔
244

245
    void VM::listAppendInPlace(Value* list, const std::size_t count, ExecutionContext& context)
3,476✔
246
    {
3,476✔
247
        if (list->valueType() != ValueType::List)
3,476✔
248
        {
249
            std::vector<Value> args = { *list };
1✔
250
            for (std::size_t i = 0; i < count; ++i)
2✔
251
                args.push_back(*popAndResolveAsPtr(context));
1✔
252
            throw types::TypeCheckingError(
2✔
253
                "append!",
1✔
254
                { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
255
                args);
256
        }
1✔
257

258
        for (std::size_t i = 0; i < count; ++i)
6,950✔
259
            list->push_back(*popAndResolveAsPtr(context));
3,475✔
260
    }
3,476✔
261

262
    Value& VM::operator[](const std::string& name) noexcept
36✔
263
    {
36✔
264
        // find id of object
265
        const auto it = std::ranges::find(m_state.m_symbols, name);
36✔
266
        if (it == m_state.m_symbols.end())
36✔
267
        {
268
            m_no_value = Builtins::nil;
1✔
269
            return m_no_value;
1✔
270
        }
271

272
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
35✔
273
        if (std::cmp_less(dist, MaxValue16Bits))
35✔
274
        {
275
            ExecutionContext& context = *m_execution_contexts.front();
35✔
276

277
            const auto id = static_cast<uint16_t>(dist);
35✔
278
            Value* var = findNearestVariable(id, context);
35✔
279
            if (var != nullptr)
35✔
280
                return *var;
35✔
281
        }
35✔
282

283
        m_no_value = Builtins::nil;
×
284
        return m_no_value;
×
285
    }
36✔
286

287
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
1✔
288
    {
1✔
289
        namespace fs = std::filesystem;
290

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

293
        std::string path = file;
1✔
294
        // bytecode loaded from file
295
        if (m_state.m_filename != ARK_NO_NAME_FILE)
1✔
296
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
1✔
297

298
        std::shared_ptr<SharedLibrary> lib;
1✔
299
        // if it exists alongside the .arkc file
300
        if (Utils::fileExists(path))
1✔
301
            lib = std::make_shared<SharedLibrary>(path);
×
302
        else
303
        {
304
            for (auto const& v : m_state.m_libenv)
3✔
305
            {
306
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
2✔
307

308
                // if it's already loaded don't do anything
309
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
2✔
310
                        return (val->path() == path || val->path() == lib_path);
×
311
                    }) != m_shared_lib_objects.end())
2✔
312
                    return;
×
313

314
                // check in lib_path
315
                if (Utils::fileExists(lib_path))
2✔
316
                {
317
                    lib = std::make_shared<SharedLibrary>(lib_path);
1✔
318
                    break;
1✔
319
                }
320
            }
2✔
321
        }
322

323
        if (!lib)
1✔
324
        {
325
            auto lib_path = std::accumulate(
×
326
                std::next(m_state.m_libenv.begin()),
×
327
                m_state.m_libenv.end(),
×
328
                m_state.m_libenv[0].string(),
×
329
                [](const std::string& a, const fs::path& b) -> std::string {
×
330
                    return a + "\n\t- " + b.string();
×
331
                });
×
332
            throwVMError(
×
333
                ErrorKind::Module,
334
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
335
        }
×
336

337
        m_shared_lib_objects.emplace_back(lib);
1✔
338

339
        // load the mapping from the dynamic library
340
        try
341
        {
342
            std::vector<ScopeView::pair_t> data;
1✔
343
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
1✔
344

345
            std::size_t i = 0;
1✔
346
            while (map[i].name != nullptr)
2✔
347
            {
348
                const auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
1✔
349
                if (it != m_state.m_symbols.end())
1✔
350
                    data.emplace_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
1✔
351

352
                ++i;
1✔
353
            }
1✔
354

355
            context.locals.back().insertFront(data);
1✔
356
        }
1✔
357
        catch (const std::system_error& e)
358
        {
359
            throwVMError(
×
360
                ErrorKind::Module,
361
                fmt::format(
×
362
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
363
                    file, e.what()));
×
364
        }
1✔
365
    }
1✔
366

367
    void VM::exit(const int code) noexcept
×
368
    {
×
369
        m_exit_code = code;
×
370
        m_running = false;
×
371
    }
×
372

373
    ExecutionContext* VM::createAndGetContext()
17✔
374
    {
17✔
375
        const std::lock_guard lock(m_mutex);
17✔
376

377
        ExecutionContext* ctx = nullptr;
17✔
378

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

384
        if (m_execution_contexts.size() > 1)
17✔
385
        {
386
            const auto it = std::ranges::find_if(
28✔
387
                m_execution_contexts,
14✔
388
                [](const std::unique_ptr<ExecutionContext>& context) -> bool {
38✔
389
                    return !context->primary && context->isFree();
38✔
390
                });
391

392
            if (it != m_execution_contexts.end())
14✔
393
            {
394
                ctx = it->get();
10✔
395
                ctx->setActive(true);
10✔
396
                // reset the context before using it
397
                ctx->sp = 0;
10✔
398
                ctx->saved_scope.reset();
10✔
399
                ctx->stacked_closure_scopes.clear();
10✔
400
                ctx->locals.clear();
10✔
401
            }
10✔
402
        }
14✔
403

404
        if (ctx == nullptr)
17✔
405
            ctx = m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>()).get();
7✔
406

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

410
        const ExecutionContext& primary_ctx = *m_execution_contexts.front();
17✔
411
        ctx->locals.reserve(primary_ctx.locals.size());
17✔
412
        ctx->scopes_storage = primary_ctx.scopes_storage;
17✔
413
        ctx->stacked_closure_scopes.emplace_back(nullptr);
17✔
414
        ctx->fc = 1;
17✔
415

416
        for (const auto& scope_view : primary_ctx.locals)
62✔
417
        {
418
            auto& new_scope = ctx->locals.emplace_back(ctx->scopes_storage.data(), scope_view.m_start);
45✔
419
            for (std::size_t i = 0; i < scope_view.size(); ++i)
3,152✔
420
            {
421
                const auto& [id, val] = scope_view.atPos(i);
3,107✔
422
                new_scope.pushBack(id, val);
3,107✔
423
            }
3,107✔
424
        }
45✔
425

426
        return ctx;
17✔
427
    }
17✔
428

429
    void VM::deleteContext(ExecutionContext* ec)
16✔
430
    {
16✔
431
        const std::lock_guard lock(m_mutex);
16✔
432

433
        // 1 + 4 additional contexts, it's a bit much (~600kB per context) to have in memory
434
        if (m_execution_contexts.size() > 5)
16✔
435
        {
436
            const auto it =
1✔
437
                std::ranges::remove_if(
2✔
438
                    m_execution_contexts,
1✔
439
                    [ec](const std::unique_ptr<ExecutionContext>& ctx) {
7✔
440
                        return ctx.get() == ec;
6✔
441
                    })
442
                    .begin();
1✔
443
            m_execution_contexts.erase(it);
1✔
444
        }
1✔
445
        else
446
        {
447
            // mark the used context as ready to be used again
448
            for (std::size_t i = 1; i < m_execution_contexts.size(); ++i)
40✔
449
            {
450
                if (m_execution_contexts[i].get() == ec)
25✔
451
                {
452
                    ec->setActive(false);
15✔
453
                    break;
15✔
454
                }
455
            }
10✔
456
        }
457
    }
16✔
458

459
    Future* VM::createFuture(std::vector<Value>& args)
17✔
460
    {
17✔
461
        const std::lock_guard lock(m_mutex_futures);
17✔
462

463
        ExecutionContext* ctx = createAndGetContext();
17✔
464
        // so that we have access to the presumed symbol id of the function we are calling
465
        // assuming that the callee is always the global context
466
        ctx->last_symbol = m_execution_contexts.front()->last_symbol;
17✔
467

468
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
17✔
469
        return m_futures.back().get();
17✔
470
    }
17✔
471

472
    void VM::deleteFuture(Future* f)
1✔
473
    {
1✔
474
        const std::lock_guard lock(m_mutex_futures);
1✔
475

476
        std::erase_if(
1✔
477
            m_futures,
1✔
478
            [f](const std::unique_ptr<Future>& future) {
3✔
479
                return future.get() == f;
2✔
480
            });
481
    }
1✔
482

483
    bool VM::forceReloadPlugins() const
×
484
    {
×
485
        // load the mapping from the dynamic library
486
        try
487
        {
488
            for (const auto& shared_lib : m_shared_lib_objects)
×
489
            {
490
                const mapping* map = shared_lib->get<mapping* (*)()>("getFunctionsMapping")();
×
491
                // load the mapping data
492
                std::size_t i = 0;
×
493
                while (map[i].name != nullptr)
×
494
                {
495
                    // put it in the global frame, aka the first one
496
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
497
                    if (it != m_state.m_symbols.end())
×
498
                        m_execution_contexts[0]->locals[0].pushBack(
×
499
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
500
                            Value(map[i].value));
×
501

502
                    ++i;
×
503
                }
×
504
            }
×
505

506
            return true;
×
507
        }
×
508
        catch (const std::system_error&)
509
        {
510
            return false;
×
511
        }
×
512
    }
×
513

514
    void VM::usePromptFileForDebugger(const std::string& path, std::ostream& os)
5✔
515
    {
5✔
516
        m_debugger = std::make_unique<Debugger>(m_state.m_libenv, path, os, m_state.m_symbols, m_state.m_constants);
5✔
517
    }
5✔
518

519
    void VM::throwVMError(ErrorKind kind, const std::string& message)
34✔
520
    {
34✔
521
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
34✔
522
    }
34✔
523

524
    int VM::run(const bool fail_with_exception)
219✔
525
    {
219✔
526
        init();
219✔
527
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
219✔
528
        return m_exit_code;
219✔
529
    }
530

531
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
250✔
532
    {
250✔
533
#if ARK_USE_COMPUTED_GOTOS
534
#    define TARGET(op) TARGET_##op:
535
#    define DISPATCH_GOTO()            \
536
        _Pragma("GCC diagnostic push") \
537
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
538
        _Pragma("GCC diagnostic pop")
539
#    define GOTO_HALT() goto dispatch_end
540
#else
541
#    define TARGET(op) case op:
542
#    define DISPATCH_GOTO() goto dispatch_opcode
543
#    define GOTO_HALT() break
544
#endif
545

546
#define NEXTOPARG()                                                                                                               \
547
    do                                                                                                                            \
548
    {                                                                                                                             \
549
        inst = m_state.inst(context.pp, context.ip);                                                                              \
550
        padding = m_state.inst(context.pp, context.ip + 1);                                                                       \
551
        arg = static_cast<uint16_t>((m_state.inst(context.pp, context.ip + 2) << 8) +                                             \
552
                                    m_state.inst(context.pp, context.ip + 3));                                                    \
553
        context.ip += 4;                                                                                                          \
554
        context.inst_exec_counter = (context.inst_exec_counter + 1) % VMOverflowBufferSize;                                       \
555
        if (context.inst_exec_counter < 2 && context.sp >= VMStackSize)                                                           \
556
        {                                                                                                                         \
557
            if (context.pp != 0)                                                                                                  \
558
                throw Error("Stack overflow. You could consider rewriting your function to make use of tail-call optimization."); \
559
            else                                                                                                                  \
560
                throw Error("Stack overflow. Are you trying to call a function with too many arguments?");                        \
561
        }                                                                                                                         \
562
    } while (false)
563
#define DISPATCH() \
564
    NEXTOPARG();   \
565
    DISPATCH_GOTO();
566
#define UNPACK_ARGS()                                                                 \
567
    do                                                                                \
568
    {                                                                                 \
569
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
570
        primary_arg = arg & 0x0fff;                                                   \
571
    } while (false)
572

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

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

697
        try
698
        {
699
            uint8_t inst = 0;
250✔
700
            uint8_t padding = 0;
250✔
701
            uint16_t arg = 0;
250✔
702
            uint16_t primary_arg = 0;
250✔
703
            uint16_t secondary_arg = 0;
250✔
704

705
            m_running = true;
250✔
706

707
            DISPATCH();
250✔
708
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
709
            {
710
#if !ARK_USE_COMPUTED_GOTOS
711
            dispatch_opcode:
712
                switch (inst)
713
#endif
714
                {
×
715
#pragma region "Instructions"
716
                    TARGET(NOP)
717
                    {
718
                        DISPATCH();
×
719
                    }
144,470✔
720

721
                    TARGET(LOAD_FAST)
722
                    {
723
                        push(loadSymbol(arg, context), context);
144,470✔
724
                        DISPATCH();
144,470✔
725
                    }
335,413✔
726

727
                    TARGET(LOAD_FAST_BY_INDEX)
728
                    {
729
                        push(loadSymbolFromIndex(arg, context), context);
335,413✔
730
                        DISPATCH();
335,413✔
731
                    }
4,598✔
732

733
                    TARGET(LOAD_SYMBOL)
734
                    {
735
                        // force resolving the reference
736
                        push(*loadSymbol(arg, context), context);
4,598✔
737
                        DISPATCH();
4,598✔
738
                    }
116,411✔
739

740
                    TARGET(LOAD_CONST)
741
                    {
742
                        push(loadConstAsPtr(arg), context);
116,411✔
743
                        DISPATCH();
116,411✔
744
                    }
30,948✔
745

746
                    TARGET(POP_JUMP_IF_TRUE)
747
                    {
748
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
39,356✔
749
                            jump(arg, context);
8,408✔
750
                        DISPATCH();
30,948✔
751
                    }
403,356✔
752

753
                    TARGET(STORE)
754
                    {
755
                        store(arg, popAndResolveAsPtr(context), context);
403,356✔
756
                        DISPATCH();
403,356✔
757
                    }
472✔
758

759
                    TARGET(STORE_REF)
760
                    {
761
                        // Not resolving a potential ref is on purpose!
762
                        // This instruction is only used by functions when storing arguments
763
                        const Value* tmp = pop(context);
472✔
764
                        store(arg, tmp, context);
472✔
765
                        DISPATCH();
472✔
766
                    }
23,608✔
767

768
                    TARGET(SET_VAL)
769
                    {
770
                        setVal(arg, popAndResolveAsPtr(context), context);
23,608✔
771
                        DISPATCH();
23,608✔
772
                    }
18,987✔
773

774
                    TARGET(POP_JUMP_IF_FALSE)
775
                    {
776
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
20,054✔
777
                            jump(arg, context);
1,067✔
778
                        DISPATCH();
18,987✔
779
                    }
208,965✔
780

781
                    TARGET(JUMP)
782
                    {
783
                        jump(arg, context);
208,965✔
784
                        DISPATCH();
208,965✔
785
                    }
139,067✔
786

787
                    TARGET(RET)
788
                    {
789
                        {
790
                            Value ip_or_val = *popAndResolveAsPtr(context);
139,067✔
791
                            // no return value on the stack
792
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
139,067✔
793
                            {
794
                                context.ip = ip_or_val.pageAddr();
3,825✔
795
                                // we always push PP then IP, thus the next value
796
                                // MUST be the page pointer
797
                                context.pp = pop(context)->pageAddr();
3,825✔
798

799
                                returnFromFuncCall(context);
3,825✔
800
                                push(Builtins::nil, context);
3,825✔
801
                            }
3,825✔
802
                            // value on the stack
803
                            else [[likely]]
804
                            {
805
                                const Value* ip = popAndResolveAsPtr(context);
135,242✔
806
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
135,242✔
807
                                context.ip = ip->pageAddr();
135,242✔
808
                                context.pp = pop(context)->pageAddr();
135,242✔
809

810
                                returnFromFuncCall(context);
135,242✔
811
                                push(std::move(ip_or_val), context);
135,242✔
812
                            }
813

814
                            if (context.fc <= untilFrameCount)
139,067✔
815
                                GOTO_HALT();
18✔
816
                        }
139,067✔
817

818
                        DISPATCH();
139,049✔
819
                    }
88✔
820

821
                    TARGET(HALT)
822
                    {
823
                        m_running = false;
88✔
824
                        GOTO_HALT();
88✔
825
                    }
142,864✔
826

827
                    TARGET(PUSH_RETURN_ADDRESS)
828
                    {
829
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
142,864✔
830
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
831
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
142,864✔
832
                        context.inst_exec_counter++;
142,864✔
833
                        DISPATCH();
142,864✔
834
                    }
3,300✔
835

836
                    TARGET(CALL)
837
                    {
838
                        call(context, arg);
3,300✔
839
                        if (!m_running)
3,292✔
840
                            GOTO_HALT();
×
841
                        DISPATCH();
3,292✔
842
                    }
3,191✔
843

844
                    TARGET(CAPTURE)
845
                    {
846
                        if (!context.saved_scope)
3,191✔
847
                            context.saved_scope = ClosureScope();
631✔
848

849
                        const Value* ptr = findNearestVariable(arg, context);
3,191✔
850
                        if (!ptr)
3,191✔
851
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
852
                        else
853
                        {
854
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
3,191✔
855
                            uint16_t id = context.capture_rename_id.value_or(arg);
3,191✔
856
                            context.saved_scope.value().push_back(id, *ptr);
3,191✔
857
                            context.capture_rename_id.reset();
3,191✔
858
                        }
859

860
                        DISPATCH();
3,191✔
861
                    }
13✔
862

863
                    TARGET(RENAME_NEXT_CAPTURE)
864
                    {
865
                        context.capture_rename_id = arg;
13✔
866
                        DISPATCH();
13✔
867
                    }
1,874✔
868

869
                    TARGET(BUILTIN)
870
                    {
871
                        push(Builtins::builtins[arg].second, context);
1,874✔
872
                        DISPATCH();
1,874✔
873
                    }
2✔
874

875
                    TARGET(DEL)
876
                    {
877
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
2✔
878
                        {
879
                            if (var->valueType() == ValueType::User)
1✔
880
                                var->usertypeRef().del();
1✔
881
                            *var = Value();
1✔
882
                            DISPATCH();
1✔
883
                        }
884

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

888
                    TARGET(MAKE_CLOSURE)
889
                    {
890
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
631✔
891
                        context.saved_scope.reset();
631✔
892
                        DISPATCH();
631✔
893
                    }
6✔
894

895
                    TARGET(GET_FIELD)
896
                    {
897
                        Value* var = popAndResolveAsPtr(context);
6✔
898
                        push(getField(var, arg, context), context);
6✔
899
                        DISPATCH();
6✔
900
                    }
1✔
901

902
                    TARGET(PLUGIN)
903
                    {
904
                        loadPlugin(arg, context);
1✔
905
                        DISPATCH();
1✔
906
                    }
824✔
907

908
                    TARGET(LIST)
909
                    {
910
                        {
911
                            Value l = createList(arg, context);
824✔
912
                            push(std::move(l), context);
824✔
913
                        }
824✔
914
                        DISPATCH();
824✔
915
                    }
30✔
916

917
                    TARGET(APPEND)
918
                    {
919
                        {
920
                            Value* list = popAndResolveAsPtr(context);
30✔
921
                            if (list->valueType() != ValueType::List)
30✔
922
                            {
923
                                std::vector<Value> args = { *list };
1✔
924
                                for (uint16_t i = 0; i < arg; ++i)
2✔
925
                                    args.push_back(*popAndResolveAsPtr(context));
1✔
926
                                throw types::TypeCheckingError(
2✔
927
                                    "append",
1✔
928
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any, /* is_variadic= */ true) } } } },
1✔
929
                                    args);
930
                            }
1✔
931

932
                            const auto size = static_cast<uint16_t>(list->constList().size());
29✔
933

934
                            Value obj { *list };
29✔
935
                            obj.list().reserve(size + arg);
29✔
936

937
                            for (uint16_t i = 0; i < arg; ++i)
58✔
938
                                obj.push_back(*popAndResolveAsPtr(context));
29✔
939
                            push(std::move(obj), context);
29✔
940
                        }
29✔
941
                        DISPATCH();
29✔
942
                    }
15✔
943

944
                    TARGET(CONCAT)
945
                    {
946
                        {
947
                            Value* list = popAndResolveAsPtr(context);
15✔
948
                            Value obj { *list };
15✔
949

950
                            for (uint16_t i = 0; i < arg; ++i)
30✔
951
                            {
952
                                Value* next = popAndResolveAsPtr(context);
17✔
953

954
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
17✔
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(obj.list()));
15✔
961
                            }
15✔
962
                            push(std::move(obj), context);
13✔
963
                        }
15✔
964
                        DISPATCH();
13✔
965
                    }
1✔
966

967
                    TARGET(APPEND_IN_PLACE)
968
                    {
969
                        Value* list = popAndResolveAsPtr(context);
1✔
970
                        listAppendInPlace(list, arg, context);
1✔
971
                        DISPATCH();
1✔
972
                    }
570✔
973

974
                    TARGET(CONCAT_IN_PLACE)
975
                    {
976
                        Value* list = popAndResolveAsPtr(context);
570✔
977

978
                        for (uint16_t i = 0; i < arg; ++i)
1,175✔
979
                        {
980
                            Value* next = popAndResolveAsPtr(context);
607✔
981

982
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
607✔
983
                                throw types::TypeCheckingError(
4✔
984
                                    "concat!",
2✔
985
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
986
                                    { *list, *next });
2✔
987

988
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
605✔
989
                        }
605✔
990
                        DISPATCH();
568✔
991
                    }
6✔
992

993
                    TARGET(POP_LIST)
994
                    {
995
                        {
996
                            Value list = *popAndResolveAsPtr(context);
6✔
997
                            Value number = *popAndResolveAsPtr(context);
6✔
998

999
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
6✔
1000
                                throw types::TypeCheckingError(
2✔
1001
                                    "pop",
1✔
1002
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
1003
                                    { list, number });
1✔
1004

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

1012
                            list.list().erase(list.list().begin() + idx);
3✔
1013
                            push(list, context);
3✔
1014
                        }
6✔
1015
                        DISPATCH();
3✔
1016
                    }
209✔
1017

1018
                    TARGET(POP_LIST_IN_PLACE)
1019
                    {
1020
                        {
1021
                            Value* list = popAndResolveAsPtr(context);
209✔
1022
                            Value number = *popAndResolveAsPtr(context);
209✔
1023

1024
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
209✔
1025
                                throw types::TypeCheckingError(
2✔
1026
                                    "pop!",
1✔
1027
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
1028
                                    { *list, number });
1✔
1029

1030
                            long idx = static_cast<long>(number.number());
208✔
1031
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
208✔
1032
                            if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
208✔
1033
                                throwVMError(
2✔
1034
                                    ErrorKind::Index,
1035
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
1036

1037
                            list->list().erase(list->list().begin() + idx);
206✔
1038
                        }
209✔
1039
                        DISPATCH();
206✔
1040
                    }
510✔
1041

1042
                    TARGET(SET_AT_INDEX)
1043
                    {
1044
                        {
1045
                            Value* list = popAndResolveAsPtr(context);
510✔
1046
                            Value number = *popAndResolveAsPtr(context);
510✔
1047
                            Value new_value = *popAndResolveAsPtr(context);
510✔
1048

1049
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
510✔
1050
                                throw types::TypeCheckingError(
2✔
1051
                                    "@=",
1✔
1052
                                    { { types::Contract {
3✔
1053
                                          { types::Typedef("list", ValueType::List),
3✔
1054
                                            types::Typedef("index", ValueType::Number),
1✔
1055
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
1056
                                      { types::Contract {
1✔
1057
                                          { types::Typedef("string", ValueType::String),
3✔
1058
                                            types::Typedef("index", ValueType::Number),
1✔
1059
                                            types::Typedef("char", ValueType::String) } } } },
1✔
1060
                                    { *list, number, new_value });
1✔
1061

1062
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
509✔
1063
                            long idx = static_cast<long>(number.number());
509✔
1064
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
509✔
1065
                            if (std::cmp_greater_equal(idx, size) || idx < 0)
509✔
1066
                                throwVMError(
2✔
1067
                                    ErrorKind::Index,
1068
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
2✔
1069

1070
                            if (list->valueType() == ValueType::List)
507✔
1071
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
505✔
1072
                            else
1073
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
1074
                        }
510✔
1075
                        DISPATCH();
507✔
1076
                    }
12✔
1077

1078
                    TARGET(SET_AT_2_INDEX)
1079
                    {
1080
                        {
1081
                            Value* list = popAndResolveAsPtr(context);
12✔
1082
                            Value x = *popAndResolveAsPtr(context);
12✔
1083
                            Value y = *popAndResolveAsPtr(context);
12✔
1084
                            Value new_value = *popAndResolveAsPtr(context);
12✔
1085

1086
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
12✔
1087
                                throw types::TypeCheckingError(
2✔
1088
                                    "@@=",
1✔
1089
                                    { { types::Contract {
2✔
1090
                                        { types::Typedef("list", ValueType::List),
4✔
1091
                                          types::Typedef("x", ValueType::Number),
1✔
1092
                                          types::Typedef("y", ValueType::Number),
1✔
1093
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
1094
                                    { *list, x, y, new_value });
1✔
1095

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

1103
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
13✔
1104
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
8✔
1105
                                throw types::TypeCheckingError(
2✔
1106
                                    "@@=",
1✔
1107
                                    { { types::Contract {
3✔
1108
                                          { types::Typedef("list", ValueType::List),
4✔
1109
                                            types::Typedef("x", ValueType::Number),
1✔
1110
                                            types::Typedef("y", ValueType::Number),
1✔
1111
                                            types::Typedef("new_value", ValueType::Any) } } },
1✔
1112
                                      { types::Contract {
1✔
1113
                                          { types::Typedef("string", ValueType::String),
4✔
1114
                                            types::Typedef("x", ValueType::Number),
1✔
1115
                                            types::Typedef("y", ValueType::Number),
1✔
1116
                                            types::Typedef("char", ValueType::String) } } } },
1✔
1117
                                    { *list, x, y, new_value });
1✔
1118

1119
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
1120
                            const std::size_t size =
8✔
1121
                                is_list
16✔
1122
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
1123
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1124

1125
                            long idx_x = static_cast<long>(y.number());
8✔
1126
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
8✔
1127
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
8✔
1128
                                throwVMError(
2✔
1129
                                    ErrorKind::Index,
1130
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1131

1132
                            if (is_list)
6✔
1133
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1134
                            else
1135
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1136
                        }
12✔
1137
                        DISPATCH();
6✔
1138
                    }
4,324✔
1139

1140
                    TARGET(POP)
1141
                    {
1142
                        pop(context);
4,324✔
1143
                        DISPATCH();
4,324✔
1144
                    }
23,905✔
1145

1146
                    TARGET(SHORTCIRCUIT_AND)
1147
                    {
1148
                        if (!*peekAndResolveAsPtr(context))
23,905✔
1149
                            jump(arg, context);
822✔
1150
                        else
1151
                            pop(context);
23,083✔
1152
                        DISPATCH();
23,905✔
1153
                    }
851✔
1154

1155
                    TARGET(SHORTCIRCUIT_OR)
1156
                    {
1157
                        if (!!*peekAndResolveAsPtr(context))
851✔
1158
                            jump(arg, context);
219✔
1159
                        else
1160
                            pop(context);
632✔
1161
                        DISPATCH();
851✔
1162
                    }
3,127✔
1163

1164
                    TARGET(CREATE_SCOPE)
1165
                    {
1166
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
3,127✔
1167
                        DISPATCH();
3,127✔
1168
                    }
33,122✔
1169

1170
                    TARGET(RESET_SCOPE_JUMP)
1171
                    {
1172
                        context.locals.back().reset();
33,122✔
1173
                        jump(arg, context);
33,122✔
1174
                        DISPATCH();
33,122✔
1175
                    }
3,126✔
1176

1177
                    TARGET(POP_SCOPE)
1178
                    {
1179
                        context.locals.pop_back();
3,126✔
1180
                        DISPATCH();
3,126✔
1181
                    }
×
1182

1183
                    TARGET(GET_CURRENT_PAGE_ADDR)
1184
                    {
1185
                        context.last_symbol = arg;
×
1186
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1187
                        DISPATCH();
×
1188
                    }
21✔
1189

1190
#pragma endregion
1191

1192
#pragma region "Operators"
1193

1194
                    TARGET(BREAKPOINT)
1195
                    {
1196
                        {
1197
                            bool breakpoint_active = true;
21✔
1198
                            if (arg == 1)
21✔
1199
                                breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym;
19✔
1200

1201
                            if (m_state.m_features & FeatureVMDebugger && breakpoint_active)
21✔
1202
                            {
1203
                                initDebugger(context);
9✔
1204
                                m_debugger->run(*this, context, /* from_breakpoint= */ true);
9✔
1205
                                m_debugger->resetContextToSavedState(context);
9✔
1206

1207
                                if (m_debugger->shouldQuitVM())
9✔
1208
                                    GOTO_HALT();
1✔
1209
                            }
8✔
1210
                        }
1211
                        DISPATCH();
20✔
1212
                    }
28,238✔
1213

1214
                    TARGET(ADD)
1215
                    {
1216
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,238✔
1217

1218
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
28,238✔
1219
                            push(Value(a->number() + b->number()), context);
19,579✔
1220
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
8,659✔
1221
                            push(Value(a->string() + b->string()), context);
8,658✔
1222
                        else
1223
                            throw types::TypeCheckingError(
2✔
1224
                                "+",
1✔
1225
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1226
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1227
                                { *a, *b });
1✔
1228
                        DISPATCH();
28,237✔
1229
                    }
379✔
1230

1231
                    TARGET(SUB)
1232
                    {
1233
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
379✔
1234

1235
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
379✔
1236
                            throw types::TypeCheckingError(
2✔
1237
                                "-",
1✔
1238
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1239
                                { *a, *b });
1✔
1240
                        push(Value(a->number() - b->number()), context);
378✔
1241
                        DISPATCH();
378✔
1242
                    }
825✔
1243

1244
                    TARGET(MUL)
1245
                    {
1246
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
825✔
1247

1248
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
825✔
1249
                            throw types::TypeCheckingError(
2✔
1250
                                "*",
1✔
1251
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1252
                                { *a, *b });
1✔
1253
                        push(Value(a->number() * b->number()), context);
824✔
1254
                        DISPATCH();
824✔
1255
                    }
141✔
1256

1257
                    TARGET(DIV)
1258
                    {
1259
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
141✔
1260

1261
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
141✔
1262
                            throw types::TypeCheckingError(
2✔
1263
                                "/",
1✔
1264
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1265
                                { *a, *b });
1✔
1266
                        auto d = b->number();
140✔
1267
                        if (d == 0)
140✔
1268
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1269

1270
                        push(Value(a->number() / d), context);
139✔
1271
                        DISPATCH();
139✔
1272
                    }
206✔
1273

1274
                    TARGET(GT)
1275
                    {
1276
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
206✔
1277
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
206✔
1278
                        DISPATCH();
206✔
1279
                    }
21,008✔
1280

1281
                    TARGET(LT)
1282
                    {
1283
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
21,008✔
1284
                        push(*a < *b ? Builtins::trueSym : Builtins::falseSym, context);
21,008✔
1285
                        DISPATCH();
21,008✔
1286
                    }
7,294✔
1287

1288
                    TARGET(LE)
1289
                    {
1290
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,294✔
1291
                        push((*a < *b || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
7,294✔
1292
                        DISPATCH();
7,294✔
1293
                    }
5,932✔
1294

1295
                    TARGET(GE)
1296
                    {
1297
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,932✔
1298
                        push((*b < *a || *a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,932✔
1299
                        DISPATCH();
5,932✔
1300
                    }
1,246✔
1301

1302
                    TARGET(NEQ)
1303
                    {
1304
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,246✔
1305
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1,246✔
1306
                        DISPATCH();
1,246✔
1307
                    }
18,245✔
1308

1309
                    TARGET(EQ)
1310
                    {
1311
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
18,245✔
1312
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
18,245✔
1313
                        DISPATCH();
18,245✔
1314
                    }
3,990✔
1315

1316
                    TARGET(LEN)
1317
                    {
1318
                        const Value* a = popAndResolveAsPtr(context);
3,990✔
1319

1320
                        if (a->valueType() == ValueType::List)
3,990✔
1321
                            push(Value(static_cast<int>(a->constList().size())), context);
1,579✔
1322
                        else if (a->valueType() == ValueType::String)
2,411✔
1323
                            push(Value(static_cast<int>(a->string().size())), context);
2,407✔
1324
                        else if (a->valueType() == ValueType::Dict)
4✔
1325
                            push(Value(static_cast<int>(a->dict().size())), context);
3✔
1326
                        else
1327
                            throw types::TypeCheckingError(
2✔
1328
                                "len",
1✔
1329
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
3✔
1330
                                    types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1331
                                    types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1332
                                { *a });
1✔
1333
                        DISPATCH();
3,989✔
1334
                    }
627✔
1335

1336
                    TARGET(IS_EMPTY)
1337
                    {
1338
                        const Value* a = popAndResolveAsPtr(context);
627✔
1339

1340
                        if (a->valueType() == ValueType::List)
627✔
1341
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
126✔
1342
                        else if (a->valueType() == ValueType::String)
501✔
1343
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
498✔
1344
                        else if (a->valueType() == ValueType::Dict)
3✔
1345
                            push(std::cmp_equal(a->dict().size(), 0) ? Builtins::trueSym : Builtins::falseSym, context);
2✔
1346
                        else if (a->valueType() == ValueType::Nil)
1✔
1347
                            push(Builtins::trueSym, context);
×
1348
                        else
1349
                            throw types::TypeCheckingError(
2✔
1350
                                "empty?",
1✔
1351
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
4✔
1352
                                    types::Contract { { types::Typedef("value", ValueType::Nil) } },
1✔
1353
                                    types::Contract { { types::Typedef("value", ValueType::String) } },
1✔
1354
                                    types::Contract { { types::Typedef("value", ValueType::Dict) } } } },
1✔
1355
                                { *a });
1✔
1356
                        DISPATCH();
626✔
1357
                    }
335✔
1358

1359
                    TARGET(TAIL)
1360
                    {
1361
                        Value* const a = popAndResolveAsPtr(context);
335✔
1362
                        push(helper::tail(a), context);
335✔
1363
                        DISPATCH();
334✔
1364
                    }
1,128✔
1365

1366
                    TARGET(HEAD)
1367
                    {
1368
                        Value* const a = popAndResolveAsPtr(context);
1,128✔
1369
                        push(helper::head(a), context);
1,128✔
1370
                        DISPATCH();
1,127✔
1371
                    }
2,391✔
1372

1373
                    TARGET(IS_NIL)
1374
                    {
1375
                        const Value* a = popAndResolveAsPtr(context);
2,391✔
1376
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,391✔
1377
                        DISPATCH();
2,391✔
1378
                    }
15✔
1379

1380
                    TARGET(TO_NUM)
1381
                    {
1382
                        const Value* a = popAndResolveAsPtr(context);
15✔
1383

1384
                        if (a->valueType() != ValueType::String)
15✔
1385
                            throw types::TypeCheckingError(
2✔
1386
                                "toNumber",
1✔
1387
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1388
                                { *a });
1✔
1389

1390
                        double val;
1391
                        if (Utils::isDouble(a->string(), &val))
14✔
1392
                            push(Value(val), context);
11✔
1393
                        else
1394
                            push(Builtins::nil, context);
3✔
1395
                        DISPATCH();
14✔
1396
                    }
156✔
1397

1398
                    TARGET(TO_STR)
1399
                    {
1400
                        const Value* a = popAndResolveAsPtr(context);
156✔
1401
                        push(Value(a->toString(*this)), context);
156✔
1402
                        DISPATCH();
156✔
1403
                    }
187✔
1404

1405
                    TARGET(AT)
1406
                    {
1407
                        Value& b = *popAndResolveAsPtr(context);
187✔
1408
                        Value& a = *popAndResolveAsPtr(context);
187✔
1409
                        push(helper::at(a, b, *this), context);
187✔
1410
                        DISPATCH();
185✔
1411
                    }
74✔
1412

1413
                    TARGET(AT_AT)
1414
                    {
1415
                        {
1416
                            const Value* x = popAndResolveAsPtr(context);
74✔
1417
                            const Value* y = popAndResolveAsPtr(context);
74✔
1418
                            Value& list = *popAndResolveAsPtr(context);
74✔
1419

1420
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
74✔
1421
                                list.valueType() != ValueType::List)
73✔
1422
                                throw types::TypeCheckingError(
2✔
1423
                                    "@@",
1✔
1424
                                    { { types::Contract {
2✔
1425
                                        { types::Typedef("src", ValueType::List),
3✔
1426
                                          types::Typedef("y", ValueType::Number),
1✔
1427
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1428
                                    { list, *y, *x });
1✔
1429

1430
                            long idx_y = static_cast<long>(y->number());
73✔
1431
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
73✔
1432
                            if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
73✔
1433
                                throwVMError(
2✔
1434
                                    ErrorKind::Index,
1435
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
2✔
1436

1437
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
71✔
1438
                            const std::size_t size =
71✔
1439
                                is_list
142✔
1440
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
42✔
1441
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
29✔
1442

1443
                            long idx_x = static_cast<long>(x->number());
71✔
1444
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
71✔
1445
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
71✔
1446
                                throwVMError(
2✔
1447
                                    ErrorKind::Index,
1448
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1449

1450
                            if (is_list)
69✔
1451
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
40✔
1452
                            else
1453
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
29✔
1454
                        }
1455
                        DISPATCH();
69✔
1456
                    }
16,406✔
1457

1458
                    TARGET(MOD)
1459
                    {
1460
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
16,406✔
1461
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
16,406✔
1462
                            throw types::TypeCheckingError(
2✔
1463
                                "mod",
1✔
1464
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1465
                                { *a, *b });
1✔
1466
                        push(Value(std::fmod(a->number(), b->number())), context);
16,405✔
1467
                        DISPATCH();
16,405✔
1468
                    }
28✔
1469

1470
                    TARGET(TYPE)
1471
                    {
1472
                        const Value* a = popAndResolveAsPtr(context);
28✔
1473
                        push(Value(std::to_string(a->valueType())), context);
28✔
1474
                        DISPATCH();
28✔
1475
                    }
3✔
1476

1477
                    TARGET(HAS_FIELD)
1478
                    {
1479
                        {
1480
                            Value* const field = popAndResolveAsPtr(context);
3✔
1481
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1482
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1483
                                throw types::TypeCheckingError(
2✔
1484
                                    "hasField",
1✔
1485
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1486
                                    { *closure, *field });
1✔
1487

1488
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1489
                            if (it == m_state.m_symbols.end())
2✔
1490
                            {
1491
                                push(Builtins::falseSym, context);
1✔
1492
                                DISPATCH();
1✔
1493
                            }
1494

1495
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1496
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1497
                        }
1498
                        DISPATCH();
1✔
1499
                    }
3,708✔
1500

1501
                    TARGET(NOT)
1502
                    {
1503
                        const Value* a = popAndResolveAsPtr(context);
3,708✔
1504
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,708✔
1505
                        DISPATCH();
3,708✔
1506
                    }
8,318✔
1507

1508
#pragma endregion
1509

1510
#pragma region "Super Instructions"
1511
                    TARGET(LOAD_CONST_LOAD_CONST)
1512
                    {
1513
                        UNPACK_ARGS();
8,318✔
1514
                        push(loadConstAsPtr(primary_arg), context);
8,318✔
1515
                        push(loadConstAsPtr(secondary_arg), context);
8,318✔
1516
                        context.inst_exec_counter++;
8,318✔
1517
                        DISPATCH();
8,318✔
1518
                    }
10,019✔
1519

1520
                    TARGET(LOAD_CONST_STORE)
1521
                    {
1522
                        UNPACK_ARGS();
10,019✔
1523
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
10,019✔
1524
                        DISPATCH();
10,019✔
1525
                    }
907✔
1526

1527
                    TARGET(LOAD_CONST_SET_VAL)
1528
                    {
1529
                        UNPACK_ARGS();
907✔
1530
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
907✔
1531
                        DISPATCH();
906✔
1532
                    }
25✔
1533

1534
                    TARGET(STORE_FROM)
1535
                    {
1536
                        UNPACK_ARGS();
25✔
1537
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
25✔
1538
                        DISPATCH();
24✔
1539
                    }
1,223✔
1540

1541
                    TARGET(STORE_FROM_INDEX)
1542
                    {
1543
                        UNPACK_ARGS();
1,223✔
1544
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,223✔
1545
                        DISPATCH();
1,223✔
1546
                    }
627✔
1547

1548
                    TARGET(SET_VAL_FROM)
1549
                    {
1550
                        UNPACK_ARGS();
627✔
1551
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
627✔
1552
                        DISPATCH();
627✔
1553
                    }
555✔
1554

1555
                    TARGET(SET_VAL_FROM_INDEX)
1556
                    {
1557
                        UNPACK_ARGS();
555✔
1558
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
555✔
1559
                        DISPATCH();
555✔
1560
                    }
68✔
1561

1562
                    TARGET(INCREMENT)
1563
                    {
1564
                        UNPACK_ARGS();
68✔
1565
                        {
1566
                            Value* var = loadSymbol(primary_arg, context);
68✔
1567

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

1572
                            if (var->valueType() == ValueType::Number)
68✔
1573
                                push(Value(var->number() + secondary_arg), context);
67✔
1574
                            else
1575
                                throw types::TypeCheckingError(
2✔
1576
                                    "+",
1✔
1577
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1578
                                    { *var, Value(secondary_arg) });
1✔
1579
                        }
1580
                        DISPATCH();
67✔
1581
                    }
88,028✔
1582

1583
                    TARGET(INCREMENT_BY_INDEX)
1584
                    {
1585
                        UNPACK_ARGS();
88,028✔
1586
                        {
1587
                            Value* var = loadSymbolFromIndex(primary_arg, context);
88,028✔
1588

1589
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1590
                            if (var->valueType() == ValueType::Reference)
88,028✔
1591
                                var = var->reference();
×
1592

1593
                            if (var->valueType() == ValueType::Number)
88,028✔
1594
                                push(Value(var->number() + secondary_arg), context);
88,027✔
1595
                            else
1596
                                throw types::TypeCheckingError(
2✔
1597
                                    "+",
1✔
1598
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1599
                                    { *var, Value(secondary_arg) });
1✔
1600
                        }
1601
                        DISPATCH();
88,027✔
1602
                    }
33,206✔
1603

1604
                    TARGET(INCREMENT_STORE)
1605
                    {
1606
                        UNPACK_ARGS();
33,206✔
1607
                        {
1608
                            Value* var = loadSymbol(primary_arg, context);
33,206✔
1609

1610
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1611
                            if (var->valueType() == ValueType::Reference)
33,206✔
1612
                                var = var->reference();
×
1613

1614
                            if (var->valueType() == ValueType::Number)
33,206✔
1615
                            {
1616
                                auto val = Value(var->number() + secondary_arg);
33,205✔
1617
                                setVal(primary_arg, &val, context);
33,205✔
1618
                            }
33,205✔
1619
                            else
1620
                                throw types::TypeCheckingError(
2✔
1621
                                    "+",
1✔
1622
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1623
                                    { *var, Value(secondary_arg) });
1✔
1624
                        }
1625
                        DISPATCH();
33,205✔
1626
                    }
1,854✔
1627

1628
                    TARGET(DECREMENT)
1629
                    {
1630
                        UNPACK_ARGS();
1,854✔
1631
                        {
1632
                            Value* var = loadSymbol(primary_arg, context);
1,854✔
1633

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

1638
                            if (var->valueType() == ValueType::Number)
1,854✔
1639
                                push(Value(var->number() - secondary_arg), context);
1,853✔
1640
                            else
1641
                                throw types::TypeCheckingError(
2✔
1642
                                    "-",
1✔
1643
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1644
                                    { *var, Value(secondary_arg) });
1✔
1645
                        }
1646
                        DISPATCH();
1,853✔
1647
                    }
194,414✔
1648

1649
                    TARGET(DECREMENT_BY_INDEX)
1650
                    {
1651
                        UNPACK_ARGS();
194,414✔
1652
                        {
1653
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,414✔
1654

1655
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1656
                            if (var->valueType() == ValueType::Reference)
194,414✔
1657
                                var = var->reference();
×
1658

1659
                            if (var->valueType() == ValueType::Number)
194,414✔
1660
                                push(Value(var->number() - secondary_arg), context);
194,413✔
1661
                            else
1662
                                throw types::TypeCheckingError(
2✔
1663
                                    "-",
1✔
1664
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1665
                                    { *var, Value(secondary_arg) });
1✔
1666
                        }
1667
                        DISPATCH();
194,413✔
1668
                    }
866✔
1669

1670
                    TARGET(DECREMENT_STORE)
1671
                    {
1672
                        UNPACK_ARGS();
866✔
1673
                        {
1674
                            Value* var = loadSymbol(primary_arg, context);
866✔
1675

1676
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1677
                            if (var->valueType() == ValueType::Reference)
866✔
1678
                                var = var->reference();
×
1679

1680
                            if (var->valueType() == ValueType::Number)
866✔
1681
                            {
1682
                                auto val = Value(var->number() - secondary_arg);
865✔
1683
                                setVal(primary_arg, &val, context);
865✔
1684
                            }
865✔
1685
                            else
1686
                                throw types::TypeCheckingError(
2✔
1687
                                    "-",
1✔
1688
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1689
                                    { *var, Value(secondary_arg) });
1✔
1690
                        }
1691
                        DISPATCH();
865✔
1692
                    }
1✔
1693

1694
                    TARGET(STORE_TAIL)
1695
                    {
1696
                        UNPACK_ARGS();
1✔
1697
                        {
1698
                            Value* list = loadSymbol(primary_arg, context);
1✔
1699
                            Value tail = helper::tail(list);
1✔
1700
                            store(secondary_arg, &tail, context);
1✔
1701
                        }
1✔
1702
                        DISPATCH();
1✔
1703
                    }
8✔
1704

1705
                    TARGET(STORE_TAIL_BY_INDEX)
1706
                    {
1707
                        UNPACK_ARGS();
8✔
1708
                        {
1709
                            Value* list = loadSymbolFromIndex(primary_arg, context);
8✔
1710
                            Value tail = helper::tail(list);
8✔
1711
                            store(secondary_arg, &tail, context);
8✔
1712
                        }
8✔
1713
                        DISPATCH();
8✔
1714
                    }
4✔
1715

1716
                    TARGET(STORE_HEAD)
1717
                    {
1718
                        UNPACK_ARGS();
4✔
1719
                        {
1720
                            Value* list = loadSymbol(primary_arg, context);
4✔
1721
                            Value head = helper::head(list);
4✔
1722
                            store(secondary_arg, &head, context);
4✔
1723
                        }
4✔
1724
                        DISPATCH();
4✔
1725
                    }
38✔
1726

1727
                    TARGET(STORE_HEAD_BY_INDEX)
1728
                    {
1729
                        UNPACK_ARGS();
38✔
1730
                        {
1731
                            Value* list = loadSymbolFromIndex(primary_arg, context);
38✔
1732
                            Value head = helper::head(list);
38✔
1733
                            store(secondary_arg, &head, context);
38✔
1734
                        }
38✔
1735
                        DISPATCH();
38✔
1736
                    }
1,013✔
1737

1738
                    TARGET(STORE_LIST)
1739
                    {
1740
                        UNPACK_ARGS();
1,013✔
1741
                        {
1742
                            Value l = createList(primary_arg, context);
1,013✔
1743
                            store(secondary_arg, &l, context);
1,013✔
1744
                        }
1,013✔
1745
                        DISPATCH();
1,013✔
1746
                    }
3✔
1747

1748
                    TARGET(SET_VAL_TAIL)
1749
                    {
1750
                        UNPACK_ARGS();
3✔
1751
                        {
1752
                            Value* list = loadSymbol(primary_arg, context);
3✔
1753
                            Value tail = helper::tail(list);
3✔
1754
                            setVal(secondary_arg, &tail, context);
3✔
1755
                        }
3✔
1756
                        DISPATCH();
3✔
1757
                    }
1✔
1758

1759
                    TARGET(SET_VAL_TAIL_BY_INDEX)
1760
                    {
1761
                        UNPACK_ARGS();
1✔
1762
                        {
1763
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1764
                            Value tail = helper::tail(list);
1✔
1765
                            setVal(secondary_arg, &tail, context);
1✔
1766
                        }
1✔
1767
                        DISPATCH();
1✔
1768
                    }
1✔
1769

1770
                    TARGET(SET_VAL_HEAD)
1771
                    {
1772
                        UNPACK_ARGS();
1✔
1773
                        {
1774
                            Value* list = loadSymbol(primary_arg, context);
1✔
1775
                            Value head = helper::head(list);
1✔
1776
                            setVal(secondary_arg, &head, context);
1✔
1777
                        }
1✔
1778
                        DISPATCH();
1✔
1779
                    }
1✔
1780

1781
                    TARGET(SET_VAL_HEAD_BY_INDEX)
1782
                    {
1783
                        UNPACK_ARGS();
1✔
1784
                        {
1785
                            Value* list = loadSymbolFromIndex(primary_arg, context);
1✔
1786
                            Value head = helper::head(list);
1✔
1787
                            setVal(secondary_arg, &head, context);
1✔
1788
                        }
1✔
1789
                        DISPATCH();
1✔
1790
                    }
1,695✔
1791

1792
                    TARGET(CALL_BUILTIN)
1793
                    {
1794
                        UNPACK_ARGS();
1,695✔
1795
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1796
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
1,695✔
1797
                        if (!m_running)
1,630✔
1798
                            GOTO_HALT();
×
1799
                        DISPATCH();
1,630✔
1800
                    }
11,709✔
1801

1802
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1803
                    {
1804
                        UNPACK_ARGS();
11,709✔
1805
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1806
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,709✔
1807
                        if (!m_running)
11,708✔
1808
                            GOTO_HALT();
×
1809
                        DISPATCH();
11,708✔
1810
                    }
877✔
1811

1812
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1813
                    {
1814
                        UNPACK_ARGS();
877✔
1815
                        const Value* sym = popAndResolveAsPtr(context);
877✔
1816
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
877✔
1817
                            jump(secondary_arg, context);
124✔
1818
                        DISPATCH();
877✔
1819
                    }
21,988✔
1820

1821
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1822
                    {
1823
                        UNPACK_ARGS();
21,988✔
1824
                        const Value* sym = popAndResolveAsPtr(context);
21,988✔
1825
                        if (*sym < *loadConstAsPtr(primary_arg))
21,988✔
1826
                            jump(secondary_arg, context);
10,960✔
1827
                        DISPATCH();
21,988✔
1828
                    }
6,917✔
1829

1830
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1831
                    {
1832
                        UNPACK_ARGS();
6,917✔
1833
                        const Value* sym = popAndResolveAsPtr(context);
6,917✔
1834
                        if (!(*sym < *loadSymbol(primary_arg, context)))
6,917✔
1835
                            jump(secondary_arg, context);
669✔
1836
                        DISPATCH();
6,917✔
1837
                    }
172,506✔
1838

1839
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1840
                    {
1841
                        UNPACK_ARGS();
172,506✔
1842
                        const Value* sym = popAndResolveAsPtr(context);
172,506✔
1843
                        const Value* cst = loadConstAsPtr(primary_arg);
172,506✔
1844
                        if (*cst < *sym)
172,506✔
1845
                            jump(secondary_arg, context);
86,589✔
1846
                        DISPATCH();
172,506✔
1847
                    }
187✔
1848

1849
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1850
                    {
1851
                        UNPACK_ARGS();
187✔
1852
                        const Value* sym = popAndResolveAsPtr(context);
187✔
1853
                        const Value* cst = loadConstAsPtr(primary_arg);
187✔
1854
                        if (!(*cst < *sym))
187✔
1855
                            jump(secondary_arg, context);
42✔
1856
                        DISPATCH();
187✔
1857
                    }
6✔
1858

1859
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1860
                    {
1861
                        UNPACK_ARGS();
6✔
1862
                        const Value* sym = popAndResolveAsPtr(context);
6✔
1863
                        const Value* rhs = loadSymbol(primary_arg, context);
6✔
1864
                        if (!(*rhs < *sym))
6✔
1865
                            jump(secondary_arg, context);
1✔
1866
                        DISPATCH();
6✔
1867
                    }
1,099✔
1868

1869
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1870
                    {
1871
                        UNPACK_ARGS();
1,099✔
1872
                        const Value* sym = popAndResolveAsPtr(context);
1,099✔
1873
                        if (*sym == *loadConstAsPtr(primary_arg))
1,099✔
1874
                            jump(secondary_arg, context);
41✔
1875
                        DISPATCH();
1,099✔
1876
                    }
87,351✔
1877

1878
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1879
                    {
1880
                        UNPACK_ARGS();
87,351✔
1881
                        const Value* sym = popAndResolveAsPtr(context);
87,351✔
1882
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
87,351✔
1883
                            jump(secondary_arg, context);
548✔
1884
                        DISPATCH();
87,351✔
1885
                    }
11✔
1886

1887
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1888
                    {
1889
                        UNPACK_ARGS();
11✔
1890
                        const Value* sym = popAndResolveAsPtr(context);
11✔
1891
                        if (*sym != *loadConstAsPtr(primary_arg))
11✔
1892
                            jump(secondary_arg, context);
2✔
1893
                        DISPATCH();
11✔
1894
                    }
30✔
1895

1896
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1897
                    {
1898
                        UNPACK_ARGS();
30✔
1899
                        const Value* sym = popAndResolveAsPtr(context);
30✔
1900
                        if (*sym == *loadSymbol(primary_arg, context))
30✔
1901
                            jump(secondary_arg, context);
10✔
1902
                        DISPATCH();
30✔
1903
                    }
27,982✔
1904

1905
                    TARGET(CALL_SYMBOL)
1906
                    {
1907
                        UNPACK_ARGS();
27,982✔
1908
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
27,982✔
1909
                        if (!m_running)
27,980✔
1910
                            GOTO_HALT();
×
1911
                        DISPATCH();
27,980✔
1912
                    }
109,875✔
1913

1914
                    TARGET(CALL_CURRENT_PAGE)
1915
                    {
1916
                        UNPACK_ARGS();
109,875✔
1917
                        context.last_symbol = primary_arg;
109,875✔
1918
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,875✔
1919
                        if (!m_running)
109,874✔
1920
                            GOTO_HALT();
×
1921
                        DISPATCH();
109,874✔
1922
                    }
3,003✔
1923

1924
                    TARGET(GET_FIELD_FROM_SYMBOL)
1925
                    {
1926
                        UNPACK_ARGS();
3,003✔
1927
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
3,003✔
1928
                        DISPATCH();
3,003✔
1929
                    }
842✔
1930

1931
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1932
                    {
1933
                        UNPACK_ARGS();
842✔
1934
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
842✔
1935
                        DISPATCH();
840✔
1936
                    }
16,135✔
1937

1938
                    TARGET(AT_SYM_SYM)
1939
                    {
1940
                        UNPACK_ARGS();
16,135✔
1941
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
16,135✔
1942
                        DISPATCH();
16,135✔
1943
                    }
49✔
1944

1945
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1946
                    {
1947
                        UNPACK_ARGS();
49✔
1948
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
49✔
1949
                        DISPATCH();
49✔
1950
                    }
1,045✔
1951

1952
                    TARGET(AT_SYM_INDEX_CONST)
1953
                    {
1954
                        UNPACK_ARGS();
1,045✔
1955
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
1,045✔
1956
                        DISPATCH();
1,042✔
1957
                    }
2✔
1958

1959
                    TARGET(CHECK_TYPE_OF)
1960
                    {
1961
                        UNPACK_ARGS();
2✔
1962
                        const Value* sym = loadSymbol(primary_arg, context);
2✔
1963
                        const Value* cst = loadConstAsPtr(secondary_arg);
2✔
1964
                        push(
2✔
1965
                            cst->valueType() == ValueType::String &&
4✔
1966
                                    std::to_string(sym->valueType()) == cst->string()
2✔
1967
                                ? Builtins::trueSym
1968
                                : Builtins::falseSym,
1969
                            context);
2✔
1970
                        DISPATCH();
2✔
1971
                    }
130✔
1972

1973
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1974
                    {
1975
                        UNPACK_ARGS();
130✔
1976
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
130✔
1977
                        const Value* cst = loadConstAsPtr(secondary_arg);
130✔
1978
                        push(
130✔
1979
                            cst->valueType() == ValueType::String &&
260✔
1980
                                    std::to_string(sym->valueType()) == cst->string()
130✔
1981
                                ? Builtins::trueSym
1982
                                : Builtins::falseSym,
1983
                            context);
130✔
1984
                        DISPATCH();
130✔
1985
                    }
3,461✔
1986

1987
                    TARGET(APPEND_IN_PLACE_SYM)
1988
                    {
1989
                        UNPACK_ARGS();
3,461✔
1990
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
3,461✔
1991
                        DISPATCH();
3,461✔
1992
                    }
14✔
1993

1994
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1995
                    {
1996
                        UNPACK_ARGS();
14✔
1997
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
14✔
1998
                        DISPATCH();
13✔
1999
                    }
123✔
2000

2001
                    TARGET(STORE_LEN)
2002
                    {
2003
                        UNPACK_ARGS();
123✔
2004
                        {
2005
                            Value* a = loadSymbolFromIndex(primary_arg, context);
123✔
2006
                            Value len;
123✔
2007
                            if (a->valueType() == ValueType::List)
123✔
2008
                                len = Value(static_cast<int>(a->constList().size()));
43✔
2009
                            else if (a->valueType() == ValueType::String)
80✔
2010
                                len = Value(static_cast<int>(a->string().size()));
79✔
2011
                            else
2012
                                throw types::TypeCheckingError(
2✔
2013
                                    "len",
1✔
2014
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2015
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2016
                                    { *a });
1✔
2017
                            store(secondary_arg, &len, context);
122✔
2018
                        }
123✔
2019
                        DISPATCH();
122✔
2020
                    }
9,245✔
2021

2022
                    TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
2023
                    {
2024
                        UNPACK_ARGS();
9,245✔
2025
                        {
2026
                            const Value* sym = loadSymbol(primary_arg, context);
9,245✔
2027
                            Value size;
9,245✔
2028

2029
                            if (sym->valueType() == ValueType::List)
9,245✔
2030
                                size = Value(static_cast<int>(sym->constList().size()));
3,574✔
2031
                            else if (sym->valueType() == ValueType::String)
5,671✔
2032
                                size = Value(static_cast<int>(sym->string().size()));
5,670✔
2033
                            else
2034
                                throw types::TypeCheckingError(
2✔
2035
                                    "len",
1✔
2036
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2037
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2038
                                    { *sym });
1✔
2039

2040
                            if (!(*popAndResolveAsPtr(context) < size))
9,244✔
2041
                                jump(secondary_arg, context);
1,213✔
2042
                        }
9,245✔
2043
                        DISPATCH();
9,244✔
2044
                    }
521✔
2045

2046
                    TARGET(MUL_BY)
2047
                    {
2048
                        UNPACK_ARGS();
521✔
2049
                        {
2050
                            Value* var = loadSymbol(primary_arg, context);
521✔
2051
                            const int other = static_cast<int>(secondary_arg) - 2048;
521✔
2052

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

2057
                            if (var->valueType() == ValueType::Number)
521✔
2058
                                push(Value(var->number() * other), context);
520✔
2059
                            else
2060
                                throw types::TypeCheckingError(
2✔
2061
                                    "*",
1✔
2062
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2063
                                    { *var, Value(other) });
1✔
2064
                        }
2065
                        DISPATCH();
520✔
2066
                    }
36✔
2067

2068
                    TARGET(MUL_BY_INDEX)
2069
                    {
2070
                        UNPACK_ARGS();
36✔
2071
                        {
2072
                            Value* var = loadSymbolFromIndex(primary_arg, context);
36✔
2073
                            const int other = static_cast<int>(secondary_arg) - 2048;
36✔
2074

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

2079
                            if (var->valueType() == ValueType::Number)
36✔
2080
                                push(Value(var->number() * other), context);
35✔
2081
                            else
2082
                                throw types::TypeCheckingError(
2✔
2083
                                    "*",
1✔
2084
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2085
                                    { *var, Value(other) });
1✔
2086
                        }
2087
                        DISPATCH();
35✔
2088
                    }
2✔
2089

2090
                    TARGET(MUL_SET_VAL)
2091
                    {
2092
                        UNPACK_ARGS();
2✔
2093
                        {
2094
                            Value* var = loadSymbol(primary_arg, context);
2✔
2095
                            const int other = static_cast<int>(secondary_arg) - 2048;
2✔
2096

2097
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
2098
                            if (var->valueType() == ValueType::Reference)
2✔
2099
                                var = var->reference();
×
2100

2101
                            if (var->valueType() == ValueType::Number)
2✔
2102
                            {
2103
                                auto val = Value(var->number() * other);
1✔
2104
                                setVal(primary_arg, &val, context);
1✔
2105
                            }
1✔
2106
                            else
2107
                                throw types::TypeCheckingError(
2✔
2108
                                    "*",
1✔
2109
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2110
                                    { *var, Value(other) });
1✔
2111
                        }
2112
                        DISPATCH();
1✔
2113
                    }
1,109✔
2114

2115
                    TARGET(FUSED_MATH)
2116
                    {
2117
                        const auto op1 = static_cast<Instruction>(padding),
1,109✔
2118
                                   op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
1,109✔
2119
                                   op3 = static_cast<Instruction>(arg & 0x00ff);
1,109✔
2120
                        const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
1,109✔
2121

2122
                        const Value* d = popAndResolveAsPtr(context);
1,109✔
2123
                        const Value* c = popAndResolveAsPtr(context);
1,109✔
2124
                        const Value* b = popAndResolveAsPtr(context);
1,109✔
2125

2126
                        if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
1,109✔
2127
                            throw types::TypeCheckingError(
2✔
2128
                                helper::mathInstToStr(op1),
1✔
2129
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2130
                                { *c, *d });
1✔
2131

2132
                        double temp = helper::doMath(c->number(), d->number(), op1);
1,108✔
2133
                        if (b->valueType() != ValueType::Number)
1,108✔
2134
                            throw types::TypeCheckingError(
4✔
2135
                                helper::mathInstToStr(op2),
2✔
2136
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
2✔
2137
                                { *b, Value(temp) });
2✔
2138
                        temp = helper::doMath(b->number(), temp, op2);
1,106✔
2139

2140
                        if (arg_count == 2)
1,105✔
2141
                            push(Value(temp), context);
1,068✔
2142
                        else if (arg_count == 3)
37✔
2143
                        {
2144
                            const Value* a = popAndResolveAsPtr(context);
37✔
2145
                            if (a->valueType() != ValueType::Number)
37✔
2146
                                throw types::TypeCheckingError(
2✔
2147
                                    helper::mathInstToStr(op3),
1✔
2148
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2149
                                    { *a, Value(temp) });
1✔
2150

2151
                            temp = helper::doMath(a->number(), temp, op3);
36✔
2152
                            push(Value(temp), context);
36✔
2153
                        }
36✔
2154
                        else
2155
                            throw Error(
×
2156
                                fmt::format(
×
2157
                                    "FUSED_MATH got {} arguments, expected 2 or 3. Arguments: {:x}{:x}{:x}. There is a bug in the codegen!",
×
2158
                                    arg_count, static_cast<uint8_t>(op1), static_cast<uint8_t>(op2), static_cast<uint8_t>(op3)));
×
2159
                        DISPATCH();
1,104✔
2160
                    }
2161
#pragma endregion
2162
                }
107✔
2163
#if ARK_USE_COMPUTED_GOTOS
2164
            dispatch_end:
2165
                do
107✔
2166
                {
2167
                } while (false);
107✔
2168
#endif
2169
            }
2170
        }
250✔
2171
        catch (const Error& e)
2172
        {
2173
            if (fail_with_exception)
99✔
2174
            {
2175
                std::stringstream stream;
98✔
2176
                backtrace(context, stream, /* colorize= */ false);
98✔
2177
                // It's important we have an Ark::Error here, as the constructor for NestedError
2178
                // does more than just aggregate error messages, hence the code duplication.
2179
                throw NestedError(e, stream.str(), *this);
98✔
2180
            }
98✔
2181
            else
2182
                showBacktraceWithException(Error(e.details(/* colorize= */ true, *this)), context);
1✔
2183
        }
206✔
2184
        catch (const std::exception& e)
2185
        {
2186
            if (fail_with_exception)
44✔
2187
            {
2188
                std::stringstream stream;
44✔
2189
                backtrace(context, stream, /* colorize= */ false);
44✔
2190
                throw NestedError(e, stream.str());
44✔
2191
            }
44✔
2192
            else
2193
                showBacktraceWithException(e, context);
×
2194
        }
143✔
2195
        catch (...)
2196
        {
2197
            if (fail_with_exception)
×
2198
                throw;
×
2199

2200
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2201
            throw;
2202
#endif
2203
            fmt::println("Unknown error");
×
2204
            backtrace(context);
×
2205
            m_exit_code = 1;
×
2206
        }
186✔
2207

2208
        return m_exit_code;
108✔
2209
    }
285✔
2210

2211
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,056✔
2212
    {
2,056✔
2213
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,202✔
2214
        {
2215
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,146✔
2216
                return id;
2,050✔
2217
        }
2,096,146✔
2218
        return MaxValue16Bits;
6✔
2219
    }
2,056✔
2220

2221
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, ExecutionContext& context)
6✔
2222
    {
6✔
2223
        std::vector<std::string> arg_names;
6✔
2224
        arg_names.reserve(expected_arg_count + 1);
6✔
2225
        if (expected_arg_count > 0)
6✔
2226
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
2227

2228
        std::size_t index = 0;
6✔
2229
        while (m_state.inst(context.pp, index) == STORE ||
12✔
2230
               m_state.inst(context.pp, index) == STORE_REF)
6✔
2231
        {
2232
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
2233
            arg_names.push_back(m_state.m_symbols[id]);
×
2234
            index += 4;
×
2235
        }
×
2236
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2237
        if (arg_names.size() == 1 && index == 0)
6✔
2238
        {
2239
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
2240
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
2241
                arg_names.emplace_back(1, static_cast<char>('a' + i));
2✔
2242
        }
2✔
2243

2244
        std::vector<std::string> arg_vals;
6✔
2245
        arg_vals.reserve(passed_arg_count + 1);
6✔
2246
        if (passed_arg_count > 0)
6✔
2247
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
2248

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

2253
        // set ip/pp to the callee location so that the error can pinpoint the line
2254
        // where the bad call happened
2255
        if (context.sp >= 2 + passed_arg_count)
6✔
2256
        {
2257
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
6✔
2258
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
6✔
2259
            context.sp -= 2;
6✔
2260
            returnFromFuncCall(context);
6✔
2261
        }
6✔
2262

2263
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
12✔
2264
            ? m_state.m_symbols[context.last_symbol]
6✔
2265
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2266

2267
        throwVMError(
6✔
2268
            ErrorKind::Arity,
2269
            fmt::format(
12✔
2270
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
6✔
2271
                function_name,
2272
                fmt::join(arg_vals, " "),
6✔
2273
                passed_arg_count,
2274
                passed_arg_count > 1 ? "s" : "",
6✔
2275
                expected_arg_count,
2276
                function_name,
2277
                fmt::join(arg_names, " ")));
6✔
2278
    }
12✔
2279

2280
    void VM::initDebugger(ExecutionContext& context)
10✔
2281
    {
10✔
2282
        if (!m_debugger)
10✔
2283
            m_debugger = std::make_unique<Debugger>(context, m_state.m_libenv, m_state.m_symbols, m_state.m_constants);
×
2284
        else
2285
            m_debugger->saveState(context);
10✔
2286
    }
10✔
2287

2288
    void VM::showBacktraceWithException(const std::exception& e, ExecutionContext& context)
1✔
2289
    {
1✔
2290
        std::string text = e.what();
1✔
2291
        if (!text.empty() && text.back() != '\n')
1✔
2292
            text += '\n';
×
2293
        fmt::println("{}", text);
1✔
2294

2295
        // If code being run from the debugger crashed, ignore it and don't trigger a debugger inside the VM inside the debugger inside the VM
2296
        const bool error_from_debugger = m_debugger && m_debugger->isRunning();
1✔
2297
        if (m_state.m_features & FeatureVMDebugger && !error_from_debugger)
1✔
2298
            initDebugger(context);
1✔
2299

2300
        const std::size_t saved_ip = context.ip;
1✔
2301
        const std::size_t saved_pp = context.pp;
1✔
2302
        const uint16_t saved_sp = context.sp;
1✔
2303

2304
        backtrace(context);
1✔
2305

2306
        fmt::println(
1✔
2307
            "At IP: {}, PP: {}, SP: {}",
1✔
2308
            // dividing by 4 because the instructions are actually on 4 bytes
2309
            fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)),
1✔
2310
            fmt::styled(saved_pp, fmt::fg(fmt::color::green)),
1✔
2311
            fmt::styled(saved_sp, fmt::fg(fmt::color::yellow)));
1✔
2312

2313
        if (m_debugger && !error_from_debugger)
1✔
2314
        {
2315
            m_debugger->resetContextToSavedState(context);
1✔
2316
            m_debugger->run(*this, context, /* from_breakpoint= */ false);
1✔
2317
        }
1✔
2318

2319
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2320
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2321
        m_exit_code = 0;
2322
#else
2323
        m_exit_code = 1;
1✔
2324
#endif
2325
    }
1✔
2326

2327
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,217✔
2328
    {
2,217✔
2329
        std::optional<InstLoc> match = std::nullopt;
2,217✔
2330

2331
        for (const auto location : m_state.m_inst_locations)
11,084✔
2332
        {
2333
            if (location.page_pointer == pp && !match)
8,867✔
2334
                match = location;
2,217✔
2335

2336
            // select the best match: we want to find the location that's nearest our instruction pointer,
2337
            // but not equal to it as the IP will always be pointing to the next instruction,
2338
            // not yet executed. Thus, the erroneous instruction is the previous one.
2339
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
8,867✔
2340
                match = location;
2,411✔
2341

2342
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2343
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
8,867✔
2344
                break;
2,081✔
2345
        }
8,867✔
2346

2347
        return match;
2,217✔
2348
    }
2349

2350
    std::string VM::debugShowSource() const
×
2351
    {
×
2352
        const auto& context = m_execution_contexts.front();
×
2353
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2354
        if (maybe_source_loc)
×
2355
        {
2356
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2357
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2358
        }
×
2359
        return "No source location found";
×
2360
    }
×
2361

2362
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
143✔
2363
    {
143✔
2364
        constexpr std::size_t max_consecutive_traces = 7;
143✔
2365

2366
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
143✔
2367
        if (maybe_location)
143✔
2368
        {
2369
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
143✔
2370

2371
            if (Utils::fileExists(filename))
143✔
2372
                Diagnostics::makeContext(
282✔
2373
                    Diagnostics::ErrorLocation {
282✔
2374
                        .filename = filename,
141✔
2375
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
141✔
2376
                        .end = std::nullopt,
141✔
2377
                        .maybe_content = std::nullopt },
141✔
2378
                    os,
141✔
2379
                    /* maybe_context= */ std::nullopt,
141✔
2380
                    /* colorize= */ colorize);
141✔
2381
            fmt::println(os, "");
143✔
2382
        }
143✔
2383

2384
        if (context.fc > 1)
143✔
2385
        {
2386
            // display call stack trace
2387
            const ScopeView old_scope = context.locals.back();
9✔
2388

2389
            std::string previous_trace;
9✔
2390
            std::size_t displayed_traces = 0;
9✔
2391
            std::size_t consecutive_similar_traces = 0;
9✔
2392

2393
            while (context.fc != 0 && context.pp != 0)
2,065✔
2394
            {
2395
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,056✔
2396
                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✔
2397

2398
                const uint16_t id = findNearestVariableIdWithValue(
2,056✔
2399
                    Value(static_cast<PageAddr_t>(context.pp)),
2,056✔
2400
                    context);
2,056✔
2401
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,056✔
2402

2403
                if (func_name + loc_as_text != previous_trace)
2,056✔
2404
                {
2405
                    fmt::println(
20✔
2406
                        os,
10✔
2407
                        "[{:4}] In function `{}'{}",
10✔
2408
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
10✔
2409
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
10✔
2410
                        loc_as_text);
2411
                    previous_trace = func_name + loc_as_text;
10✔
2412
                    ++displayed_traces;
10✔
2413
                    consecutive_similar_traces = 0;
10✔
2414
                }
10✔
2415
                else if (consecutive_similar_traces == 0)
2,046✔
2416
                {
2417
                    fmt::println(os, "       ...");
1✔
2418
                    ++consecutive_similar_traces;
1✔
2419
                }
1✔
2420

2421
                const Value* ip;
2,056✔
2422
                do
6,261✔
2423
                {
2424
                    ip = popAndResolveAsPtr(context);
6,261✔
2425
                } while (ip->valueType() != ValueType::InstPtr);
6,261✔
2426

2427
                context.ip = ip->pageAddr();
2,056✔
2428
                context.pp = pop(context)->pageAddr();
2,056✔
2429
                returnFromFuncCall(context);
2,056✔
2430

2431
                if (displayed_traces > max_consecutive_traces)
2,056✔
2432
                {
2433
                    fmt::println(os, "       ...");
×
2434
                    break;
×
2435
                }
2436
            }
2,056✔
2437

2438
            if (context.pp == 0)
9✔
2439
            {
2440
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
9✔
2441
                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✔
2442
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
9✔
2443
            }
9✔
2444

2445
            // display variables values in the current scope
2446
            fmt::println(os, "\nCurrent scope variables values:");
9✔
2447
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
10✔
2448
            {
2449
                fmt::println(
2✔
2450
                    os,
1✔
2451
                    "{} = {}",
1✔
2452
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2453
                    old_scope.atPos(i).second.toString(*this));
1✔
2454
            }
1✔
2455
        }
9✔
2456
    }
143✔
2457
}
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