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

ArkScript-lang / Ark / 13344738465

15 Feb 2025 11:50AM UTC coverage: 77.022% (-1.9%) from 78.929%
13344738465

Pull #510

github

web-flow
Merge 70d135e0e into dd2f0e5fc
Pull Request #510: wip

5695 of 7394 relevant lines covered (77.02%)

44192.48 hits per line

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

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

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

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

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

21
namespace Ark
22
{
23
    using namespace internal;
24

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

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

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

49
            types::generateError(
×
50
                "tail",
×
51
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
52
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
53
                { *a });
×
54
        }
336✔
55

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

71
            types::generateError(
×
72
                "head",
×
73
                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
74
                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
75
                { *a });
×
76
        }
306✔
77
    }
78

79
    VM::VM(State& state) noexcept :
171✔
80
        m_state(state), m_exit_code(0), m_running(false)
57✔
81
    {
57✔
82
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
57✔
83
    }
57✔
84

85
    void VM::init() noexcept
58✔
86
    {
58✔
87
        ExecutionContext& context = *m_execution_contexts.back();
58✔
88
        for (const auto& c : m_execution_contexts)
116✔
89
        {
90
            c->ip = 0;
58✔
91
            c->pp = 0;
58✔
92
            c->sp = 0;
58✔
93
        }
58✔
94

95
        context.sp = 0;
58✔
96
        context.fc = 1;
58✔
97

98
        m_shared_lib_objects.clear();
58✔
99
        context.stacked_closure_scopes.clear();
58✔
100
        context.stacked_closure_scopes.emplace_back(nullptr);
58✔
101

102
        context.saved_scope.reset();
58✔
103
        m_exit_code = 0;
58✔
104

105
        context.locals.clear();
58✔
106
        context.locals.emplace_back();
58✔
107

108
        // loading bound stuff
109
        // put them in the global frame if we can, aka the first one
110
        for (const auto& [sym_id, value] : m_state.m_binded)
81✔
111
        {
112
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
18✔
113
            if (it != m_state.m_symbols.end())
18✔
114
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
5✔
115
        }
18✔
116
    }
58✔
117

118
    Value& VM::operator[](const std::string& name) noexcept
28✔
119
    {
28✔
120
        // find id of object
121
        const auto it = std::ranges::find(m_state.m_symbols, name);
28✔
122
        if (it == m_state.m_symbols.end())
28✔
123
        {
124
            m_no_value = Builtins::nil;
1✔
125
            return m_no_value;
1✔
126
        }
127

128
        const auto dist = std::distance(m_state.m_symbols.begin(), it);
27✔
129
        if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
27✔
130
        {
131
            ExecutionContext& context = *m_execution_contexts.front();
27✔
132

133
            const auto id = static_cast<uint16_t>(dist);
27✔
134
            Value* var = findNearestVariable(id, context);
27✔
135
            if (var != nullptr)
27✔
136
                return *var;
27✔
137
        }
27✔
138

139
        m_no_value = Builtins::nil;
×
140
        return m_no_value;
×
141
    }
28✔
142

143
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
×
144
    {
×
145
        namespace fs = std::filesystem;
146

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

149
        std::string path = file;
×
150
        // bytecode loaded from file
151
        if (m_state.m_filename != ARK_NO_NAME_FILE)
×
152
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
×
153

154
        std::shared_ptr<SharedLibrary> lib;
×
155
        // if it exists alongside the .arkc file
156
        if (Utils::fileExists(path))
×
157
            lib = std::make_shared<SharedLibrary>(path);
×
158
        else
159
        {
160
            for (auto const& v : m_state.m_libenv)
×
161
            {
162
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
×
163

164
                // if it's already loaded don't do anything
165
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
×
166
                        return (val->path() == path || val->path() == lib_path);
57✔
167
                    }) != m_shared_lib_objects.end())
×
168
                    return;
×
169

170
                // check in lib_path
171
                if (Utils::fileExists(lib_path))
×
172
                {
173
                    lib = std::make_shared<SharedLibrary>(lib_path);
×
174
                    break;
×
175
                }
176
            }
×
177
        }
178

179
        if (!lib)
×
180
        {
181
            auto lib_path = std::accumulate(
×
182
                std::next(m_state.m_libenv.begin()),
×
183
                m_state.m_libenv.end(),
×
184
                m_state.m_libenv[0].string(),
×
185
                [](const std::string& a, const fs::path& b) -> std::string {
×
186
                    return a + "\n\t- " + b.string();
×
187
                });
×
188
            throwVMError(
×
189
                ErrorKind::Module,
190
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
191
        }
×
192

193
        m_shared_lib_objects.emplace_back(lib);
×
194

195
        // load the mapping from the dynamic library
196
        try
197
        {
198
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
×
199
            // load the mapping data
200
            std::size_t i = 0;
×
201
            while (map[i].name != nullptr)
×
202
            {
203
                // put it in the global frame, aka the first one
204
                auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
205
                if (it != m_state.m_symbols.end())
×
206
                    context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
×
207

208
                ++i;
×
209
            }
×
210
        }
×
211
        catch (const std::system_error& e)
212
        {
213
            throwVMError(
×
214
                ErrorKind::Module,
215
                fmt::format(
×
216
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
217
                    file, e.what()));
×
218
        }
×
219
    }
×
220

221
    void VM::exit(const int code) noexcept
×
222
    {
×
223
        m_exit_code = code;
×
224
        m_running = false;
×
225
    }
×
226

227
    ExecutionContext* VM::createAndGetContext()
1✔
228
    {
1✔
229
        const std::lock_guard lock(m_mutex);
1✔
230

231
        m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
1✔
232
        ExecutionContext* ctx = m_execution_contexts.back().get();
1✔
233
        ctx->stacked_closure_scopes.emplace_back(nullptr);
1✔
234

235
        ctx->locals.reserve(m_execution_contexts.front()->locals.size());
1✔
236
        for (const auto& local : m_execution_contexts.front()->locals)
2✔
237
            ctx->locals.push_back(local);
1✔
238

239
        return ctx;
1✔
240
    }
1✔
241

242
    void VM::deleteContext(ExecutionContext* ec)
×
243
    {
×
244
        const std::lock_guard lock(m_mutex);
×
245

246
        const auto it =
×
247
            std::ranges::remove_if(
×
248
                m_execution_contexts,
×
249
                [ec](const std::unique_ptr<ExecutionContext>& ctx) {
×
250
                    return ctx.get() == ec;
×
251
                })
252
                .begin();
×
253
        m_execution_contexts.erase(it);
×
254
    }
×
255

256
    Future* VM::createFuture(std::vector<Value>& args)
1✔
257
    {
1✔
258
        ExecutionContext* ctx = createAndGetContext();
1✔
259

260
        // doing this after having created the context
261
        // because the context uses the mutex and we don't want a deadlock
262
        const std::lock_guard lock(m_mutex);
1✔
263
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
1✔
264

265
        return m_futures.back().get();
1✔
266
    }
1✔
267

268
    void VM::deleteFuture(Future* f)
×
269
    {
×
270
        const std::lock_guard lock(m_mutex);
×
271

272
        const auto it =
×
273
            std::ranges::remove_if(
×
274
                m_futures,
×
275
                [f](const std::unique_ptr<Future>& future) {
×
276
                    return future.get() == f;
×
277
                })
278
                .begin();
×
279
        m_futures.erase(it);
×
280
    }
×
281

282
    bool VM::forceReloadPlugins() const
×
283
    {
×
284
        // load the mapping from the dynamic library
285
        try
286
        {
287
            for (const auto& shared_lib : m_shared_lib_objects)
×
288
            {
289
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
290
                // load the mapping data
291
                std::size_t i = 0;
×
292
                while (map[i].name != nullptr)
×
293
                {
294
                    // put it in the global frame, aka the first one
295
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
296
                    if (it != m_state.m_symbols.end())
×
297
                        m_execution_contexts[0]->locals[0].push_back(
×
298
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
299
                            Value(map[i].value));
×
300

301
                    ++i;
×
302
                }
×
303
            }
×
304

305
            return true;
×
306
        }
×
307
        catch (const std::system_error&)
308
        {
309
            return false;
×
310
        }
×
311
    }
×
312

313
    int VM::run(const bool fail_with_exception)
58✔
314
    {
58✔
315
        init();
58✔
316
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
58✔
317
        return m_exit_code;
58✔
318
    }
319

320
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
59✔
321
    {
59✔
322
#if ARK_USE_COMPUTED_GOTOS
323
#    define TARGET(op) TARGET_##op:
324
#    define DISPATCH_GOTO()            \
325
        _Pragma("GCC diagnostic push") \
326
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
327
        _Pragma("GCC diagnostic pop")
328
#    define GOTO_HALT() goto dispatch_end
329
#else
330
#    define TARGET(op) case op:
331
#    define DISPATCH_GOTO() goto dispatch_opcode
1✔
332
#    define GOTO_HALT() break
333
#endif
1✔
334

335
#define NEXTOPARG()                                                                      \
1✔
336
    do                                                                                   \
337
    {                                                                                    \
338
        inst = m_state.m_pages[context.pp][context.ip];                                  \
339
        padding = m_state.m_pages[context.pp][context.ip + 1];                           \
340
        arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
341
                                    m_state.m_pages[context.pp][context.ip + 3]);        \
342
        context.ip += 4;                                                                 \
343
    } while (false)
344
#define DISPATCH() \
345
    NEXTOPARG();   \
346
    DISPATCH_GOTO();
347
#define UNPACK_ARGS()                                                                 \
348
    do                                                                                \
349
    {                                                                                 \
350
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
351
        primary_arg = arg & 0x0fff;                                                   \
352
    } while (false)
353

354
#if ARK_USE_COMPUTED_GOTOS
355
#    pragma GCC diagnostic push
356
#    pragma GCC diagnostic ignored "-Wpedantic"
357
            constexpr std::array opcode_targets = {
59✔
358
                &&TARGET_NOP,
359
                &&TARGET_LOAD_SYMBOL,
360
                &&TARGET_LOAD_CONST,
361
                &&TARGET_POP_JUMP_IF_TRUE,
362
                &&TARGET_STORE,
363
                &&TARGET_SET_VAL,
364
                &&TARGET_POP_JUMP_IF_FALSE,
365
                &&TARGET_JUMP,
366
                &&TARGET_RET,
367
                &&TARGET_HALT,
368
                &&TARGET_CALL,
369
                &&TARGET_CAPTURE,
370
                &&TARGET_BUILTIN,
371
                &&TARGET_DEL,
372
                &&TARGET_MAKE_CLOSURE,
373
                &&TARGET_GET_FIELD,
374
                &&TARGET_PLUGIN,
375
                &&TARGET_LIST,
376
                &&TARGET_APPEND,
377
                &&TARGET_CONCAT,
378
                &&TARGET_APPEND_IN_PLACE,
379
                &&TARGET_CONCAT_IN_PLACE,
380
                &&TARGET_POP_LIST,
381
                &&TARGET_POP_LIST_IN_PLACE,
382
                &&TARGET_SET_AT_INDEX,
383
                &&TARGET_SET_AT_2_INDEX,
384
                &&TARGET_POP,
385
                &&TARGET_DUP,
386
                &&TARGET_CREATE_SCOPE,
387
                &&TARGET_POP_SCOPE,
388
                &&TARGET_ADD,
389
                &&TARGET_SUB,
390
                &&TARGET_MUL,
391
                &&TARGET_DIV,
392
                &&TARGET_GT,
393
                &&TARGET_LT,
394
                &&TARGET_LE,
395
                &&TARGET_GE,
396
                &&TARGET_NEQ,
397
                &&TARGET_EQ,
398
                &&TARGET_LEN,
399
                &&TARGET_EMPTY,
400
                &&TARGET_TAIL,
401
                &&TARGET_HEAD,
402
                &&TARGET_ISNIL,
403
                &&TARGET_ASSERT,
404
                &&TARGET_TO_NUM,
405
                &&TARGET_TO_STR,
406
                &&TARGET_AT,
407
                &&TARGET_AT_AT,
408
                &&TARGET_MOD,
409
                &&TARGET_TYPE,
410
                &&TARGET_HASFIELD,
411
                &&TARGET_NOT,
412
                &&TARGET_LOAD_CONST_LOAD_CONST,
413
                &&TARGET_LOAD_CONST_STORE,
414
                &&TARGET_LOAD_CONST_SET_VAL,
415
                &&TARGET_STORE_FROM,
416
                &&TARGET_SET_VAL_FROM,
417
                &&TARGET_INCREMENT,
418
                &&TARGET_DECREMENT,
419
                &&TARGET_STORE_TAIL,
420
                &&TARGET_STORE_HEAD,
421
                &&TARGET_SET_VAL_TAIL,
422
                &&TARGET_SET_VAL_HEAD,
423
                &&TARGET_CALL_BUILTIN
424
            };
425
#    pragma GCC diagnostic pop
426
#endif
427

428
        try
429
        {
430
            uint8_t inst = 0;
59✔
431
            uint8_t padding = 0;
59✔
432
            uint16_t arg = 0;
59✔
433
            uint16_t primary_arg = 0;
59✔
434
            uint16_t secondary_arg = 0;
59✔
435

436
            m_running = true;
59✔
437

438
            DISPATCH();
59✔
439
            {
440
#if !ARK_USE_COMPUTED_GOTOS
441
            dispatch_opcode:
442
                switch (inst)
443
#endif
444
                {
×
445
#pragma region "Instructions"
446
                    TARGET(NOP)
447
                    {
448
                        DISPATCH();
×
449
                    }
510,517✔
450

451
                    TARGET(LOAD_SYMBOL)
452
                    {
453
                        push(loadSymbol(arg, context), context);
510,517✔
454
                        DISPATCH();
510,517✔
455
                    }
284,283✔
456

457
                    TARGET(LOAD_CONST)
458
                    {
459
                        push(loadConstAsPtr(arg), context);
284,283✔
460
                        DISPATCH();
284,283✔
461
                    }
283,513✔
462

463
                    TARGET(POP_JUMP_IF_TRUE)
464
                    {
465
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
382,556✔
466
                            context.ip = arg * 4;  // instructions are 4 bytes
99,043✔
467
                        DISPATCH();
283,513✔
468
                    }
374,430✔
469

470
                    TARGET(STORE)
471
                    {
472
                        store(arg, popAndResolveAsPtr(context), context);
374,430✔
473
                        DISPATCH();
374,430✔
474
                    }
3,262✔
475

476
                    TARGET(SET_VAL)
477
                    {
478
                        setVal(arg, popAndResolveAsPtr(context), context);
3,262✔
479
                        DISPATCH();
3,262✔
480
                    }
3,084✔
481

482
                    TARGET(POP_JUMP_IF_FALSE)
483
                    {
484
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
3,416✔
485
                            context.ip = arg * 4;  // instructions are 4 bytes
332✔
486
                        DISPATCH();
3,084✔
487
                    }
187,322✔
488

489
                    TARGET(JUMP)
490
                    {
491
                        context.ip = arg * 4;  // instructions are 4 bytes
187,322✔
492
                        DISPATCH();
187,322✔
493
                    }
110,366✔
494

495
                    TARGET(RET)
496
                    {
497
                        {
498
                            Value ip_or_val = *popAndResolveAsPtr(context);
110,366✔
499
                            // no return value on the stack
500
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
110,366✔
501
                            {
502
                                context.ip = ip_or_val.pageAddr();
975✔
503
                                // we always push PP then IP, thus the next value
504
                                // MUST be the page pointer
505
                                context.pp = pop(context)->pageAddr();
975✔
506

507
                                returnFromFuncCall(context);
975✔
508
                                push(Builtins::nil, context);
975✔
509
                            }
975✔
510
                            // value on the stack
511
                            else [[likely]]
512
                            {
513
                                Value* ip;
514
                                do
109,391✔
515
                                {
516
                                    ip = popAndResolveAsPtr(context);
109,391✔
517
                                } while (ip->valueType() != ValueType::InstPtr);
109,391✔
518

519
                                context.ip = ip->pageAddr();
109,391✔
520
                                context.pp = pop(context)->pageAddr();
109,391✔
521

522
                                returnFromFuncCall(context);
109,391✔
523
                                push(std::move(ip_or_val), context);
109,391✔
524
                            }
525

526
                            if (context.fc <= untilFrameCount)
110,366✔
527
                                GOTO_HALT();
1✔
528
                        }
110,366✔
529

530
                        DISPATCH();
110,365✔
531
                    }
28✔
532

533
                    TARGET(HALT)
534
                    {
535
                        m_running = false;
28✔
536
                        GOTO_HALT();
28✔
537
                    }
114,468✔
538

539
                    TARGET(CALL)
540
                    {
541
                        // stack pointer + 2 because we push IP and PP
542
                        if (context.sp + 2u >= VMStackSize) [[unlikely]]
114,468✔
543
                            throwVMError(
1✔
544
                                ErrorKind::VM,
545
                                fmt::format(
2✔
546
                                    "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
547
                                    m_state.m_symbols[context.last_symbol]));
1✔
548
                        call(context, arg);
114,467✔
549
                        if (!m_running)
114,463✔
550
                            GOTO_HALT();
×
551
                        DISPATCH();
114,463✔
552
                    }
319✔
553

554
                    TARGET(CAPTURE)
555
                    {
556
                        if (!context.saved_scope)
319✔
557
                            context.saved_scope = Scope();
62✔
558

559
                        Value* ptr = (context.locals.back())[arg];
319✔
560
                        if (!ptr)
319✔
561
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
562
                        else
563
                        {
564
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
319✔
565
                            context.saved_scope.value().push_back(arg, *ptr);
319✔
566
                        }
567

568
                        DISPATCH();
319✔
569
                    }
169✔
570

571
                    TARGET(BUILTIN)
572
                    {
573
                        push(Builtins::builtins[arg].second, context);
169✔
574
                        DISPATCH();
169✔
575
                    }
1✔
576

577
                    TARGET(DEL)
578
                    {
579
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
580
                        {
581
                            if (var->valueType() == ValueType::User)
×
582
                                var->usertypeRef().del();
×
583
                            *var = Value();
×
584
                            DISPATCH();
×
585
                        }
586

587
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
588
                    }
62✔
589

590
                    TARGET(MAKE_CLOSURE)
62✔
591
                    {
592
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
62✔
593
                        context.saved_scope.reset();
62✔
594
                        DISPATCH();
62✔
595
                    }
1,098✔
596

597
                    TARGET(GET_FIELD)
598
                    {
599
                        Value* var = popAndResolveAsPtr(context);
1,098✔
600
                        if (var->valueType() != ValueType::Closure)
1,098✔
601
                        {
602
                            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
603
                                throwVMError(
1✔
604
                                    ErrorKind::Type,
605
                                    fmt::format(
4✔
606
                                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
607
                                        m_state.m_symbols[context.last_symbol],
1✔
608
                                        types_to_str[static_cast<std::size_t>(var->valueType())],
1✔
609
                                        m_state.m_symbols[arg]));
1✔
610
                            else
611
                                throwVMError(ErrorKind::Type,
×
612
                                             fmt::format(
×
613
                                                 "{} is not a Closure, can not get the field `{}' from it",
×
614
                                                 types_to_str[static_cast<std::size_t>(var->valueType())],
×
615
                                                 m_state.m_symbols[arg]));
×
616
                        }
×
617

618
                        if (Value* field = var->refClosure().refScope()[arg]; field != nullptr)
1,097✔
619
                        {
620
                            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
621
                            if (m_state.m_pages[context.pp][context.ip] == CALL)
1,095✔
622
                                push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
484✔
623
                            else
624
                                push(field, context);
611✔
625
                        }
1,095✔
626
                        else
627
                        {
628
                            if (!var->refClosure().hasFieldEndingWith(m_state.m_symbols[arg], *this))
2✔
629
                                throwVMError(
1✔
630
                                    ErrorKind::Scope,
631
                                    fmt::format(
2✔
632
                                        "`{0}' isn't in the closure environment: {1}",
1✔
633
                                        m_state.m_symbols[arg],
1✔
634
                                        var->refClosure().toString(*this)));
1✔
635
                            throwVMError(
1✔
636
                                ErrorKind::Scope,
637
                                fmt::format(
2✔
638
                                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
1✔
639
                                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
640
                                    m_state.m_symbols[arg],
1✔
641
                                    var->refClosure().toString(*this)));
1✔
642
                        }
643
                        DISPATCH();
1,095✔
644
                    }
×
645

646
                    TARGET(PLUGIN)
647
                    {
648
                        loadPlugin(arg, context);
×
649
                        DISPATCH();
×
650
                    }
554✔
651

652
                    TARGET(LIST)
653
                    {
654
                        {
655
                            Value l(ValueType::List);
554✔
656
                            if (arg != 0)
554✔
657
                                l.list().reserve(arg);
332✔
658

659
                            for (uint16_t i = 0; i < arg; ++i)
1,451✔
660
                                l.push_back(*popAndResolveAsPtr(context));
897✔
661
                            push(std::move(l), context);
554✔
662
                        }
554✔
663
                        DISPATCH();
554✔
664
                    }
34✔
665

666
                    TARGET(APPEND)
667
                    {
668
                        {
669
                            Value* list = popAndResolveAsPtr(context);
34✔
670
                            if (list->valueType() != ValueType::List)
34✔
671
                                types::generateError(
×
672
                                    "append",
×
673
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
674
                                    { *list });
×
675

676
                            const auto size = static_cast<uint16_t>(list->constList().size());
34✔
677

678
                            Value obj { *list };
34✔
679
                            obj.list().reserve(size + arg);
34✔
680

681
                            for (uint16_t i = 0; i < arg; ++i)
68✔
682
                                obj.push_back(*popAndResolveAsPtr(context));
34✔
683
                            push(std::move(obj), context);
34✔
684
                        }
34✔
685
                        DISPATCH();
34✔
686
                    }
1✔
687

688
                    TARGET(CONCAT)
689
                    {
690
                        {
691
                            Value* list = popAndResolveAsPtr(context);
1✔
692
                            if (list->valueType() != ValueType::List)
1✔
693
                                types::generateError(
×
694
                                    "concat",
×
695
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
696
                                    { *list });
×
697

698
                            Value obj { *list };
1✔
699

700
                            for (uint16_t i = 0; i < arg; ++i)
2✔
701
                            {
702
                                Value* next = popAndResolveAsPtr(context);
1✔
703

704
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
1✔
705
                                    types::generateError(
×
706
                                        "concat",
×
707
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
708
                                        { *list, *next });
×
709

710
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
1✔
711
                            }
1✔
712
                            push(std::move(obj), context);
1✔
713
                        }
1✔
714
                        DISPATCH();
1✔
715
                    }
425✔
716

717
                    TARGET(APPEND_IN_PLACE)
718
                    {
719
                        Value* list = popAndResolveAsPtr(context);
425✔
720

721
                        if (list->valueType() != ValueType::List)
425✔
722
                            types::generateError(
×
723
                                "append!",
×
724
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
725
                                { *list });
×
726

727
                        for (uint16_t i = 0; i < arg; ++i)
850✔
728
                            list->push_back(*popAndResolveAsPtr(context));
425✔
729
                        DISPATCH();
425✔
730
                    }
40✔
731

732
                    TARGET(CONCAT_IN_PLACE)
733
                    {
734
                        Value* list = popAndResolveAsPtr(context);
40✔
735

736
                        if (list->valueType() != ValueType::List)
40✔
737
                            types::generateError(
×
738
                                "concat",
×
739
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
740
                                { *list });
×
741

742
                        for (uint16_t i = 0; i < arg; ++i)
110✔
743
                        {
744
                            Value* next = popAndResolveAsPtr(context);
70✔
745

746
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
70✔
747
                                types::generateError(
×
748
                                    "concat!",
×
749
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
750
                                    { *list, *next });
×
751

752
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
70✔
753
                        }
70✔
754
                        DISPATCH();
40✔
755
                    }
4✔
756

757
                    TARGET(POP_LIST)
758
                    {
759
                        {
760
                            Value list = *popAndResolveAsPtr(context);
4✔
761
                            Value number = *popAndResolveAsPtr(context);
4✔
762

763
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
4✔
764
                                types::generateError(
×
765
                                    "pop",
×
766
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
767
                                    { list, number });
×
768

769
                            long idx = static_cast<long>(number.number());
4✔
770
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
771
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
772
                                throwVMError(
1✔
773
                                    ErrorKind::Index,
774
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
775

776
                            list.list().erase(list.list().begin() + idx);
3✔
777
                            push(list, context);
3✔
778
                        }
4✔
779
                        DISPATCH();
3✔
780
                    }
30✔
781

782
                    TARGET(POP_LIST_IN_PLACE)
783
                    {
784
                        {
785
                            Value* list = popAndResolveAsPtr(context);
30✔
786
                            Value number = *popAndResolveAsPtr(context);
30✔
787

788
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
30✔
789
                                types::generateError(
×
790
                                    "pop!",
×
791
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
792
                                    { *list, number });
×
793

794
                            long idx = static_cast<long>(number.number());
30✔
795
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
30✔
796
                            if (std::cmp_greater_equal(idx, list->list().size()))
30✔
797
                                throwVMError(
1✔
798
                                    ErrorKind::Index,
799
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
1✔
800

801
                            list->list().erase(list->list().begin() + idx);
29✔
802
                        }
30✔
803
                        DISPATCH();
29✔
804
                    }
486✔
805

806
                    TARGET(SET_AT_INDEX)
807
                    {
808
                        {
809
                            Value* list = popAndResolveAsPtr(context);
486✔
810
                            Value number = *popAndResolveAsPtr(context);
486✔
811
                            Value new_value = *popAndResolveAsPtr(context);
486✔
812

813
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
486✔
814
                                types::generateError(
×
815
                                    "@=",
×
816
                                    { { types::Contract {
×
817
                                          { types::Typedef("list", ValueType::List),
×
818
                                            types::Typedef("index", ValueType::Number),
×
819
                                            types::Typedef("new_value", ValueType::Any) } } },
×
820
                                      { types::Contract {
×
821
                                          { types::Typedef("string", ValueType::String),
×
822
                                            types::Typedef("index", ValueType::Number),
×
823
                                            types::Typedef("char", ValueType::String) } } } },
×
824
                                    { *list, number });
×
825

826
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
486✔
827
                            long idx = static_cast<long>(number.number());
486✔
828
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
486✔
829
                            if (std::cmp_greater_equal(idx, size))
486✔
830
                                throwVMError(
1✔
831
                                    ErrorKind::Index,
832
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
1✔
833

834
                            if (list->valueType() == ValueType::List)
485✔
835
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
836
                            else
837
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
×
838
                        }
486✔
839
                        DISPATCH();
485✔
840
                    }
6✔
841

842
                    TARGET(SET_AT_2_INDEX)
843
                    {
844
                        {
845
                            Value* list = popAndResolveAsPtr(context);
6✔
846
                            Value x = *popAndResolveAsPtr(context);
6✔
847
                            Value y = *popAndResolveAsPtr(context);
6✔
848
                            Value new_value = *popAndResolveAsPtr(context);
6✔
849

850
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
6✔
851
                                types::generateError(
×
852
                                    "@@=",
×
853
                                    { { types::Contract {
×
854
                                        { types::Typedef("list", ValueType::List),
×
855
                                          types::Typedef("x", ValueType::Number),
×
856
                                          types::Typedef("y", ValueType::Number),
×
857
                                          types::Typedef("new_value", ValueType::Any) } } } },
×
858
                                    { *list, x, y });
×
859

860
                            long idx_y = static_cast<long>(x.number());
6✔
861
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
6✔
862
                            if (std::cmp_greater_equal(idx_y, list->list().size()))
6✔
863
                                throwVMError(
1✔
864
                                    ErrorKind::Index,
865
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
1✔
866

867
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
5✔
868
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
5✔
869
                                types::generateError(
×
870
                                    "@@=",
×
871
                                    { { types::Contract {
×
872
                                          { types::Typedef("list", ValueType::List),
×
873
                                            types::Typedef("x", ValueType::Number),
×
874
                                            types::Typedef("y", ValueType::Number),
×
875
                                            types::Typedef("new_value", ValueType::Any) } } },
×
876
                                      { types::Contract {
×
877
                                          { types::Typedef("string", ValueType::String),
×
878
                                            types::Typedef("x", ValueType::Number),
×
879
                                            types::Typedef("y", ValueType::Number),
×
880
                                            types::Typedef("char", ValueType::String) } } } },
×
881
                                    { *list, x, y });
×
882

883
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
5✔
884
                            const std::size_t size =
5✔
885
                                is_list
10✔
886
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
5✔
887
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
×
888

889
                            long idx_x = static_cast<long>(y.number());
5✔
890
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
5✔
891
                            if (std::cmp_greater_equal(idx_x, size))
5✔
892
                                throwVMError(
1✔
893
                                    ErrorKind::Index,
894
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
895

896
                            if (is_list)
4✔
897
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
898
                            else
899
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
×
900
                        }
6✔
901
                        DISPATCH();
4✔
902
                    }
1,668✔
903

904
                    TARGET(POP)
905
                    {
906
                        pop(context);
1,668✔
907
                        DISPATCH();
1,668✔
908
                    }
659✔
909

910
                    TARGET(DUP)
911
                    {
912
                        context.stack[context.sp] = context.stack[context.sp - 1];
659✔
913
                        ++context.sp;
659✔
914
                        DISPATCH();
659✔
915
                    }
292✔
916

917
                    TARGET(CREATE_SCOPE)
918
                    {
919
                        context.locals.emplace_back();
292✔
920
                        DISPATCH();
292✔
921
                    }
292✔
922

923
                    TARGET(POP_SCOPE)
924
                    {
925
                        context.locals.pop_back();
292✔
926
                        DISPATCH();
292✔
927
                    }
12,439✔
928

929
#pragma endregion
930

931
#pragma region "Operators"
932

933
                    TARGET(ADD)
934
                    {
935
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
12,439✔
936

937
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
12,439✔
938
                            push(Value(a->number() + b->number()), context);
12,135✔
939
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
304✔
940
                            push(Value(a->string() + b->string()), context);
304✔
941
                        else
942
                            types::generateError(
×
943
                                "+",
×
944
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
×
945
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
×
946
                                { *a, *b });
×
947
                        DISPATCH();
12,439✔
948
                    }
78✔
949

950
                    TARGET(SUB)
951
                    {
952
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
78✔
953

954
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
78✔
955
                            types::generateError(
×
956
                                "-",
×
957
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
958
                                { *a, *b });
×
959
                        push(Value(a->number() - b->number()), context);
78✔
960
                        DISPATCH();
78✔
961
                    }
98✔
962

963
                    TARGET(MUL)
964
                    {
965
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
98✔
966

967
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
98✔
968
                            types::generateError(
×
969
                                "*",
×
970
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
971
                                { *a, *b });
×
972
                        push(Value(a->number() * b->number()), context);
98✔
973
                        DISPATCH();
98✔
974
                    }
27✔
975

976
                    TARGET(DIV)
977
                    {
978
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
27✔
979

980
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
27✔
981
                            types::generateError(
×
982
                                "/",
×
983
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
984
                                { *a, *b });
×
985
                        auto d = b->number();
27✔
986
                        if (d == 0)
27✔
987
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
988

989
                        push(Value(a->number() / d), context);
26✔
990
                        DISPATCH();
26✔
991
                    }
172,305✔
992

993
                    TARGET(GT)
994
                    {
995
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
172,305✔
996
                        push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
172,305✔
997
                        DISPATCH();
172,305✔
998
                    }
24,144✔
999

1000
                    TARGET(LT)
1001
                    {
1002
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
24,144✔
1003
                        push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
24,144✔
1004
                        DISPATCH();
24,144✔
1005
                    }
634✔
1006

1007
                    TARGET(LE)
1008
                    {
1009
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
634✔
1010
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
634✔
1011
                        DISPATCH();
634✔
1012
                    }
269✔
1013

1014
                    TARGET(GE)
1015
                    {
1016
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
269✔
1017
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
269✔
1018
                        DISPATCH();
269✔
1019
                    }
587✔
1020

1021
                    TARGET(NEQ)
1022
                    {
1023
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
587✔
1024
                        push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
587✔
1025
                        DISPATCH();
587✔
1026
                    }
87,637✔
1027

1028
                    TARGET(EQ)
1029
                    {
1030
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
87,637✔
1031
                        push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
87,637✔
1032
                        DISPATCH();
87,637✔
1033
                    }
907✔
1034

1035
                    TARGET(LEN)
1036
                    {
1037
                        Value* a = popAndResolveAsPtr(context);
907✔
1038

1039
                        if (a->valueType() == ValueType::List)
907✔
1040
                            push(Value(static_cast<int>(a->constList().size())), context);
718✔
1041
                        else if (a->valueType() == ValueType::String)
189✔
1042
                            push(Value(static_cast<int>(a->string().size())), context);
189✔
1043
                        else
1044
                            types::generateError(
×
1045
                                "len",
×
1046
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
1047
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1048
                                { *a });
×
1049
                        DISPATCH();
907✔
1050
                    }
389✔
1051

1052
                    TARGET(EMPTY)
1053
                    {
1054
                        Value* a = popAndResolveAsPtr(context);
389✔
1055

1056
                        if (a->valueType() == ValueType::List)
389✔
1057
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
71✔
1058
                        else if (a->valueType() == ValueType::String)
318✔
1059
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
318✔
1060
                        else
1061
                            types::generateError(
×
1062
                                "empty?",
×
1063
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
1064
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1065
                                { *a });
×
1066
                        DISPATCH();
389✔
1067
                    }
334✔
1068

1069
                    TARGET(TAIL)
1070
                    {
1071
                        Value* a = popAndResolveAsPtr(context);
334✔
1072
                        push(helper::tail(a), context);
334✔
1073
                        DISPATCH();
334✔
1074
                    }
274✔
1075

1076
                    TARGET(HEAD)
1077
                    {
1078
                        Value* a = popAndResolveAsPtr(context);
274✔
1079
                        push(helper::head(a), context);
274✔
1080
                        DISPATCH();
274✔
1081
                    }
167✔
1082

1083
                    TARGET(ISNIL)
1084
                    {
1085
                        Value* a = popAndResolveAsPtr(context);
167✔
1086
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
167✔
1087
                        DISPATCH();
167✔
1088
                    }
30✔
1089

1090
                    TARGET(ASSERT)
1091
                    {
1092
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
30✔
1093

1094
                        if (b->valueType() != ValueType::String)
30✔
1095
                            types::generateError(
×
1096
                                "assert",
×
1097
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
×
1098
                                { *a, *b });
×
1099

1100
                        if (*a == Builtins::falseSym)
30✔
1101
                            throw AssertionFailed(b->stringRef());
×
1102
                        DISPATCH();
30✔
1103
                    }
12✔
1104

1105
                    TARGET(TO_NUM)
1106
                    {
1107
                        Value* a = popAndResolveAsPtr(context);
12✔
1108

1109
                        if (a->valueType() != ValueType::String)
12✔
1110
                            types::generateError(
×
1111
                                "toNumber",
×
1112
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1113
                                { *a });
×
1114

1115
                        double val;
1116
                        if (Utils::isDouble(a->string(), &val))
12✔
1117
                            push(Value(val), context);
9✔
1118
                        else
1119
                            push(Builtins::nil, context);
3✔
1120
                        DISPATCH();
12✔
1121
                    }
14✔
1122

1123
                    TARGET(TO_STR)
1124
                    {
1125
                        Value* a = popAndResolveAsPtr(context);
14✔
1126
                        push(Value(a->toString(*this)), context);
14✔
1127
                        DISPATCH();
14✔
1128
                    }
1,818✔
1129

1130
                    TARGET(AT)
1131
                    {
1132
                        {
1133
                            Value* b = popAndResolveAsPtr(context);
1,818✔
1134
                            Value a = *popAndResolveAsPtr(context);  // be careful, it's not a pointer
1,818✔
1135

1136
                            if (b->valueType() != ValueType::Number)
1,818✔
1137
                                types::generateError(
×
1138
                                    "@",
×
1139
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
1140
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
1141
                                    { a, *b });
×
1142

1143
                            long idx = static_cast<long>(b->number());
1,818✔
1144

1145
                            if (a.valueType() == ValueType::List)
1,818✔
1146
                            {
1147
                                if (std::cmp_less(std::abs(idx), a.list().size()))
1,532✔
1148
                                    push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
1,531✔
1149
                                else
1150
                                    throwVMError(
1✔
1151
                                        ErrorKind::Index,
1152
                                        fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1✔
1153
                            }
1,531✔
1154
                            else if (a.valueType() == ValueType::String)
286✔
1155
                            {
1156
                                if (std::cmp_less(std::abs(idx), a.string().size()))
286✔
1157
                                    push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
285✔
1158
                                else
1159
                                    throwVMError(
1✔
1160
                                        ErrorKind::Index,
1161
                                        fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1✔
1162
                            }
285✔
1163
                            else
1164
                                types::generateError(
×
1165
                                    "@",
×
1166
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
1167
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
1168
                                    { a, *b });
×
1169
                        }
1,818✔
1170
                        DISPATCH();
1,816✔
1171
                    }
8✔
1172

1173
                    TARGET(AT_AT)
1174
                    {
1175
                        {
1176
                            Value* x = popAndResolveAsPtr(context);
8✔
1177
                            Value* y = popAndResolveAsPtr(context);
8✔
1178
                            Value list = *popAndResolveAsPtr(context);  // be careful, it's not a pointer
8✔
1179

1180
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
8✔
1181
                                list.valueType() != ValueType::List)
8✔
1182
                                types::generateError(
×
1183
                                    "@@",
×
1184
                                    { { types::Contract {
×
1185
                                        { types::Typedef("src", ValueType::List),
×
1186
                                          types::Typedef("y", ValueType::Number),
×
1187
                                          types::Typedef("x", ValueType::Number) } } } },
×
1188
                                    { list, *y, *x });
×
1189

1190
                            long idx_y = static_cast<long>(y->number());
8✔
1191
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
8✔
1192
                            if (std::cmp_greater_equal(idx_y, list.list().size()))
8✔
1193
                                throwVMError(
1✔
1194
                                    ErrorKind::Index,
1195
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1✔
1196

1197
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
7✔
1198
                            const std::size_t size =
7✔
1199
                                is_list
14✔
1200
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
7✔
1201
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
×
1202

1203
                            long idx_x = static_cast<long>(x->number());
7✔
1204
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
7✔
1205
                            if (std::cmp_greater_equal(idx_x, size))
7✔
1206
                                throwVMError(
1✔
1207
                                    ErrorKind::Index,
1208
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
1209

1210
                            if (is_list)
6✔
1211
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
6✔
1212
                            else
1213
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
×
1214
                        }
8✔
1215
                        DISPATCH();
6✔
1216
                    }
508✔
1217

1218
                    TARGET(MOD)
1219
                    {
1220
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
508✔
1221
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
508✔
1222
                            types::generateError(
×
1223
                                "mod",
×
1224
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1225
                                { *a, *b });
×
1226
                        push(Value(std::fmod(a->number(), b->number())), context);
508✔
1227
                        DISPATCH();
508✔
1228
                    }
34✔
1229

1230
                    TARGET(TYPE)
1231
                    {
1232
                        Value* a = popAndResolveAsPtr(context);
34✔
1233
                        if (a == &m_undefined_value) [[unlikely]]
34✔
1234
                            types::generateError(
×
1235
                                "type",
×
1236
                                { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
×
1237
                                {});
×
1238

1239
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
34✔
1240
                        DISPATCH();
34✔
1241
                    }
2✔
1242

1243
                    TARGET(HASFIELD)
1244
                    {
1245
                        {
1246
                            Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
2✔
1247
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
2✔
1248
                                types::generateError(
×
1249
                                    "hasField",
×
1250
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
×
1251
                                    { *closure, *field });
×
1252

1253
                            auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef());
2✔
1254
                            if (it == m_state.m_symbols.end())
2✔
1255
                            {
1256
                                push(Builtins::falseSym, context);
1✔
1257
                                DISPATCH();
1✔
1258
                            }
1259

1260
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1261
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1262
                        }
1263
                        DISPATCH();
1✔
1264
                    }
994✔
1265

1266
                    TARGET(NOT)
1267
                    {
1268
                        Value* a = popAndResolveAsPtr(context);
994✔
1269
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
994✔
1270
                        DISPATCH();
994✔
1271
                    }
560✔
1272

1273
#pragma endregion
1274

1275
#pragma region "Super Instructions"
1276
                    TARGET(LOAD_CONST_LOAD_CONST)
1277
                    {
1278
                        UNPACK_ARGS();
560✔
1279
                        push(loadConstAsPtr(primary_arg), context);
560✔
1280
                        push(loadConstAsPtr(secondary_arg), context);
560✔
1281
                        DISPATCH();
560✔
1282
                    }
859✔
1283

1284
                    TARGET(LOAD_CONST_STORE)
1285
                    {
1286
                        UNPACK_ARGS();
859✔
1287
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
859✔
1288
                        DISPATCH();
859✔
1289
                    }
57✔
1290

1291
                    TARGET(LOAD_CONST_SET_VAL)
1292
                    {
1293
                        UNPACK_ARGS();
57✔
1294
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
57✔
1295
                        DISPATCH();
56✔
1296
                    }
124✔
1297

1298
                    TARGET(STORE_FROM)
1299
                    {
1300
                        UNPACK_ARGS();
124✔
1301
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
124✔
1302
                        DISPATCH();
123✔
1303
                    }
109✔
1304

1305
                    TARGET(SET_VAL_FROM)
1306
                    {
1307
                        UNPACK_ARGS();
109✔
1308
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
109✔
1309
                        DISPATCH();
109✔
1310
                    }
91,989✔
1311

1312
                    TARGET(INCREMENT)
1313
                    {
1314
                        UNPACK_ARGS();
91,989✔
1315
                        {
1316
                            Value* var = loadSymbol(primary_arg, context);
91,989✔
1317

1318
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1319
                            if (var->valueType() == ValueType::Reference)
91,989✔
1320
                                var = var->reference();
×
1321

1322
                            if (var->valueType() == ValueType::Number)
91,989✔
1323
                                push(Value(var->number() + secondary_arg), context);
91,989✔
1324
                            else
1325
                                types::generateError(
×
1326
                                    "+",
×
1327
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1328
                                    { *var, Value(secondary_arg) });
×
1329
                        }
1330
                        DISPATCH();
91,989✔
1331
                    }
194,360✔
1332

1333
                    TARGET(DECREMENT)
1334
                    {
1335
                        UNPACK_ARGS();
194,360✔
1336
                        {
1337
                            Value* var = loadSymbol(primary_arg, context);
194,360✔
1338

1339
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1340
                            if (var->valueType() == ValueType::Reference)
194,360✔
1341
                                var = var->reference();
×
1342

1343
                            if (var->valueType() == ValueType::Number)
194,360✔
1344
                                push(Value(var->number() - secondary_arg), context);
194,360✔
1345
                            else
1346
                                types::generateError(
×
1347
                                    "-",
×
1348
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1349
                                    { *var, Value(secondary_arg) });
×
1350
                        }
1351
                        DISPATCH();
194,360✔
1352
                    }
1✔
1353

1354
                    TARGET(STORE_TAIL)
1355
                    {
1356
                        UNPACK_ARGS();
1✔
1357
                        {
1358
                            Value* list = loadSymbol(primary_arg, context);
1✔
1359
                            Value tail = helper::tail(list);
1✔
1360
                            store(secondary_arg, &tail, context);
1✔
1361
                        }
1✔
1362
                        DISPATCH();
1✔
1363
                    }
31✔
1364

1365
                    TARGET(STORE_HEAD)
1366
                    {
1367
                        UNPACK_ARGS();
31✔
1368
                        {
1369
                            Value* list = loadSymbol(primary_arg, context);
31✔
1370
                            Value head = helper::head(list);
31✔
1371
                            store(secondary_arg, &head, context);
31✔
1372
                        }
31✔
1373
                        DISPATCH();
31✔
1374
                    }
1✔
1375

1376
                    TARGET(SET_VAL_TAIL)
1377
                    {
1378
                        UNPACK_ARGS();
1✔
1379
                        {
1380
                            Value* list = loadSymbol(primary_arg, context);
1✔
1381
                            Value tail = helper::tail(list);
1✔
1382
                            setVal(secondary_arg, &tail, context);
1✔
1383
                        }
1✔
1384
                        DISPATCH();
1✔
1385
                    }
1✔
1386

1387
                    TARGET(SET_VAL_HEAD)
1388
                    {
1389
                        UNPACK_ARGS();
1✔
1390
                        {
1391
                            Value* list = loadSymbol(primary_arg, context);
1✔
1392
                            Value head = helper::head(list);
1✔
1393
                            setVal(secondary_arg, &head, context);
1✔
1394
                        }
1✔
1395
                        DISPATCH();
1✔
1396
                    }
498✔
1397

1398
                    TARGET(CALL_BUILTIN)
1399
                    {
1400
                        UNPACK_ARGS();
498✔
1401
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1402
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
498✔
1403
                        if (!m_running)
489✔
1404
                            GOTO_HALT();
×
1405
                        DISPATCH();
489✔
1406
                    }
1407
#pragma endregion
1408
                }
29✔
1409
#if ARK_USE_COMPUTED_GOTOS
1410
            dispatch_end:
1411
                do
29✔
1412
                {
1413
                } while (false);
29✔
1414
#endif
1415
            }
1416
        }
59✔
1417
        catch (const std::exception& e)
1418
        {
1419
            if (fail_with_exception)
30✔
1420
                throw;
30✔
1421

1422
            fmt::println("{}", e.what());
×
1423
            backtrace(context);
×
1424
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1425
            // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1426
            m_exit_code = 0;
1427
#else
1428
            m_exit_code = 1;
×
1429
#endif
1430
        }
59✔
1431
        catch (...)
1432
        {
1433
            if (fail_with_exception)
×
1434
                throw;
×
1435

1436
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1437
            throw;
1438
#endif
1439
            fmt::println("Unknown error");
×
1440
            backtrace(context);
×
1441
            m_exit_code = 1;
×
1442
        }
60✔
1443

1444
        return m_exit_code;
29✔
1445
    }
60✔
1446

1447
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
×
1448
    {
×
1449
        for (auto& local : std::ranges::reverse_view(context.locals))
×
1450
        {
1451
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
×
1452
                return id;
×
1453
        }
×
1454
        return std::numeric_limits<uint16_t>::max();
×
1455
    }
×
1456

1457
    void VM::throwVMError(ErrorKind kind, const std::string& message)
22✔
1458
    {
22✔
1459
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
22✔
1460
    }
22✔
1461

1462
    void VM::backtrace(ExecutionContext& context) noexcept
×
1463
    {
×
1464
        const std::size_t saved_ip = context.ip;
×
1465
        const std::size_t saved_pp = context.pp;
×
1466
        const uint16_t saved_sp = context.sp;
×
1467

1468
        if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
×
1469
        {
1470
            // display call stack trace
1471
            const Scope old_scope = context.locals.back();
×
1472

1473
            while (context.fc != 0)
×
1474
            {
1475
                fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
×
1476
                if (context.pp != 0)
×
1477
                {
1478
                    const uint16_t id = findNearestVariableIdWithValue(
×
1479
                        Value(static_cast<PageAddr_t>(context.pp)),
×
1480
                        context);
×
1481

1482
                    if (id < m_state.m_symbols.size())
×
1483
                        fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)));
×
1484
                    else  // should never happen
1485
                        fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold)));
×
1486

1487
                    Value* ip;
×
1488
                    do
×
1489
                    {
1490
                        ip = popAndResolveAsPtr(context);
×
1491
                    } while (ip->valueType() != ValueType::InstPtr);
×
1492

1493
                    context.ip = ip->pageAddr();
×
1494
                    context.pp = pop(context)->pageAddr();
×
1495
                    returnFromFuncCall(context);
×
1496
                }
×
1497
                else
1498
                {
1499
                    fmt::println("In global scope");
×
1500
                    break;
×
1501
                }
1502

1503
                if (original_frame_count - context.fc > 7)
×
1504
                {
1505
                    fmt::println("...");
×
1506
                    break;
×
1507
                }
1508
            }
1509

1510
            // display variables values in the current scope
1511
            fmt::println("\nCurrent scope variables values:");
×
1512
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
×
1513
            {
1514
                fmt::println(
×
1515
                    "{} = {}",
×
1516
                    fmt::styled(m_state.m_symbols[old_scope.m_data[i].first], fmt::fg(fmt::color::cyan)),
×
1517
                    old_scope.m_data[i].second.toString(*this));
×
1518
            }
×
1519

1520
            while (context.fc != 1)
×
1521
            {
1522
                Value* tmp = pop(context);
×
1523
                if (tmp->valueType() == ValueType::InstPtr)
×
1524
                    --context.fc;
×
1525
                *tmp = m_no_value;
×
1526
            }
×
1527
            // pop the PP as well
1528
            pop(context);
×
1529
        }
×
1530

1531
        std::cerr << "At IP: " << (saved_ip / 4)  // dividing by 4 because the instructions are actually on 4 bytes
×
1532
                  << ", PP: " << saved_pp
×
1533
                  << ", SP: " << saved_sp
×
1534
                  << "\n";
×
1535
    }
×
1536
}
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