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

ArkScript-lang / Ark / 21114730479

18 Jan 2026 04:11PM UTC coverage: 91.335% (-1.4%) from 92.743%
21114730479

Pull #628

github

web-flow
Merge 31f78d00a into ada0e0686
Pull Request #628: Feat/debugger

148 of 320 new or added lines in 11 files covered. (46.25%)

2 existing lines in 1 file now uncovered.

8580 of 9394 relevant lines covered (91.33%)

274862.98 hits per line

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

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

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

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

16
namespace Ark
17
{
18
    using namespace internal;
19

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

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

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

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

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

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

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

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

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

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

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

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

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

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

160
        context.sp = 0;
214✔
161
        context.fc = 1;
214✔
162

163
        m_shared_lib_objects.clear();
214✔
164
        context.stacked_closure_scopes.clear();
214✔
165
        context.stacked_closure_scopes.emplace_back(nullptr);
214✔
166

167
        context.saved_scope.reset();
214✔
168
        m_exit_code = 0;
214✔
169

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

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

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

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

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

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

241
        return l;
1,829✔
242
    }
1,829✔
243

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

257
        for (std::size_t i = 0; i < count; ++i)
6,930✔
258
            list->push_back(*popAndResolveAsPtr(context));
3,465✔
259
    }
3,466✔
260

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

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

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

282
        m_no_value = Builtins::nil;
×
283
        return m_no_value;
×
284
    }
38✔
285

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

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

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

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

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

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

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

336
        m_shared_lib_objects.emplace_back(lib);
1✔
337

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

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

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

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

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

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

376
        ExecutionContext* ctx = nullptr;
17✔
377

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

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

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

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

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

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

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

425
        return ctx;
17✔
426
    }
17✔
427

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

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

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

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

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

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

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

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

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

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

513
    void VM::throwVMError(ErrorKind kind, const std::string& message)
34✔
514
    {
34✔
515
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
34✔
516
    }
34✔
517

518
    int VM::run(const bool fail_with_exception)
214✔
519
    {
214✔
520
        init();
214✔
521
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
214✔
522
        return m_exit_code;
214✔
523
    }
524

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

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

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

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

691
        try
692
        {
693
            uint8_t inst = 0;
232✔
694
            uint8_t padding = 0;
232✔
695
            uint16_t arg = 0;
232✔
696
            uint16_t primary_arg = 0;
232✔
697
            uint16_t secondary_arg = 0;
232✔
698

699
            m_running = true;
232✔
700

701
            DISPATCH();
232✔
702
            // cppcheck-suppress unreachableCode ; analysis cannot follow the chain of goto... but it works!
703
            {
704
#if !ARK_USE_COMPUTED_GOTOS
705
            dispatch_opcode:
706
                switch (inst)
707
#endif
708
                {
×
709
#pragma region "Instructions"
710
                    TARGET(NOP)
711
                    {
712
                        DISPATCH();
×
713
                    }
144,290✔
714

715
                    TARGET(LOAD_FAST)
716
                    {
717
                        push(loadSymbol(arg, context), context);
144,290✔
718
                        DISPATCH();
144,290✔
719
                    }
335,323✔
720

721
                    TARGET(LOAD_FAST_BY_INDEX)
722
                    {
723
                        push(loadSymbolFromIndex(arg, context), context);
335,323✔
724
                        DISPATCH();
335,323✔
725
                    }
4,598✔
726

727
                    TARGET(LOAD_SYMBOL)
728
                    {
729
                        // force resolving the reference
730
                        push(*loadSymbol(arg, context), context);
4,598✔
731
                        DISPATCH();
4,598✔
732
                    }
116,320✔
733

734
                    TARGET(LOAD_CONST)
735
                    {
736
                        push(loadConstAsPtr(arg), context);
116,320✔
737
                        DISPATCH();
116,320✔
738
                    }
30,834✔
739

740
                    TARGET(POP_JUMP_IF_TRUE)
741
                    {
742
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
39,222✔
743
                            jump(arg, context);
8,388✔
744
                        DISPATCH();
30,834✔
745
                    }
403,302✔
746

747
                    TARGET(STORE)
748
                    {
749
                        store(arg, popAndResolveAsPtr(context), context);
403,302✔
750
                        DISPATCH();
403,302✔
751
                    }
469✔
752

753
                    TARGET(STORE_REF)
754
                    {
755
                        // Not resolving a potential ref is on purpose!
756
                        // This instruction is only used by functions when storing arguments
757
                        const Value* tmp = pop(context);
469✔
758
                        store(arg, tmp, context);
469✔
759
                        DISPATCH();
469✔
760
                    }
23,570✔
761

762
                    TARGET(SET_VAL)
763
                    {
764
                        setVal(arg, popAndResolveAsPtr(context), context);
23,570✔
765
                        DISPATCH();
23,570✔
766
                    }
18,987✔
767

768
                    TARGET(POP_JUMP_IF_FALSE)
769
                    {
770
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
20,054✔
771
                            jump(arg, context);
1,067✔
772
                        DISPATCH();
18,987✔
773
                    }
208,871✔
774

775
                    TARGET(JUMP)
776
                    {
777
                        jump(arg, context);
208,871✔
778
                        DISPATCH();
208,871✔
779
                    }
138,986✔
780

781
                    TARGET(RET)
782
                    {
783
                        {
784
                            Value ip_or_val = *popAndResolveAsPtr(context);
138,986✔
785
                            // no return value on the stack
786
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
138,986✔
787
                            {
788
                                context.ip = ip_or_val.pageAddr();
3,788✔
789
                                // we always push PP then IP, thus the next value
790
                                // MUST be the page pointer
791
                                context.pp = pop(context)->pageAddr();
3,788✔
792

793
                                returnFromFuncCall(context);
3,788✔
794
                                push(Builtins::nil, context);
3,788✔
795
                            }
3,788✔
796
                            // value on the stack
797
                            else [[likely]]
798
                            {
799
                                const Value* ip = popAndResolveAsPtr(context);
135,198✔
800
                                assert(ip->valueType() == ValueType::InstPtr && "Expected instruction pointer on the stack (is the stack trashed?)");
135,198✔
801
                                context.ip = ip->pageAddr();
135,198✔
802
                                context.pp = pop(context)->pageAddr();
135,198✔
803

804
                                returnFromFuncCall(context);
135,198✔
805
                                push(std::move(ip_or_val), context);
135,198✔
806
                            }
807

808
                            if (context.fc <= untilFrameCount)
138,986✔
809
                                GOTO_HALT();
18✔
810
                        }
138,986✔
811

812
                        DISPATCH();
138,968✔
813
                    }
72✔
814

815
                    TARGET(HALT)
816
                    {
817
                        m_running = false;
72✔
818
                        GOTO_HALT();
72✔
819
                    }
142,713✔
820

821
                    TARGET(PUSH_RETURN_ADDRESS)
822
                    {
823
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
142,713✔
824
                        // arg * 4 to skip over the call instruction, so that the return address points to AFTER the call
825
                        push(Value(ValueType::InstPtr, static_cast<PageAddr_t>(arg * 4)), context);
142,713✔
826
                        context.inst_exec_counter++;
142,713✔
827
                        DISPATCH();
142,713✔
828
                    }
3,279✔
829

830
                    TARGET(CALL)
831
                    {
832
                        call(context, arg);
3,279✔
833
                        if (!m_running)
3,271✔
834
                            GOTO_HALT();
×
835
                        DISPATCH();
3,271✔
836
                    }
3,191✔
837

838
                    TARGET(CAPTURE)
839
                    {
840
                        if (!context.saved_scope)
3,191✔
841
                            context.saved_scope = ClosureScope();
631✔
842

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

854
                        DISPATCH();
3,191✔
855
                    }
13✔
856

857
                    TARGET(RENAME_NEXT_CAPTURE)
858
                    {
859
                        context.capture_rename_id = arg;
13✔
860
                        DISPATCH();
13✔
861
                    }
1,860✔
862

863
                    TARGET(BUILTIN)
864
                    {
865
                        push(Builtins::builtins[arg].second, context);
1,860✔
866
                        DISPATCH();
1,860✔
867
                    }
2✔
868

869
                    TARGET(DEL)
870
                    {
871
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
2✔
872
                        {
873
                            if (var->valueType() == ValueType::User)
1✔
874
                                var->usertypeRef().del();
1✔
875
                            *var = Value();
1✔
876
                            DISPATCH();
1✔
877
                        }
878

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

882
                    TARGET(MAKE_CLOSURE)
883
                    {
884
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
631✔
885
                        context.saved_scope.reset();
631✔
886
                        DISPATCH();
631✔
887
                    }
6✔
888

889
                    TARGET(GET_FIELD)
890
                    {
891
                        Value* var = popAndResolveAsPtr(context);
6✔
892
                        push(getField(var, arg, context), context);
6✔
893
                        DISPATCH();
6✔
894
                    }
1✔
895

896
                    TARGET(PLUGIN)
897
                    {
898
                        loadPlugin(arg, context);
1✔
899
                        DISPATCH();
1✔
900
                    }
821✔
901

902
                    TARGET(LIST)
903
                    {
904
                        {
905
                            Value l = createList(arg, context);
821✔
906
                            push(std::move(l), context);
821✔
907
                        }
821✔
908
                        DISPATCH();
821✔
909
                    }
30✔
910

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

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

928
                            Value obj { *list };
29✔
929
                            obj.list().reserve(size + arg);
29✔
930

931
                            for (uint16_t i = 0; i < arg; ++i)
58✔
932
                                obj.push_back(*popAndResolveAsPtr(context));
29✔
933
                            push(std::move(obj), context);
29✔
934
                        }
29✔
935
                        DISPATCH();
29✔
936
                    }
15✔
937

938
                    TARGET(CONCAT)
939
                    {
940
                        {
941
                            Value* list = popAndResolveAsPtr(context);
15✔
942
                            Value obj { *list };
15✔
943

944
                            for (uint16_t i = 0; i < arg; ++i)
30✔
945
                            {
946
                                Value* next = popAndResolveAsPtr(context);
17✔
947

948
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
17✔
949
                                    throw types::TypeCheckingError(
4✔
950
                                        "concat",
2✔
951
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
952
                                        { *list, *next });
2✔
953

954
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
15✔
955
                            }
15✔
956
                            push(std::move(obj), context);
13✔
957
                        }
15✔
958
                        DISPATCH();
13✔
959
                    }
1✔
960

961
                    TARGET(APPEND_IN_PLACE)
962
                    {
963
                        Value* list = popAndResolveAsPtr(context);
1✔
964
                        listAppendInPlace(list, arg, context);
1✔
965
                        DISPATCH();
1✔
966
                    }
570✔
967

968
                    TARGET(CONCAT_IN_PLACE)
969
                    {
970
                        Value* list = popAndResolveAsPtr(context);
570✔
971

972
                        for (uint16_t i = 0; i < arg; ++i)
1,175✔
973
                        {
974
                            Value* next = popAndResolveAsPtr(context);
607✔
975

976
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
607✔
977
                                throw types::TypeCheckingError(
4✔
978
                                    "concat!",
2✔
979
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
2✔
980
                                    { *list, *next });
2✔
981

982
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
605✔
983
                        }
605✔
984
                        DISPATCH();
568✔
985
                    }
6✔
986

987
                    TARGET(POP_LIST)
988
                    {
989
                        {
990
                            Value list = *popAndResolveAsPtr(context);
6✔
991
                            Value number = *popAndResolveAsPtr(context);
6✔
992

993
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
6✔
994
                                throw types::TypeCheckingError(
2✔
995
                                    "pop",
1✔
996
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
997
                                    { list, number });
1✔
998

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

1006
                            list.list().erase(list.list().begin() + idx);
3✔
1007
                            push(list, context);
3✔
1008
                        }
6✔
1009
                        DISPATCH();
3✔
1010
                    }
208✔
1011

1012
                    TARGET(POP_LIST_IN_PLACE)
1013
                    {
1014
                        {
1015
                            Value* list = popAndResolveAsPtr(context);
208✔
1016
                            Value number = *popAndResolveAsPtr(context);
208✔
1017

1018
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
208✔
1019
                                throw types::TypeCheckingError(
2✔
1020
                                    "pop!",
1✔
1021
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
1✔
1022
                                    { *list, number });
1✔
1023

1024
                            long idx = static_cast<long>(number.number());
207✔
1025
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
207✔
1026
                            if (std::cmp_greater_equal(idx, list->list().size()) || idx < 0)
207✔
1027
                                throwVMError(
2✔
1028
                                    ErrorKind::Index,
1029
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
2✔
1030

1031
                            list->list().erase(list->list().begin() + idx);
205✔
1032
                        }
208✔
1033
                        DISPATCH();
205✔
1034
                    }
510✔
1035

1036
                    TARGET(SET_AT_INDEX)
1037
                    {
1038
                        {
1039
                            Value* list = popAndResolveAsPtr(context);
510✔
1040
                            Value number = *popAndResolveAsPtr(context);
510✔
1041
                            Value new_value = *popAndResolveAsPtr(context);
510✔
1042

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

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

1064
                            if (list->valueType() == ValueType::List)
507✔
1065
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
505✔
1066
                            else
1067
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
1068
                        }
510✔
1069
                        DISPATCH();
507✔
1070
                    }
12✔
1071

1072
                    TARGET(SET_AT_2_INDEX)
1073
                    {
1074
                        {
1075
                            Value* list = popAndResolveAsPtr(context);
12✔
1076
                            Value x = *popAndResolveAsPtr(context);
12✔
1077
                            Value y = *popAndResolveAsPtr(context);
12✔
1078
                            Value new_value = *popAndResolveAsPtr(context);
12✔
1079

1080
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
12✔
1081
                                throw types::TypeCheckingError(
2✔
1082
                                    "@@=",
1✔
1083
                                    { { types::Contract {
2✔
1084
                                        { types::Typedef("list", ValueType::List),
4✔
1085
                                          types::Typedef("x", ValueType::Number),
1✔
1086
                                          types::Typedef("y", ValueType::Number),
1✔
1087
                                          types::Typedef("new_value", ValueType::Any) } } } },
1✔
1088
                                    { *list, x, y, new_value });
1✔
1089

1090
                            long idx_y = static_cast<long>(x.number());
11✔
1091
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
11✔
1092
                            if (std::cmp_greater_equal(idx_y, list->list().size()) || idx_y < 0)
11✔
1093
                                throwVMError(
2✔
1094
                                    ErrorKind::Index,
1095
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
2✔
1096

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

1113
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
8✔
1114
                            const std::size_t size =
8✔
1115
                                is_list
16✔
1116
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
6✔
1117
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
1118

1119
                            long idx_x = static_cast<long>(y.number());
8✔
1120
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
8✔
1121
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
8✔
1122
                                throwVMError(
2✔
1123
                                    ErrorKind::Index,
1124
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1125

1126
                            if (is_list)
6✔
1127
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
1128
                            else
1129
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
1130
                        }
12✔
1131
                        DISPATCH();
6✔
1132
                    }
4,265✔
1133

1134
                    TARGET(POP)
1135
                    {
1136
                        pop(context);
4,265✔
1137
                        DISPATCH();
4,265✔
1138
                    }
23,905✔
1139

1140
                    TARGET(SHORTCIRCUIT_AND)
1141
                    {
1142
                        if (!*peekAndResolveAsPtr(context))
23,905✔
1143
                            jump(arg, context);
822✔
1144
                        else
1145
                            pop(context);
23,083✔
1146
                        DISPATCH();
23,905✔
1147
                    }
851✔
1148

1149
                    TARGET(SHORTCIRCUIT_OR)
1150
                    {
1151
                        if (!!*peekAndResolveAsPtr(context))
851✔
1152
                            jump(arg, context);
219✔
1153
                        else
1154
                            pop(context);
632✔
1155
                        DISPATCH();
851✔
1156
                    }
3,112✔
1157

1158
                    TARGET(CREATE_SCOPE)
1159
                    {
1160
                        context.locals.emplace_back(context.scopes_storage.data(), context.locals.back().storageEnd());
3,112✔
1161
                        DISPATCH();
3,112✔
1162
                    }
33,077✔
1163

1164
                    TARGET(RESET_SCOPE_JUMP)
1165
                    {
1166
                        context.locals.back().reset();
33,077✔
1167
                        jump(arg, context);
33,077✔
1168
                        DISPATCH();
33,077✔
1169
                    }
3,111✔
1170

1171
                    TARGET(POP_SCOPE)
1172
                    {
1173
                        context.locals.pop_back();
3,111✔
1174
                        DISPATCH();
3,111✔
1175
                    }
×
1176

1177
                    TARGET(GET_CURRENT_PAGE_ADDR)
1178
                    {
1179
                        context.last_symbol = arg;
×
1180
                        push(Value(static_cast<PageAddr_t>(context.pp)), context);
×
1181
                        DISPATCH();
×
UNCOV
1182
                    }
×
1183

1184
#pragma endregion
1185

1186
#pragma region "Operators"
1187

1188
                    TARGET(BREAKPOINT)
1189
                    {
1190
                        {
NEW
1191
                            bool breakpoint_active = true;
×
NEW
1192
                            if (arg == 1)
×
NEW
1193
                                breakpoint_active = *popAndResolveAsPtr(context) == Builtins::trueSym;
×
1194

NEW
1195
                            if (m_state.m_features & FeatureVMDebugger && breakpoint_active)
×
1196
                            {
NEW
1197
                                initDebugger(context);
×
NEW
1198
                                m_debugger->run(*this, context);
×
NEW
1199
                                m_debugger->resetContextToSavedState(context);
×
1200

NEW
1201
                                if (m_debugger->shouldQuitVM())
×
NEW
1202
                                    GOTO_HALT();
×
NEW
1203
                            }
×
1204
                        }
NEW
1205
                        DISPATCH();
×
1206
                    }
28,181✔
1207

1208
                    TARGET(ADD)
1209
                    {
1210
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,181✔
1211

1212
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
28,181✔
1213
                            push(Value(a->number() + b->number()), context);
19,575✔
1214
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
8,606✔
1215
                            push(Value(a->string() + b->string()), context);
8,605✔
1216
                        else
1217
                            throw types::TypeCheckingError(
2✔
1218
                                "+",
1✔
1219
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
2✔
1220
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
1✔
1221
                                { *a, *b });
1✔
1222
                        DISPATCH();
28,180✔
1223
                    }
379✔
1224

1225
                    TARGET(SUB)
1226
                    {
1227
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
379✔
1228

1229
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
379✔
1230
                            throw types::TypeCheckingError(
2✔
1231
                                "-",
1✔
1232
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1233
                                { *a, *b });
1✔
1234
                        push(Value(a->number() - b->number()), context);
378✔
1235
                        DISPATCH();
378✔
1236
                    }
825✔
1237

1238
                    TARGET(MUL)
1239
                    {
1240
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
825✔
1241

1242
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
825✔
1243
                            throw types::TypeCheckingError(
2✔
1244
                                "*",
1✔
1245
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1246
                                { *a, *b });
1✔
1247
                        push(Value(a->number() * b->number()), context);
824✔
1248
                        DISPATCH();
824✔
1249
                    }
141✔
1250

1251
                    TARGET(DIV)
1252
                    {
1253
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
141✔
1254

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

1264
                        push(Value(a->number() / d), context);
139✔
1265
                        DISPATCH();
139✔
1266
                    }
187✔
1267

1268
                    TARGET(GT)
1269
                    {
1270
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
187✔
1271
                        push(*b < *a ? Builtins::trueSym : Builtins::falseSym, context);
187✔
1272
                        DISPATCH();
187✔
1273
                    }
21,008✔
1274

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

1282
                    TARGET(LE)
1283
                    {
1284
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
7,293✔
1285
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
7,293✔
1286
                        DISPATCH();
7,293✔
1287
                    }
5,931✔
1288

1289
                    TARGET(GE)
1290
                    {
1291
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
5,931✔
1292
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
5,931✔
1293
                        DISPATCH();
5,931✔
1294
                    }
1,235✔
1295

1296
                    TARGET(NEQ)
1297
                    {
1298
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,235✔
1299
                        push(*a != *b ? Builtins::trueSym : Builtins::falseSym, context);
1,235✔
1300
                        DISPATCH();
1,235✔
1301
                    }
18,219✔
1302

1303
                    TARGET(EQ)
1304
                    {
1305
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
18,219✔
1306
                        push(*a == *b ? Builtins::trueSym : Builtins::falseSym, context);
18,219✔
1307
                        DISPATCH();
18,219✔
1308
                    }
3,969✔
1309

1310
                    TARGET(LEN)
1311
                    {
1312
                        const Value* a = popAndResolveAsPtr(context);
3,969✔
1313

1314
                        if (a->valueType() == ValueType::List)
3,969✔
1315
                            push(Value(static_cast<int>(a->constList().size())), context);
1,561✔
1316
                        else if (a->valueType() == ValueType::String)
2,408✔
1317
                            push(Value(static_cast<int>(a->string().size())), context);
2,407✔
1318
                        else
1319
                            throw types::TypeCheckingError(
2✔
1320
                                "len",
1✔
1321
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
1322
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1323
                                { *a });
1✔
1324
                        DISPATCH();
3,968✔
1325
                    }
625✔
1326

1327
                    TARGET(IS_EMPTY)
1328
                    {
1329
                        const Value* a = popAndResolveAsPtr(context);
625✔
1330

1331
                        if (a->valueType() == ValueType::List)
625✔
1332
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
126✔
1333
                        else if (a->valueType() == ValueType::String)
499✔
1334
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
498✔
1335
                        else if (a->valueType() == ValueType::Nil)
1✔
1336
                            push(Builtins::trueSym, context);
×
1337
                        else
1338
                            throw types::TypeCheckingError(
2✔
1339
                                "empty?",
1✔
1340
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
3✔
1341
                                    types::Contract { { types::Typedef("value", ValueType::Nil) } },
1✔
1342
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1343
                                { *a });
1✔
1344
                        DISPATCH();
624✔
1345
                    }
335✔
1346

1347
                    TARGET(TAIL)
1348
                    {
1349
                        Value* const a = popAndResolveAsPtr(context);
335✔
1350
                        push(helper::tail(a), context);
335✔
1351
                        DISPATCH();
334✔
1352
                    }
1,128✔
1353

1354
                    TARGET(HEAD)
1355
                    {
1356
                        Value* const a = popAndResolveAsPtr(context);
1,128✔
1357
                        push(helper::head(a), context);
1,128✔
1358
                        DISPATCH();
1,127✔
1359
                    }
2,379✔
1360

1361
                    TARGET(IS_NIL)
1362
                    {
1363
                        const Value* a = popAndResolveAsPtr(context);
2,379✔
1364
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
2,379✔
1365
                        DISPATCH();
2,379✔
1366
                    }
15✔
1367

1368
                    TARGET(TO_NUM)
1369
                    {
1370
                        const Value* a = popAndResolveAsPtr(context);
15✔
1371

1372
                        if (a->valueType() != ValueType::String)
15✔
1373
                            throw types::TypeCheckingError(
2✔
1374
                                "toNumber",
1✔
1375
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
1376
                                { *a });
1✔
1377

1378
                        double val;
1379
                        if (Utils::isDouble(a->string(), &val))
14✔
1380
                            push(Value(val), context);
11✔
1381
                        else
1382
                            push(Builtins::nil, context);
3✔
1383
                        DISPATCH();
14✔
1384
                    }
145✔
1385

1386
                    TARGET(TO_STR)
1387
                    {
1388
                        const Value* a = popAndResolveAsPtr(context);
145✔
1389
                        push(Value(a->toString(*this)), context);
145✔
1390
                        DISPATCH();
145✔
1391
                    }
187✔
1392

1393
                    TARGET(AT)
1394
                    {
1395
                        Value& b = *popAndResolveAsPtr(context);
187✔
1396
                        Value& a = *popAndResolveAsPtr(context);
187✔
1397
                        push(helper::at(a, b, *this), context);
187✔
1398
                        DISPATCH();
185✔
1399
                    }
74✔
1400

1401
                    TARGET(AT_AT)
1402
                    {
1403
                        {
1404
                            const Value* x = popAndResolveAsPtr(context);
74✔
1405
                            const Value* y = popAndResolveAsPtr(context);
74✔
1406
                            Value& list = *popAndResolveAsPtr(context);
74✔
1407

1408
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
74✔
1409
                                list.valueType() != ValueType::List)
73✔
1410
                                throw types::TypeCheckingError(
2✔
1411
                                    "@@",
1✔
1412
                                    { { types::Contract {
2✔
1413
                                        { types::Typedef("src", ValueType::List),
3✔
1414
                                          types::Typedef("y", ValueType::Number),
1✔
1415
                                          types::Typedef("x", ValueType::Number) } } } },
1✔
1416
                                    { list, *y, *x });
1✔
1417

1418
                            long idx_y = static_cast<long>(y->number());
73✔
1419
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
73✔
1420
                            if (std::cmp_greater_equal(idx_y, list.list().size()) || idx_y < 0)
73✔
1421
                                throwVMError(
2✔
1422
                                    ErrorKind::Index,
1423
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
2✔
1424

1425
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
71✔
1426
                            const std::size_t size =
71✔
1427
                                is_list
142✔
1428
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
42✔
1429
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
29✔
1430

1431
                            long idx_x = static_cast<long>(x->number());
71✔
1432
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
71✔
1433
                            if (std::cmp_greater_equal(idx_x, size) || idx_x < 0)
71✔
1434
                                throwVMError(
2✔
1435
                                    ErrorKind::Index,
1436
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
2✔
1437

1438
                            if (is_list)
69✔
1439
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
40✔
1440
                            else
1441
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
29✔
1442
                        }
1443
                        DISPATCH();
69✔
1444
                    }
16,406✔
1445

1446
                    TARGET(MOD)
1447
                    {
1448
                        const Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
16,406✔
1449
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
16,406✔
1450
                            throw types::TypeCheckingError(
2✔
1451
                                "mod",
1✔
1452
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1453
                                { *a, *b });
1✔
1454
                        push(Value(std::fmod(a->number(), b->number())), context);
16,405✔
1455
                        DISPATCH();
16,405✔
1456
                    }
27✔
1457

1458
                    TARGET(TYPE)
1459
                    {
1460
                        const Value* a = popAndResolveAsPtr(context);
27✔
1461
                        push(Value(std::to_string(a->valueType())), context);
27✔
1462
                        DISPATCH();
27✔
1463
                    }
3✔
1464

1465
                    TARGET(HAS_FIELD)
1466
                    {
1467
                        {
1468
                            Value* const field = popAndResolveAsPtr(context);
3✔
1469
                            Value* const closure = popAndResolveAsPtr(context);
3✔
1470
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
3✔
1471
                                throw types::TypeCheckingError(
2✔
1472
                                    "hasField",
1✔
1473
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
1✔
1474
                                    { *closure, *field });
1✔
1475

1476
                            auto it = std::ranges::find(m_state.m_symbols, field->stringRef());
2✔
1477
                            if (it == m_state.m_symbols.end())
2✔
1478
                            {
1479
                                push(Builtins::falseSym, context);
1✔
1480
                                DISPATCH();
1✔
1481
                            }
1482

1483
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1484
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1485
                        }
1486
                        DISPATCH();
1✔
1487
                    }
3,698✔
1488

1489
                    TARGET(NOT)
1490
                    {
1491
                        const Value* a = popAndResolveAsPtr(context);
3,698✔
1492
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
3,698✔
1493
                        DISPATCH();
3,698✔
1494
                    }
8,309✔
1495

1496
#pragma endregion
1497

1498
#pragma region "Super Instructions"
1499
                    TARGET(LOAD_CONST_LOAD_CONST)
1500
                    {
1501
                        UNPACK_ARGS();
8,309✔
1502
                        push(loadConstAsPtr(primary_arg), context);
8,309✔
1503
                        push(loadConstAsPtr(secondary_arg), context);
8,309✔
1504
                        context.inst_exec_counter++;
8,309✔
1505
                        DISPATCH();
8,309✔
1506
                    }
9,989✔
1507

1508
                    TARGET(LOAD_CONST_STORE)
1509
                    {
1510
                        UNPACK_ARGS();
9,989✔
1511
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
9,989✔
1512
                        DISPATCH();
9,989✔
1513
                    }
896✔
1514

1515
                    TARGET(LOAD_CONST_SET_VAL)
1516
                    {
1517
                        UNPACK_ARGS();
896✔
1518
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
896✔
1519
                        DISPATCH();
895✔
1520
                    }
25✔
1521

1522
                    TARGET(STORE_FROM)
1523
                    {
1524
                        UNPACK_ARGS();
25✔
1525
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
25✔
1526
                        DISPATCH();
24✔
1527
                    }
1,223✔
1528

1529
                    TARGET(STORE_FROM_INDEX)
1530
                    {
1531
                        UNPACK_ARGS();
1,223✔
1532
                        store(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
1,223✔
1533
                        DISPATCH();
1,223✔
1534
                    }
627✔
1535

1536
                    TARGET(SET_VAL_FROM)
1537
                    {
1538
                        UNPACK_ARGS();
627✔
1539
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
627✔
1540
                        DISPATCH();
627✔
1541
                    }
553✔
1542

1543
                    TARGET(SET_VAL_FROM_INDEX)
1544
                    {
1545
                        UNPACK_ARGS();
553✔
1546
                        setVal(secondary_arg, loadSymbolFromIndex(primary_arg, context), context);
553✔
1547
                        DISPATCH();
553✔
1548
                    }
50✔
1549

1550
                    TARGET(INCREMENT)
1551
                    {
1552
                        UNPACK_ARGS();
50✔
1553
                        {
1554
                            Value* var = loadSymbol(primary_arg, context);
50✔
1555

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

1560
                            if (var->valueType() == ValueType::Number)
50✔
1561
                                push(Value(var->number() + secondary_arg), context);
49✔
1562
                            else
1563
                                throw types::TypeCheckingError(
2✔
1564
                                    "+",
1✔
1565
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1566
                                    { *var, Value(secondary_arg) });
1✔
1567
                        }
1568
                        DISPATCH();
49✔
1569
                    }
88,028✔
1570

1571
                    TARGET(INCREMENT_BY_INDEX)
1572
                    {
1573
                        UNPACK_ARGS();
88,028✔
1574
                        {
1575
                            Value* var = loadSymbolFromIndex(primary_arg, context);
88,028✔
1576

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

1581
                            if (var->valueType() == ValueType::Number)
88,028✔
1582
                                push(Value(var->number() + secondary_arg), context);
88,027✔
1583
                            else
1584
                                throw types::TypeCheckingError(
2✔
1585
                                    "+",
1✔
1586
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1587
                                    { *var, Value(secondary_arg) });
1✔
1588
                        }
1589
                        DISPATCH();
88,027✔
1590
                    }
33,144✔
1591

1592
                    TARGET(INCREMENT_STORE)
1593
                    {
1594
                        UNPACK_ARGS();
33,144✔
1595
                        {
1596
                            Value* var = loadSymbol(primary_arg, context);
33,144✔
1597

1598
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1599
                            if (var->valueType() == ValueType::Reference)
33,144✔
1600
                                var = var->reference();
×
1601

1602
                            if (var->valueType() == ValueType::Number)
33,144✔
1603
                            {
1604
                                auto val = Value(var->number() + secondary_arg);
33,143✔
1605
                                setVal(primary_arg, &val, context);
33,143✔
1606
                            }
33,143✔
1607
                            else
1608
                                throw types::TypeCheckingError(
2✔
1609
                                    "+",
1✔
1610
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1611
                                    { *var, Value(secondary_arg) });
1✔
1612
                        }
1613
                        DISPATCH();
33,143✔
1614
                    }
1,854✔
1615

1616
                    TARGET(DECREMENT)
1617
                    {
1618
                        UNPACK_ARGS();
1,854✔
1619
                        {
1620
                            Value* var = loadSymbol(primary_arg, context);
1,854✔
1621

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

1626
                            if (var->valueType() == ValueType::Number)
1,854✔
1627
                                push(Value(var->number() - secondary_arg), context);
1,853✔
1628
                            else
1629
                                throw types::TypeCheckingError(
2✔
1630
                                    "-",
1✔
1631
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1632
                                    { *var, Value(secondary_arg) });
1✔
1633
                        }
1634
                        DISPATCH();
1,853✔
1635
                    }
194,414✔
1636

1637
                    TARGET(DECREMENT_BY_INDEX)
1638
                    {
1639
                        UNPACK_ARGS();
194,414✔
1640
                        {
1641
                            Value* var = loadSymbolFromIndex(primary_arg, context);
194,414✔
1642

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

1647
                            if (var->valueType() == ValueType::Number)
194,414✔
1648
                                push(Value(var->number() - secondary_arg), context);
194,413✔
1649
                            else
1650
                                throw types::TypeCheckingError(
2✔
1651
                                    "-",
1✔
1652
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1653
                                    { *var, Value(secondary_arg) });
1✔
1654
                        }
1655
                        DISPATCH();
194,413✔
1656
                    }
866✔
1657

1658
                    TARGET(DECREMENT_STORE)
1659
                    {
1660
                        UNPACK_ARGS();
866✔
1661
                        {
1662
                            Value* var = loadSymbol(primary_arg, context);
866✔
1663

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

1668
                            if (var->valueType() == ValueType::Number)
866✔
1669
                            {
1670
                                auto val = Value(var->number() - secondary_arg);
865✔
1671
                                setVal(primary_arg, &val, context);
865✔
1672
                            }
865✔
1673
                            else
1674
                                throw types::TypeCheckingError(
2✔
1675
                                    "-",
1✔
1676
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
1677
                                    { *var, Value(secondary_arg) });
1✔
1678
                        }
1679
                        DISPATCH();
865✔
1680
                    }
1✔
1681

1682
                    TARGET(STORE_TAIL)
1683
                    {
1684
                        UNPACK_ARGS();
1✔
1685
                        {
1686
                            Value* list = loadSymbol(primary_arg, context);
1✔
1687
                            Value tail = helper::tail(list);
1✔
1688
                            store(secondary_arg, &tail, context);
1✔
1689
                        }
1✔
1690
                        DISPATCH();
1✔
1691
                    }
8✔
1692

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

1704
                    TARGET(STORE_HEAD)
1705
                    {
1706
                        UNPACK_ARGS();
4✔
1707
                        {
1708
                            Value* list = loadSymbol(primary_arg, context);
4✔
1709
                            Value head = helper::head(list);
4✔
1710
                            store(secondary_arg, &head, context);
4✔
1711
                        }
4✔
1712
                        DISPATCH();
4✔
1713
                    }
38✔
1714

1715
                    TARGET(STORE_HEAD_BY_INDEX)
1716
                    {
1717
                        UNPACK_ARGS();
38✔
1718
                        {
1719
                            Value* list = loadSymbolFromIndex(primary_arg, context);
38✔
1720
                            Value head = helper::head(list);
38✔
1721
                            store(secondary_arg, &head, context);
38✔
1722
                        }
38✔
1723
                        DISPATCH();
38✔
1724
                    }
1,008✔
1725

1726
                    TARGET(STORE_LIST)
1727
                    {
1728
                        UNPACK_ARGS();
1,008✔
1729
                        {
1730
                            Value l = createList(primary_arg, context);
1,008✔
1731
                            store(secondary_arg, &l, context);
1,008✔
1732
                        }
1,008✔
1733
                        DISPATCH();
1,008✔
1734
                    }
3✔
1735

1736
                    TARGET(SET_VAL_TAIL)
1737
                    {
1738
                        UNPACK_ARGS();
3✔
1739
                        {
1740
                            Value* list = loadSymbol(primary_arg, context);
3✔
1741
                            Value tail = helper::tail(list);
3✔
1742
                            setVal(secondary_arg, &tail, context);
3✔
1743
                        }
3✔
1744
                        DISPATCH();
3✔
1745
                    }
1✔
1746

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

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

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

1780
                    TARGET(CALL_BUILTIN)
1781
                    {
1782
                        UNPACK_ARGS();
1,660✔
1783
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1784
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
1,660✔
1785
                        if (!m_running)
1,595✔
1786
                            GOTO_HALT();
×
1787
                        DISPATCH();
1,595✔
1788
                    }
11,692✔
1789

1790
                    TARGET(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS)
1791
                    {
1792
                        UNPACK_ARGS();
11,692✔
1793
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1794
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg, /* remove_return_address= */ false);
11,692✔
1795
                        if (!m_running)
11,691✔
1796
                            GOTO_HALT();
×
1797
                        DISPATCH();
11,691✔
1798
                    }
857✔
1799

1800
                    TARGET(LT_CONST_JUMP_IF_FALSE)
1801
                    {
1802
                        UNPACK_ARGS();
857✔
1803
                        const Value* sym = popAndResolveAsPtr(context);
857✔
1804
                        if (!(*sym < *loadConstAsPtr(primary_arg)))
857✔
1805
                            jump(secondary_arg, context);
122✔
1806
                        DISPATCH();
857✔
1807
                    }
21,988✔
1808

1809
                    TARGET(LT_CONST_JUMP_IF_TRUE)
1810
                    {
1811
                        UNPACK_ARGS();
21,988✔
1812
                        const Value* sym = popAndResolveAsPtr(context);
21,988✔
1813
                        if (*sym < *loadConstAsPtr(primary_arg))
21,988✔
1814
                            jump(secondary_arg, context);
10,960✔
1815
                        DISPATCH();
21,988✔
1816
                    }
6,917✔
1817

1818
                    TARGET(LT_SYM_JUMP_IF_FALSE)
1819
                    {
1820
                        UNPACK_ARGS();
6,917✔
1821
                        const Value* sym = popAndResolveAsPtr(context);
6,917✔
1822
                        if (!(*sym < *loadSymbol(primary_arg, context)))
6,917✔
1823
                            jump(secondary_arg, context);
669✔
1824
                        DISPATCH();
6,917✔
1825
                    }
172,506✔
1826

1827
                    TARGET(GT_CONST_JUMP_IF_TRUE)
1828
                    {
1829
                        UNPACK_ARGS();
172,506✔
1830
                        const Value* sym = popAndResolveAsPtr(context);
172,506✔
1831
                        const Value* cst = loadConstAsPtr(primary_arg);
172,506✔
1832
                        if (*cst < *sym)
172,506✔
1833
                            jump(secondary_arg, context);
86,589✔
1834
                        DISPATCH();
172,506✔
1835
                    }
187✔
1836

1837
                    TARGET(GT_CONST_JUMP_IF_FALSE)
1838
                    {
1839
                        UNPACK_ARGS();
187✔
1840
                        const Value* sym = popAndResolveAsPtr(context);
187✔
1841
                        const Value* cst = loadConstAsPtr(primary_arg);
187✔
1842
                        if (!(*cst < *sym))
187✔
1843
                            jump(secondary_arg, context);
42✔
1844
                        DISPATCH();
187✔
1845
                    }
6✔
1846

1847
                    TARGET(GT_SYM_JUMP_IF_FALSE)
1848
                    {
1849
                        UNPACK_ARGS();
6✔
1850
                        const Value* sym = popAndResolveAsPtr(context);
6✔
1851
                        const Value* rhs = loadSymbol(primary_arg, context);
6✔
1852
                        if (!(*rhs < *sym))
6✔
1853
                            jump(secondary_arg, context);
1✔
1854
                        DISPATCH();
6✔
1855
                    }
1,099✔
1856

1857
                    TARGET(EQ_CONST_JUMP_IF_TRUE)
1858
                    {
1859
                        UNPACK_ARGS();
1,099✔
1860
                        const Value* sym = popAndResolveAsPtr(context);
1,099✔
1861
                        if (*sym == *loadConstAsPtr(primary_arg))
1,099✔
1862
                            jump(secondary_arg, context);
41✔
1863
                        DISPATCH();
1,099✔
1864
                    }
87,351✔
1865

1866
                    TARGET(EQ_SYM_INDEX_JUMP_IF_TRUE)
1867
                    {
1868
                        UNPACK_ARGS();
87,351✔
1869
                        const Value* sym = popAndResolveAsPtr(context);
87,351✔
1870
                        if (*sym == *loadSymbolFromIndex(primary_arg, context))
87,351✔
1871
                            jump(secondary_arg, context);
548✔
1872
                        DISPATCH();
87,351✔
1873
                    }
11✔
1874

1875
                    TARGET(NEQ_CONST_JUMP_IF_TRUE)
1876
                    {
1877
                        UNPACK_ARGS();
11✔
1878
                        const Value* sym = popAndResolveAsPtr(context);
11✔
1879
                        if (*sym != *loadConstAsPtr(primary_arg))
11✔
1880
                            jump(secondary_arg, context);
2✔
1881
                        DISPATCH();
11✔
1882
                    }
30✔
1883

1884
                    TARGET(NEQ_SYM_JUMP_IF_FALSE)
1885
                    {
1886
                        UNPACK_ARGS();
30✔
1887
                        const Value* sym = popAndResolveAsPtr(context);
30✔
1888
                        if (*sym == *loadSymbol(primary_arg, context))
30✔
1889
                            jump(secondary_arg, context);
10✔
1890
                        DISPATCH();
30✔
1891
                    }
27,888✔
1892

1893
                    TARGET(CALL_SYMBOL)
1894
                    {
1895
                        UNPACK_ARGS();
27,888✔
1896
                        call(context, secondary_arg, loadSymbol(primary_arg, context));
27,888✔
1897
                        if (!m_running)
27,886✔
1898
                            GOTO_HALT();
×
1899
                        DISPATCH();
27,886✔
1900
                    }
109,875✔
1901

1902
                    TARGET(CALL_CURRENT_PAGE)
1903
                    {
1904
                        UNPACK_ARGS();
109,875✔
1905
                        context.last_symbol = primary_arg;
109,875✔
1906
                        call(context, secondary_arg, /* function_ptr= */ nullptr, /* or_address= */ static_cast<PageAddr_t>(context.pp));
109,875✔
1907
                        if (!m_running)
109,874✔
1908
                            GOTO_HALT();
×
1909
                        DISPATCH();
109,874✔
1910
                    }
2,965✔
1911

1912
                    TARGET(GET_FIELD_FROM_SYMBOL)
1913
                    {
1914
                        UNPACK_ARGS();
2,965✔
1915
                        push(getField(loadSymbol(primary_arg, context), secondary_arg, context), context);
2,965✔
1916
                        DISPATCH();
2,965✔
1917
                    }
842✔
1918

1919
                    TARGET(GET_FIELD_FROM_SYMBOL_INDEX)
1920
                    {
1921
                        UNPACK_ARGS();
842✔
1922
                        push(getField(loadSymbolFromIndex(primary_arg, context), secondary_arg, context), context);
842✔
1923
                        DISPATCH();
840✔
1924
                    }
16,108✔
1925

1926
                    TARGET(AT_SYM_SYM)
1927
                    {
1928
                        UNPACK_ARGS();
16,108✔
1929
                        push(helper::at(*loadSymbol(primary_arg, context), *loadSymbol(secondary_arg, context), *this), context);
16,108✔
1930
                        DISPATCH();
16,108✔
1931
                    }
49✔
1932

1933
                    TARGET(AT_SYM_INDEX_SYM_INDEX)
1934
                    {
1935
                        UNPACK_ARGS();
49✔
1936
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadSymbolFromIndex(secondary_arg, context), *this), context);
49✔
1937
                        DISPATCH();
49✔
1938
                    }
1,044✔
1939

1940
                    TARGET(AT_SYM_INDEX_CONST)
1941
                    {
1942
                        UNPACK_ARGS();
1,044✔
1943
                        push(helper::at(*loadSymbolFromIndex(primary_arg, context), *loadConstAsPtr(secondary_arg), *this), context);
1,044✔
1944
                        DISPATCH();
1,042✔
1945
                    }
2✔
1946

1947
                    TARGET(CHECK_TYPE_OF)
1948
                    {
1949
                        UNPACK_ARGS();
2✔
1950
                        const Value* sym = loadSymbol(primary_arg, context);
2✔
1951
                        const Value* cst = loadConstAsPtr(secondary_arg);
2✔
1952
                        push(
2✔
1953
                            cst->valueType() == ValueType::String &&
4✔
1954
                                    std::to_string(sym->valueType()) == cst->string()
2✔
1955
                                ? Builtins::trueSym
1956
                                : Builtins::falseSym,
1957
                            context);
2✔
1958
                        DISPATCH();
2✔
1959
                    }
80✔
1960

1961
                    TARGET(CHECK_TYPE_OF_BY_INDEX)
1962
                    {
1963
                        UNPACK_ARGS();
80✔
1964
                        const Value* sym = loadSymbolFromIndex(primary_arg, context);
80✔
1965
                        const Value* cst = loadConstAsPtr(secondary_arg);
80✔
1966
                        push(
80✔
1967
                            cst->valueType() == ValueType::String &&
160✔
1968
                                    std::to_string(sym->valueType()) == cst->string()
80✔
1969
                                ? Builtins::trueSym
1970
                                : Builtins::falseSym,
1971
                            context);
80✔
1972
                        DISPATCH();
80✔
1973
                    }
3,451✔
1974

1975
                    TARGET(APPEND_IN_PLACE_SYM)
1976
                    {
1977
                        UNPACK_ARGS();
3,451✔
1978
                        listAppendInPlace(loadSymbol(primary_arg, context), secondary_arg, context);
3,451✔
1979
                        DISPATCH();
3,451✔
1980
                    }
14✔
1981

1982
                    TARGET(APPEND_IN_PLACE_SYM_INDEX)
1983
                    {
1984
                        UNPACK_ARGS();
14✔
1985
                        listAppendInPlace(loadSymbolFromIndex(primary_arg, context), secondary_arg, context);
14✔
1986
                        DISPATCH();
13✔
1987
                    }
123✔
1988

1989
                    TARGET(STORE_LEN)
1990
                    {
1991
                        UNPACK_ARGS();
123✔
1992
                        {
1993
                            Value* a = loadSymbolFromIndex(primary_arg, context);
123✔
1994
                            Value len;
123✔
1995
                            if (a->valueType() == ValueType::List)
123✔
1996
                                len = Value(static_cast<int>(a->constList().size()));
43✔
1997
                            else if (a->valueType() == ValueType::String)
80✔
1998
                                len = Value(static_cast<int>(a->string().size()));
79✔
1999
                            else
2000
                                throw types::TypeCheckingError(
2✔
2001
                                    "len",
1✔
2002
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2003
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2004
                                    { *a });
1✔
2005
                            store(secondary_arg, &len, context);
122✔
2006
                        }
123✔
2007
                        DISPATCH();
122✔
2008
                    }
9,205✔
2009

2010
                    TARGET(LT_LEN_SYM_JUMP_IF_FALSE)
2011
                    {
2012
                        UNPACK_ARGS();
9,205✔
2013
                        {
2014
                            const Value* sym = loadSymbol(primary_arg, context);
9,205✔
2015
                            Value size;
9,205✔
2016

2017
                            if (sym->valueType() == ValueType::List)
9,205✔
2018
                                size = Value(static_cast<int>(sym->constList().size()));
3,534✔
2019
                            else if (sym->valueType() == ValueType::String)
5,671✔
2020
                                size = Value(static_cast<int>(sym->string().size()));
5,670✔
2021
                            else
2022
                                throw types::TypeCheckingError(
2✔
2023
                                    "len",
1✔
2024
                                    { { types::Contract { { types::Typedef("value", ValueType::List) } },
2✔
2025
                                        types::Contract { { types::Typedef("value", ValueType::String) } } } },
1✔
2026
                                    { *sym });
1✔
2027

2028
                            if (!(*popAndResolveAsPtr(context) < size))
9,204✔
2029
                                jump(secondary_arg, context);
1,200✔
2030
                        }
9,205✔
2031
                        DISPATCH();
9,204✔
2032
                    }
521✔
2033

2034
                    TARGET(MUL_BY)
2035
                    {
2036
                        UNPACK_ARGS();
521✔
2037
                        {
2038
                            Value* var = loadSymbol(primary_arg, context);
521✔
2039
                            const int other = static_cast<int>(secondary_arg) - 2048;
521✔
2040

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

2045
                            if (var->valueType() == ValueType::Number)
521✔
2046
                                push(Value(var->number() * other), context);
520✔
2047
                            else
2048
                                throw types::TypeCheckingError(
2✔
2049
                                    "*",
1✔
2050
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2051
                                    { *var, Value(other) });
1✔
2052
                        }
2053
                        DISPATCH();
520✔
2054
                    }
36✔
2055

2056
                    TARGET(MUL_BY_INDEX)
2057
                    {
2058
                        UNPACK_ARGS();
36✔
2059
                        {
2060
                            Value* var = loadSymbolFromIndex(primary_arg, context);
36✔
2061
                            const int other = static_cast<int>(secondary_arg) - 2048;
36✔
2062

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

2067
                            if (var->valueType() == ValueType::Number)
36✔
2068
                                push(Value(var->number() * other), context);
35✔
2069
                            else
2070
                                throw types::TypeCheckingError(
2✔
2071
                                    "*",
1✔
2072
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2073
                                    { *var, Value(other) });
1✔
2074
                        }
2075
                        DISPATCH();
35✔
2076
                    }
2✔
2077

2078
                    TARGET(MUL_SET_VAL)
2079
                    {
2080
                        UNPACK_ARGS();
2✔
2081
                        {
2082
                            Value* var = loadSymbol(primary_arg, context);
2✔
2083
                            const int other = static_cast<int>(secondary_arg) - 2048;
2✔
2084

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

2089
                            if (var->valueType() == ValueType::Number)
2✔
2090
                            {
2091
                                auto val = Value(var->number() * other);
1✔
2092
                                setVal(primary_arg, &val, context);
1✔
2093
                            }
1✔
2094
                            else
2095
                                throw types::TypeCheckingError(
2✔
2096
                                    "*",
1✔
2097
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2098
                                    { *var, Value(other) });
1✔
2099
                        }
2100
                        DISPATCH();
1✔
2101
                    }
1,109✔
2102

2103
                    TARGET(FUSED_MATH)
2104
                    {
2105
                        const auto op1 = static_cast<Instruction>(padding),
1,109✔
2106
                                   op2 = static_cast<Instruction>((arg & 0xff00) >> 8),
1,109✔
2107
                                   op3 = static_cast<Instruction>(arg & 0x00ff);
1,109✔
2108
                        const std::size_t arg_count = (op1 != NOP) + (op2 != NOP) + (op3 != NOP);
1,109✔
2109

2110
                        const Value* d = popAndResolveAsPtr(context);
1,109✔
2111
                        const Value* c = popAndResolveAsPtr(context);
1,109✔
2112
                        const Value* b = popAndResolveAsPtr(context);
1,109✔
2113

2114
                        if (d->valueType() != ValueType::Number || c->valueType() != ValueType::Number)
1,109✔
2115
                            throw types::TypeCheckingError(
2✔
2116
                                helper::mathInstToStr(op1),
1✔
2117
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2118
                                { *c, *d });
1✔
2119

2120
                        double temp = helper::doMath(c->number(), d->number(), op1);
1,108✔
2121
                        if (b->valueType() != ValueType::Number)
1,108✔
2122
                            throw types::TypeCheckingError(
4✔
2123
                                helper::mathInstToStr(op2),
2✔
2124
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
2✔
2125
                                { *b, Value(temp) });
2✔
2126
                        temp = helper::doMath(b->number(), temp, op2);
1,106✔
2127

2128
                        if (arg_count == 2)
1,105✔
2129
                            push(Value(temp), context);
1,068✔
2130
                        else if (arg_count == 3)
37✔
2131
                        {
2132
                            const Value* a = popAndResolveAsPtr(context);
37✔
2133
                            if (a->valueType() != ValueType::Number)
37✔
2134
                                throw types::TypeCheckingError(
2✔
2135
                                    helper::mathInstToStr(op3),
1✔
2136
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
1✔
2137
                                    { *a, Value(temp) });
1✔
2138

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

2188
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2189
            throw;
2190
#endif
2191
            fmt::println("Unknown error");
×
2192
            backtrace(context);
×
2193
            m_exit_code = 1;
×
2194
        }
186✔
2195

2196
        return m_exit_code;
90✔
2197
    }
284✔
2198

2199
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
2,056✔
2200
    {
2,056✔
2201
        for (auto& local : std::ranges::reverse_view(context.locals))
2,098,202✔
2202
        {
2203
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
2,096,146✔
2204
                return id;
2,050✔
2205
        }
2,096,146✔
2206
        return MaxValue16Bits;
6✔
2207
    }
2,056✔
2208

2209
    void VM::throwArityError(std::size_t passed_arg_count, std::size_t expected_arg_count, ExecutionContext& context)
6✔
2210
    {
6✔
2211
        std::vector<std::string> arg_names;
6✔
2212
        arg_names.reserve(expected_arg_count + 1);
6✔
2213
        if (expected_arg_count > 0)
6✔
2214
            arg_names.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
2215

2216
        std::size_t index = 0;
6✔
2217
        while (m_state.inst(context.pp, index) == STORE ||
12✔
2218
               m_state.inst(context.pp, index) == STORE_REF)
6✔
2219
        {
2220
            const auto id = static_cast<uint16_t>((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3));
×
2221
            arg_names.push_back(m_state.m_symbols[id]);
×
2222
            index += 4;
×
2223
        }
×
2224
        // we only the blank space for formatting and no arg names, probably because of a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS
2225
        if (arg_names.size() == 1 && index == 0)
6✔
2226
        {
2227
            assert(m_state.inst(context.pp, 0) == CALL_BUILTIN_WITHOUT_RETURN_ADDRESS && "expected a CALL_BUILTIN_WITHOUT_RETURN_ADDRESS instruction or STORE instructions");
2✔
2228
            for (std::size_t i = 0; i < expected_arg_count; ++i)
4✔
2229
                arg_names.emplace_back(1, static_cast<char>('a' + i));
2✔
2230
        }
2✔
2231

2232
        std::vector<std::string> arg_vals;
6✔
2233
        arg_vals.reserve(passed_arg_count + 1);
6✔
2234
        if (passed_arg_count > 0)
6✔
2235
            arg_vals.emplace_back("");  // for formatting, so that we have a space between the function and the args
5✔
2236

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

2241
        // set ip/pp to the callee location so that the error can pinpoint the line
2242
        // where the bad call happened
2243
        if (context.sp >= 2 + passed_arg_count)
6✔
2244
        {
2245
            context.ip = context.stack[context.sp - 1 - passed_arg_count].pageAddr();
6✔
2246
            context.pp = context.stack[context.sp - 2 - passed_arg_count].pageAddr();
6✔
2247
            context.sp -= 2;
6✔
2248
            returnFromFuncCall(context);
6✔
2249
        }
6✔
2250

2251
        std::string function_name = (context.last_symbol < m_state.m_symbols.size())
12✔
2252
            ? m_state.m_symbols[context.last_symbol]
6✔
2253
            : Value(static_cast<PageAddr_t>(context.pp)).toString(*this);
×
2254

2255
        throwVMError(
6✔
2256
            ErrorKind::Arity,
2257
            fmt::format(
12✔
2258
                "When calling `({}{})', received {} argument{}, but expected {}: `({}{})'",
6✔
2259
                function_name,
2260
                fmt::join(arg_vals, " "),
6✔
2261
                passed_arg_count,
2262
                passed_arg_count > 1 ? "s" : "",
6✔
2263
                expected_arg_count,
2264
                function_name,
2265
                fmt::join(arg_names, " ")));
6✔
2266
    }
12✔
2267

NEW
2268
    void VM::initDebugger(ExecutionContext& context)
×
NEW
2269
    {
×
NEW
2270
        if (!m_debugger)
×
NEW
2271
            m_debugger = std::make_unique<Debugger>(context, m_state.m_libenv, m_state.m_symbols, m_state.m_constants);
×
2272
        else
NEW
2273
            m_debugger->saveState(context);
×
NEW
2274
    }
×
2275

NEW
2276
    void VM::showBacktraceWithException(const std::exception& e, ExecutionContext& context)
×
2277
    {
×
2278
        std::string text = e.what();
×
2279
        if (!text.empty() && text.back() != '\n')
×
2280
            text += '\n';
×
2281
        fmt::println("{}", text);
×
2282

2283
        // 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
NEW
2284
        const bool error_from_debugger = m_debugger && m_debugger->isRunning();
×
NEW
2285
        if (m_state.m_features & FeatureVMDebugger && !error_from_debugger)
×
NEW
2286
            initDebugger(context);
×
2287

NEW
2288
        const std::size_t saved_ip = context.ip;
×
NEW
2289
        const std::size_t saved_pp = context.pp;
×
NEW
2290
        const uint16_t saved_sp = context.sp;
×
2291

UNCOV
2292
        backtrace(context);
×
2293

NEW
2294
        fmt::println(
×
NEW
2295
            "At IP: {}, PP: {}, SP: {}",
×
2296
            // dividing by 4 because the instructions are actually on 4 bytes
NEW
2297
            fmt::styled(saved_ip / 4, fmt::fg(fmt::color::cyan)),
×
NEW
2298
            fmt::styled(saved_pp, fmt::fg(fmt::color::green)),
×
NEW
2299
            fmt::styled(saved_sp, fmt::fg(fmt::color::yellow)));
×
2300

NEW
2301
        if (m_debugger && !error_from_debugger)
×
2302
        {
NEW
2303
            m_debugger->resetContextToSavedState(context);
×
NEW
2304
            m_debugger->run(*this, context);
×
NEW
2305
        }
×
2306

2307
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2308
        // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
2309
        m_exit_code = 0;
2310
#else
2311
        m_exit_code = 1;
×
2312
#endif
2313
    }
×
2314

2315
    std::optional<InstLoc> VM::findSourceLocation(const std::size_t ip, const std::size_t pp) const
2,207✔
2316
    {
2,207✔
2317
        std::optional<InstLoc> match = std::nullopt;
2,207✔
2318

2319
        for (const auto location : m_state.m_inst_locations)
11,031✔
2320
        {
2321
            if (location.page_pointer == pp && !match)
8,824✔
2322
                match = location;
2,207✔
2323

2324
            // select the best match: we want to find the location that's nearest our instruction pointer,
2325
            // but not equal to it as the IP will always be pointing to the next instruction,
2326
            // not yet executed. Thus, the erroneous instruction is the previous one.
2327
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
8,824✔
2328
                match = location;
2,384✔
2329

2330
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
2331
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
8,824✔
2332
                break;
2,072✔
2333
        }
8,824✔
2334

2335
        return match;
2,207✔
2336
    }
2337

2338
    std::string VM::debugShowSource() const
×
2339
    {
×
2340
        const auto& context = m_execution_contexts.front();
×
2341
        auto maybe_source_loc = findSourceLocation(context->ip, context->pp);
×
2342
        if (maybe_source_loc)
×
2343
        {
2344
            const auto filename = m_state.m_filenames[maybe_source_loc->filename_id];
×
2345
            return fmt::format("{}:{} -- IP: {}, PP: {}", filename, maybe_source_loc->line + 1, maybe_source_loc->inst_pointer, maybe_source_loc->page_pointer);
×
2346
        }
×
2347
        return "No source location found";
×
2348
    }
×
2349

2350
    void VM::backtrace(ExecutionContext& context, std::ostream& os, const bool colorize)
142✔
2351
    {
142✔
2352
        constexpr std::size_t max_consecutive_traces = 7;
142✔
2353

2354
        const auto maybe_location = findSourceLocation(context.ip, context.pp);
142✔
2355
        if (maybe_location)
142✔
2356
        {
2357
            const auto filename = m_state.m_filenames[maybe_location->filename_id];
142✔
2358

2359
            if (Utils::fileExists(filename))
142✔
2360
                Diagnostics::makeContext(
280✔
2361
                    Diagnostics::ErrorLocation {
280✔
2362
                        .filename = filename,
140✔
2363
                        .start = FilePos { .line = maybe_location->line, .column = 0 },
140✔
2364
                        .end = std::nullopt },
140✔
2365
                    os,
140✔
2366
                    /* maybe_context= */ std::nullopt,
140✔
2367
                    /* colorize= */ colorize);
140✔
2368
            fmt::println(os, "");
142✔
2369
        }
142✔
2370

2371
        if (context.fc > 1)
142✔
2372
        {
2373
            // display call stack trace
2374
            const ScopeView old_scope = context.locals.back();
9✔
2375

2376
            std::string previous_trace;
9✔
2377
            std::size_t displayed_traces = 0;
9✔
2378
            std::size_t consecutive_similar_traces = 0;
9✔
2379

2380
            while (context.fc != 0 && context.pp != 0)
2,065✔
2381
            {
2382
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
2,056✔
2383
                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✔
2384

2385
                const uint16_t id = findNearestVariableIdWithValue(
2,056✔
2386
                    Value(static_cast<PageAddr_t>(context.pp)),
2,056✔
2387
                    context);
2,056✔
2388
                const std::string& func_name = (id < m_state.m_symbols.size()) ? m_state.m_symbols[id] : "???";
2,056✔
2389

2390
                if (func_name + loc_as_text != previous_trace)
2,056✔
2391
                {
2392
                    fmt::println(
20✔
2393
                        os,
10✔
2394
                        "[{:4}] In function `{}'{}",
10✔
2395
                        fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
10✔
2396
                        fmt::styled(func_name, colorize ? fmt::fg(fmt::color::green) : fmt::text_style()),
10✔
2397
                        loc_as_text);
2398
                    previous_trace = func_name + loc_as_text;
10✔
2399
                    ++displayed_traces;
10✔
2400
                    consecutive_similar_traces = 0;
10✔
2401
                }
10✔
2402
                else if (consecutive_similar_traces == 0)
2,046✔
2403
                {
2404
                    fmt::println(os, "       ...");
1✔
2405
                    ++consecutive_similar_traces;
1✔
2406
                }
1✔
2407

2408
                const Value* ip;
2,056✔
2409
                do
6,261✔
2410
                {
2411
                    ip = popAndResolveAsPtr(context);
6,261✔
2412
                } while (ip->valueType() != ValueType::InstPtr);
6,261✔
2413

2414
                context.ip = ip->pageAddr();
2,056✔
2415
                context.pp = pop(context)->pageAddr();
2,056✔
2416
                returnFromFuncCall(context);
2,056✔
2417

2418
                if (displayed_traces > max_consecutive_traces)
2,056✔
2419
                {
2420
                    fmt::println(os, "       ...");
×
2421
                    break;
×
2422
                }
2423
            }
2,056✔
2424

2425
            if (context.pp == 0)
9✔
2426
            {
2427
                const auto maybe_call_loc = findSourceLocation(context.ip, context.pp);
9✔
2428
                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✔
2429
                fmt::println(os, "[{:4}] In global scope{}", fmt::styled(context.fc, colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()), loc_as_text);
9✔
2430
            }
9✔
2431

2432
            // display variables values in the current scope
2433
            fmt::println(os, "\nCurrent scope variables values:");
9✔
2434
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
10✔
2435
            {
2436
                fmt::println(
2✔
2437
                    os,
1✔
2438
                    "{} = {}",
1✔
2439
                    fmt::styled(m_state.m_symbols[old_scope.atPos(i).first], colorize ? fmt::fg(fmt::color::cyan) : fmt::text_style()),
1✔
2440
                    old_scope.atPos(i).second.toString(*this));
1✔
2441
            }
1✔
2442
        }
9✔
2443
    }
142✔
2444
}
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