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

ArkScript-lang / Ark / 12164232004

04 Dec 2024 04:37PM UTC coverage: 77.815% (+0.6%) from 77.19%
12164232004

push

github

SuperFola
feat(name resolution): allow fqn if the scope is only exporting symbols

1 of 3 new or added lines in 2 files covered. (33.33%)

256 existing lines in 11 files now uncovered.

5412 of 6955 relevant lines covered (77.81%)

9300.67 hits per line

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

58.86
/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)
3✔
28
        {
3✔
29
            if (a->valueType() == ValueType::List)
3✔
30
            {
31
                if (a->constList().size() < 2)
×
32
                    return Value(ValueType::List);
×
33

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

44
                Value b { *a };
1✔
45
                b.stringRef().erase(b.stringRef().begin());
1✔
46
                return b;
1✔
47
            }
1✔
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
        }
3✔
55

56
        inline Value head(Value* a)
3✔
57
        {
3✔
58
            if (a->valueType() == ValueType::List)
3✔
59
            {
60
                if (a->constList().empty())
×
61
                    return Builtins::nil;
×
62
                return a->constList()[0];
×
63
            }
64
            if (a->valueType() == ValueType::String)
3✔
65
            {
66
                if (a->string().empty())
3✔
67
                    return Value(ValueType::String);
1✔
68
                return Value(std::string(1, a->stringRef()[0]));
2✔
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
        }
3✔
77
    }
78

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

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

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

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

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

105
        context.locals.clear();
32✔
106
        context.locals.emplace_back();
32✔
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)
40✔
111
        {
112
            auto it = std::ranges::find(m_state.m_symbols, sym_id);
4✔
113
            if (it != m_state.m_symbols.end())
4✔
114
                context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), value);
4✔
115
        }
4✔
116
    }
32✔
117

118
    Value& VM::operator[](const std::string& name) noexcept
20✔
119
    {
20✔
120
        // find id of object
121
        const auto it = std::ranges::find(m_state.m_symbols, name);
20✔
122
        if (it == m_state.m_symbols.end())
20✔
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);
19✔
129
        if (std::cmp_less(dist, std::numeric_limits<uint16_t>::max()))
19✔
130
        {
131
            ExecutionContext& context = *m_execution_contexts.front();
19✔
132

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

139
        m_no_value = Builtins::nil;
×
140
        return m_no_value;
×
141
    }
20✔
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);
31✔
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()
6✔
228
    {
6✔
229
        const std::lock_guard lock(m_mutex);
6✔
230

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

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

239
        return ctx;
6✔
240
    }
6✔
241

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

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

256
    Future* VM::createFuture(std::vector<Value>& args)
6✔
257
    {
6✔
258
        ExecutionContext* ctx = createAndGetContext();
6✔
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);
6✔
263
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
6✔
264

265
        return m_futures.back().get();
6✔
266
    }
6✔
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)
32✔
314
    {
32✔
315
        init();
32✔
316
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
32✔
317
        return m_exit_code;
32✔
318
    }
319

320
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
40✔
321
    {
40✔
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 = {
40✔
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_POP,
383
                &&TARGET_DUP,
384
                &&TARGET_ADD,
385
                &&TARGET_SUB,
386
                &&TARGET_MUL,
387
                &&TARGET_DIV,
388
                &&TARGET_GT,
389
                &&TARGET_LT,
390
                &&TARGET_LE,
391
                &&TARGET_GE,
392
                &&TARGET_NEQ,
393
                &&TARGET_EQ,
394
                &&TARGET_LEN,
395
                &&TARGET_EMPTY,
396
                &&TARGET_TAIL,
397
                &&TARGET_HEAD,
398
                &&TARGET_ISNIL,
399
                &&TARGET_ASSERT,
400
                &&TARGET_TO_NUM,
401
                &&TARGET_TO_STR,
402
                &&TARGET_AT,
403
                &&TARGET_MOD,
404
                &&TARGET_TYPE,
405
                &&TARGET_HASFIELD,
406
                &&TARGET_NOT,
407
                &&TARGET_LOAD_CONST_LOAD_CONST,
408
                &&TARGET_LOAD_CONST_STORE,
409
                &&TARGET_LOAD_CONST_SET_VAL,
410
                &&TARGET_STORE_FROM,
411
                &&TARGET_SET_VAL_FROM,
412
                &&TARGET_INCREMENT,
413
                &&TARGET_DECREMENT,
414
                &&TARGET_STORE_TAIL,
415
                &&TARGET_STORE_HEAD,
416
                &&TARGET_SET_VAL_TAIL,
417
                &&TARGET_SET_VAL_HEAD,
418
                &&TARGET_CALL_BUILTIN
419
            };
420
#    pragma GCC diagnostic pop
421
#endif
422

423
        try
424
        {
425
            uint8_t inst = 0;
40✔
426
            uint8_t padding = 0;
40✔
427
            uint16_t arg = 0;
40✔
428
            uint16_t primary_arg = 0;
40✔
429
            uint16_t secondary_arg = 0;
40✔
430

431
            m_running = true;
40✔
432

433
            DISPATCH();
40✔
434
            {
435
#if !ARK_USE_COMPUTED_GOTOS
436
            dispatch_opcode:
437
                switch (inst)
438
#endif
439
                {
×
440
#pragma region "Instructions"
441
                    TARGET(NOP)
442
                    {
443
                        DISPATCH();
×
444
                    }
15,727✔
445

446
                    TARGET(LOAD_SYMBOL)
447
                    {
448
                        push(loadSymbol(arg, context), context);
15,727✔
449
                        DISPATCH();
15,725✔
450
                    }
405✔
451

452
                    TARGET(LOAD_CONST)
453
                    {
454
                        push(loadConstAsPtr(arg), context);
405✔
455
                        DISPATCH();
405✔
456
                    }
589✔
457

458
                    TARGET(POP_JUMP_IF_TRUE)
459
                    {
460
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
835✔
461
                            context.ip = arg * 4;  // instructions are 4 bytes
246✔
462
                        DISPATCH();
589✔
463
                    }
4,418✔
464

465
                    TARGET(STORE)
466
                    {
467
                        store(arg, popAndResolveAsPtr(context), context);
4,418✔
468
                        DISPATCH();
4,418✔
469
                    }
4,336✔
470

471
                    TARGET(SET_VAL)
472
                    {
473
                        setVal(arg, popAndResolveAsPtr(context), context);
4,336✔
474
                        DISPATCH();
4,336✔
475
                    }
2,059✔
476

477
                    TARGET(POP_JUMP_IF_FALSE)
478
                    {
479
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
2,077✔
480
                            context.ip = arg * 4;  // instructions are 4 bytes
18✔
481
                        DISPATCH();
2,059✔
482
                    }
2,376✔
483

484
                    TARGET(JUMP)
485
                    {
486
                        context.ip = arg * 4;  // instructions are 4 bytes
2,376✔
487
                        DISPATCH();
2,376✔
488
                    }
743✔
489

490
                    TARGET(RET)
491
                    {
492
                        {
493
                            Value ip_or_val = *popAndResolveAsPtr(context);
743✔
494
                            // no return value on the stack
495
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
743✔
496
                            {
497
                                context.ip = ip_or_val.pageAddr();
679✔
498
                                // we always push PP then IP, thus the next value
499
                                // MUST be the page pointer
500
                                context.pp = pop(context)->pageAddr();
679✔
501

502
                                returnFromFuncCall(context);
679✔
503
                                push(Builtins::nil, context);
679✔
504
                            }
679✔
505
                            // value on the stack
506
                            else [[likely]]
507
                            {
508
                                Value* ip;
509
                                do
64✔
510
                                {
511
                                    ip = popAndResolveAsPtr(context);
64✔
512
                                } while (ip->valueType() != ValueType::InstPtr);
64✔
513

514
                                context.ip = ip->pageAddr();
64✔
515
                                context.pp = pop(context)->pageAddr();
64✔
516

517
                                returnFromFuncCall(context);
64✔
518
                                push(std::move(ip_or_val), context);
64✔
519
                            }
520

521
                            if (context.fc <= untilFrameCount)
743✔
522
                                GOTO_HALT();
6✔
523
                        }
743✔
524

525
                        DISPATCH();
737✔
526
                    }
10✔
527

528
                    TARGET(HALT)
529
                    {
530
                        m_running = false;
10✔
531
                        GOTO_HALT();
10✔
532
                    }
4,840✔
533

534
                    TARGET(CALL)
535
                    {
536
                        // stack pointer + 2 because we push IP and PP
537
                        if (context.sp + 2u >= VMStackSize) [[unlikely]]
4,840✔
538
                            throwVMError(
1✔
539
                                ErrorKind::VM,
540
                                fmt::format(
2✔
541
                                    "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
542
                                    m_state.m_symbols[context.last_symbol]));
1✔
543
                        call(context, arg);
4,839✔
544
                        if (!m_running)
4,835✔
545
                            GOTO_HALT();
×
546
                        DISPATCH();
4,835✔
547
                    }
144✔
548

549
                    TARGET(CAPTURE)
550
                    {
551
                        if (!context.saved_scope)
144✔
552
                            context.saved_scope = Scope();
21✔
553

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

563
                        DISPATCH();
144✔
564
                    }
142✔
565

566
                    TARGET(BUILTIN)
567
                    {
568
                        push(Builtins::builtins[arg].second, context);
142✔
569
                        DISPATCH();
142✔
570
                    }
3✔
571

572
                    TARGET(DEL)
573
                    {
574
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
3✔
575
                        {
576
                            if (var->valueType() == ValueType::User)
2✔
577
                                var->usertypeRef().del();
×
578
                            *var = Value();
2✔
579
                            DISPATCH();
2✔
580
                        }
581

582
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
583
                    }
21✔
584

585
                    TARGET(MAKE_CLOSURE)
21✔
586
                    {
587
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
21✔
588
                        context.saved_scope.reset();
21✔
589
                        DISPATCH();
21✔
590
                    }
775✔
591

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

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

641
                    TARGET(PLUGIN)
642
                    {
UNCOV
643
                        loadPlugin(arg, context);
×
UNCOV
644
                        DISPATCH();
×
645
                    }
117✔
646

647
                    TARGET(LIST)
648
                    {
649
                        {
650
                            Value l(ValueType::List);
117✔
651
                            if (arg != 0)
117✔
652
                                l.list().reserve(arg);
81✔
653

654
                            for (uint16_t i = 0; i < arg; ++i)
370✔
655
                                l.push_back(*popAndResolveAsPtr(context));
253✔
656
                            push(std::move(l), context);
117✔
657
                        }
117✔
658
                        DISPATCH();
117✔
659
                    }
9✔
660

661
                    TARGET(APPEND)
662
                    {
663
                        {
664
                            Value* list = popAndResolveAsPtr(context);
9✔
665
                            if (list->valueType() != ValueType::List)
9✔
UNCOV
666
                                types::generateError(
×
UNCOV
667
                                    "append",
×
UNCOV
668
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
669
                                    { *list });
×
670

671
                            const auto size = static_cast<uint16_t>(list->constList().size());
9✔
672

673
                            Value obj { *list };
9✔
674
                            obj.list().reserve(size + arg);
9✔
675

676
                            for (uint16_t i = 0; i < arg; ++i)
18✔
677
                                obj.push_back(*popAndResolveAsPtr(context));
9✔
678
                            push(std::move(obj), context);
9✔
679
                        }
9✔
680
                        DISPATCH();
9✔
681
                    }
3✔
682

683
                    TARGET(CONCAT)
684
                    {
685
                        {
686
                            Value* list = popAndResolveAsPtr(context);
3✔
687
                            if (list->valueType() != ValueType::List)
3✔
688
                                types::generateError(
×
UNCOV
689
                                    "concat",
×
UNCOV
690
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
691
                                    { *list });
×
692

693
                            Value obj { *list };
3✔
694

695
                            for (uint16_t i = 0; i < arg; ++i)
6✔
696
                            {
697
                                Value* next = popAndResolveAsPtr(context);
3✔
698

699
                                if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
3✔
UNCOV
700
                                    types::generateError(
×
UNCOV
701
                                        "concat",
×
702
                                        { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
703
                                        { *list, *next });
×
704

705
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
3✔
706
                            }
3✔
707
                            push(std::move(obj), context);
3✔
708
                        }
3✔
709
                        DISPATCH();
3✔
710
                    }
36✔
711

712
                    TARGET(APPEND_IN_PLACE)
713
                    {
714
                        Value* list = popAndResolveAsPtr(context);
36✔
715

716
                        if (list->valueType() != ValueType::List)
36✔
717
                            types::generateError(
×
718
                                "append!",
×
719
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
720
                                { *list });
×
721

722
                        for (uint16_t i = 0; i < arg; ++i)
72✔
723
                            list->push_back(*popAndResolveAsPtr(context));
36✔
724
                        DISPATCH();
36✔
725
                    }
1✔
726

727
                    TARGET(CONCAT_IN_PLACE)
728
                    {
729
                        Value* list = popAndResolveAsPtr(context);
1✔
730

731
                        if (list->valueType() != ValueType::List)
1✔
UNCOV
732
                            types::generateError(
×
UNCOV
733
                                "concat",
×
UNCOV
734
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
UNCOV
735
                                { *list });
×
736

737
                        for (uint16_t i = 0; i < arg; ++i)
2✔
738
                        {
739
                            Value* next = popAndResolveAsPtr(context);
1✔
740

741
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
1✔
UNCOV
742
                                types::generateError(
×
UNCOV
743
                                    "concat!",
×
744
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
745
                                    { *list, *next });
×
746

747
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
1✔
748
                        }
1✔
749
                        DISPATCH();
1✔
750
                    }
4✔
751

752
                    TARGET(POP_LIST)
753
                    {
754
                        {
755
                            Value list = *popAndResolveAsPtr(context);
4✔
756
                            Value number = *popAndResolveAsPtr(context);
4✔
757

758
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
4✔
UNCOV
759
                                types::generateError(
×
UNCOV
760
                                    "pop",
×
UNCOV
761
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
UNCOV
762
                                    { list, number });
×
763

764
                            long idx = static_cast<long>(number.number());
4✔
765
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
766
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
767
                                throwVMError(
1✔
768
                                    ErrorKind::Index,
769
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
770

771
                            list.list().erase(list.list().begin() + idx);
3✔
772
                            push(list, context);
3✔
773
                        }
4✔
774
                        DISPATCH();
3✔
775
                    }
37✔
776

777
                    TARGET(POP_LIST_IN_PLACE)
778
                    {
779
                        {
780
                            Value* list = popAndResolveAsPtr(context);
37✔
781
                            Value number = *popAndResolveAsPtr(context);
37✔
782

783
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
37✔
UNCOV
784
                                types::generateError(
×
UNCOV
785
                                    "pop!",
×
UNCOV
786
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
UNCOV
787
                                    { *list, number });
×
788

789
                            long idx = static_cast<long>(number.number());
37✔
790
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
37✔
791
                            if (std::cmp_greater_equal(idx, list->list().size()))
37✔
UNCOV
792
                                throwVMError(
×
793
                                    ErrorKind::Index,
UNCOV
794
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
×
795

796
                            list->list().erase(list->list().begin() + idx);
37✔
797
                        }
37✔
798
                        DISPATCH();
37✔
799
                    }
713✔
800

801
                    TARGET(POP)
802
                    {
803
                        pop(context);
713✔
804
                        DISPATCH();
713✔
805
                    }
12✔
806

807
                    TARGET(DUP)
808
                    {
809
                        context.stack[context.sp] = context.stack[context.sp - 1];
12✔
810
                        ++context.sp;
12✔
811
                        DISPATCH();
12✔
812
                    }
2,027✔
813

814
#pragma endregion
815

816
#pragma region "Operators"
817

818
                    TARGET(ADD)
819
                    {
820
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,027✔
821

822
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
2,027✔
823
                            push(Value(a->number() + b->number()), context);
2,024✔
824
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
3✔
825
                            push(Value(a->string() + b->string()), context);
3✔
826
                        else
827
                            types::generateError(
×
828
                                "+",
×
UNCOV
829
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
×
UNCOV
830
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
×
UNCOV
831
                                { *a, *b });
×
832
                        DISPATCH();
2,027✔
833
                    }
17✔
834

835
                    TARGET(SUB)
836
                    {
837
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17✔
838

839
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
17✔
840
                            types::generateError(
×
841
                                "-",
×
UNCOV
842
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
843
                                { *a, *b });
×
844
                        push(Value(a->number() - b->number()), context);
17✔
845
                        DISPATCH();
17✔
846
                    }
18✔
847

848
                    TARGET(MUL)
849
                    {
850
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
18✔
851

852
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
18✔
853
                            types::generateError(
×
854
                                "*",
×
UNCOV
855
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
856
                                { *a, *b });
×
857
                        push(Value(a->number() * b->number()), context);
18✔
858
                        DISPATCH();
18✔
859
                    }
10✔
860

861
                    TARGET(DIV)
862
                    {
863
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
10✔
864

865
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
10✔
UNCOV
866
                            types::generateError(
×
UNCOV
867
                                "/",
×
UNCOV
868
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
869
                                { *a, *b });
×
870
                        auto d = b->number();
10✔
871
                        if (d == 0)
10✔
872
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
873

874
                        push(Value(a->number() / d), context);
9✔
875
                        DISPATCH();
9✔
876
                    }
17✔
877

878
                    TARGET(GT)
879
                    {
880
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
17✔
881
                        push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
17✔
882
                        DISPATCH();
17✔
883
                    }
2,056✔
884

885
                    TARGET(LT)
886
                    {
887
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2,056✔
888
                        push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
2,056✔
889
                        DISPATCH();
2,056✔
890
                    }
3✔
891

892
                    TARGET(LE)
893
                    {
894
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
3✔
895
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
3✔
896
                        DISPATCH();
3✔
897
                    }
3✔
898

899
                    TARGET(GE)
900
                    {
901
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
3✔
902
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
3✔
903
                        DISPATCH();
3✔
904
                    }
61✔
905

906
                    TARGET(NEQ)
907
                    {
908
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
61✔
909
                        push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
61✔
910
                        DISPATCH();
61✔
911
                    }
243✔
912

913
                    TARGET(EQ)
914
                    {
915
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
243✔
916
                        push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
243✔
917
                        DISPATCH();
243✔
918
                    }
56✔
919

920
                    TARGET(LEN)
921
                    {
922
                        Value* a = popAndResolveAsPtr(context);
56✔
923

924
                        if (a->valueType() == ValueType::List)
56✔
925
                            push(Value(static_cast<int>(a->constList().size())), context);
37✔
926
                        else if (a->valueType() == ValueType::String)
19✔
927
                            push(Value(static_cast<int>(a->string().size())), context);
19✔
928
                        else
UNCOV
929
                            types::generateError(
×
UNCOV
930
                                "len",
×
931
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
932
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
933
                                { *a });
×
934
                        DISPATCH();
56✔
935
                    }
1✔
936

937
                    TARGET(EMPTY)
938
                    {
939
                        Value* a = popAndResolveAsPtr(context);
1✔
940

941
                        if (a->valueType() == ValueType::List)
1✔
UNCOV
942
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
×
943
                        else if (a->valueType() == ValueType::String)
1✔
944
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
1✔
945
                        else
UNCOV
946
                            types::generateError(
×
UNCOV
947
                                "empty?",
×
UNCOV
948
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
UNCOV
949
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
950
                                { *a });
×
951
                        DISPATCH();
1✔
952
                    }
3✔
953

954
                    TARGET(TAIL)
955
                    {
956
                        Value* a = popAndResolveAsPtr(context);
3✔
957
                        push(helper::tail(a), context);
3✔
958
                        DISPATCH();
3✔
959
                    }
3✔
960

961
                    TARGET(HEAD)
962
                    {
963
                        Value* a = popAndResolveAsPtr(context);
3✔
964
                        push(helper::head(a), context);
3✔
965
                        DISPATCH();
3✔
966
                    }
3✔
967

968
                    TARGET(ISNIL)
969
                    {
970
                        Value* a = popAndResolveAsPtr(context);
3✔
971
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
3✔
972
                        DISPATCH();
3✔
UNCOV
973
                    }
×
974

975
                    TARGET(ASSERT)
976
                    {
UNCOV
977
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
×
978

UNCOV
979
                        if (b->valueType() != ValueType::String)
×
980
                            types::generateError(
×
981
                                "assert",
×
982
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
×
983
                                { *a, *b });
×
984

UNCOV
985
                        if (*a == Builtins::falseSym)
×
UNCOV
986
                            throw AssertionFailed(b->stringRef());
×
UNCOV
987
                        DISPATCH();
×
988
                    }
3✔
989

990
                    TARGET(TO_NUM)
991
                    {
992
                        Value* a = popAndResolveAsPtr(context);
3✔
993

994
                        if (a->valueType() != ValueType::String)
3✔
UNCOV
995
                            types::generateError(
×
UNCOV
996
                                "toNumber",
×
UNCOV
997
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
UNCOV
998
                                { *a });
×
999

1000
                        double val;
1001
                        if (Utils::isDouble(a->string(), &val))
3✔
1002
                            push(Value(val), context);
2✔
1003
                        else
1004
                            push(Builtins::nil, context);
1✔
1005
                        DISPATCH();
3✔
1006
                    }
7✔
1007

1008
                    TARGET(TO_STR)
1009
                    {
1010
                        Value* a = popAndResolveAsPtr(context);
7✔
1011
                        push(Value(a->toString(*this)), context);
7✔
1012
                        DISPATCH();
7✔
1013
                    }
2,050✔
1014

1015
                    TARGET(AT)
1016
                    {
1017
                        {
1018
                            Value* b = popAndResolveAsPtr(context);
2,050✔
1019
                            Value a = *popAndResolveAsPtr(context);  // be careful, it's not a pointer
2,050✔
1020

1021
                            if (b->valueType() != ValueType::Number)
2,050✔
UNCOV
1022
                                types::generateError(
×
UNCOV
1023
                                    "@",
×
UNCOV
1024
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
UNCOV
1025
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
UNCOV
1026
                                    { a, *b });
×
1027

1028
                            long idx = static_cast<long>(b->number());
2,050✔
1029

1030
                            if (a.valueType() == ValueType::List)
2,050✔
1031
                            {
1032
                                if (std::cmp_less(std::abs(idx), a.list().size()))
2,046✔
1033
                                    push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
2,045✔
1034
                                else
1035
                                    throwVMError(
1✔
1036
                                        ErrorKind::Index,
1037
                                        fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1✔
1038
                            }
2,045✔
1039
                            else if (a.valueType() == ValueType::String)
4✔
1040
                            {
1041
                                if (std::cmp_less(std::abs(idx), a.string().size()))
4✔
1042
                                    push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
3✔
1043
                                else
1044
                                    throwVMError(
1✔
1045
                                        ErrorKind::Index,
1046
                                        fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1✔
1047
                            }
3✔
1048
                            else
1049
                                types::generateError(
×
1050
                                    "@",
×
UNCOV
1051
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
UNCOV
1052
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
UNCOV
1053
                                    { a, *b });
×
1054
                        }
2,050✔
1055
                        DISPATCH();
2,048✔
1056
                    }
2✔
1057

1058
                    TARGET(MOD)
1059
                    {
1060
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
2✔
1061
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
2✔
1062
                            types::generateError(
×
UNCOV
1063
                                "mod",
×
UNCOV
1064
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1065
                                { *a, *b });
×
1066
                        push(Value(std::fmod(a->number(), b->number())), context);
2✔
1067
                        DISPATCH();
2✔
1068
                    }
13✔
1069

1070
                    TARGET(TYPE)
1071
                    {
1072
                        Value* a = popAndResolveAsPtr(context);
13✔
1073
                        if (a == &m_undefined_value) [[unlikely]]
13✔
1074
                            types::generateError(
×
1075
                                "type",
×
1076
                                { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
×
UNCOV
1077
                                {});
×
1078

1079
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
13✔
1080
                        DISPATCH();
13✔
1081
                    }
2✔
1082

1083
                    TARGET(HASFIELD)
1084
                    {
1085
                        {
1086
                            Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
2✔
1087
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
2✔
UNCOV
1088
                                types::generateError(
×
UNCOV
1089
                                    "hasField",
×
UNCOV
1090
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
×
UNCOV
1091
                                    { *closure, *field });
×
1092

1093
                            auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef());
2✔
1094
                            if (it == m_state.m_symbols.end())
2✔
1095
                            {
1096
                                push(Builtins::falseSym, context);
1✔
1097
                                DISPATCH();
1✔
1098
                            }
1099

1100
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1101
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1102
                        }
1103
                        DISPATCH();
1✔
1104
                    }
9✔
1105

1106
                    TARGET(NOT)
1107
                    {
1108
                        Value* a = popAndResolveAsPtr(context);
9✔
1109
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
9✔
1110
                        DISPATCH();
9✔
1111
                    }
225✔
1112

1113
#pragma endregion
1114

1115
#pragma region "Super Instructions"
1116
                    TARGET(LOAD_CONST_LOAD_CONST)
1117
                    {
1118
                        UNPACK_ARGS();
225✔
1119
                        push(loadConstAsPtr(primary_arg), context);
225✔
1120
                        push(loadConstAsPtr(secondary_arg), context);
225✔
1121
                        DISPATCH();
225✔
1122
                    }
201✔
1123

1124
                    TARGET(LOAD_CONST_STORE)
1125
                    {
1126
                        UNPACK_ARGS();
201✔
1127
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
201✔
1128
                        DISPATCH();
201✔
1129
                    }
40✔
1130

1131
                    TARGET(LOAD_CONST_SET_VAL)
1132
                    {
1133
                        UNPACK_ARGS();
40✔
1134
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
40✔
1135
                        DISPATCH();
39✔
1136
                    }
4✔
1137

1138
                    TARGET(STORE_FROM)
1139
                    {
1140
                        UNPACK_ARGS();
4✔
1141
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
4✔
1142
                        DISPATCH();
3✔
1143
                    }
71✔
1144

1145
                    TARGET(SET_VAL_FROM)
1146
                    {
1147
                        UNPACK_ARGS();
71✔
1148
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
71✔
1149
                        DISPATCH();
71✔
1150
                    }
6,415✔
1151

1152
                    TARGET(INCREMENT)
1153
                    {
1154
                        UNPACK_ARGS();
6,415✔
1155
                        {
1156
                            Value* var = loadSymbol(primary_arg, context);
6,415✔
1157

1158
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1159
                            if (var->valueType() == ValueType::Reference)
6,415✔
1160
                                var = var->reference();
×
1161

1162
                            if (var->valueType() == ValueType::Number)
6,415✔
1163
                                push(Value(var->number() + 1), context);
6,415✔
1164
                            else
1165
                                types::generateError(
×
1166
                                    "+",
×
UNCOV
1167
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1168
                                    { *var, Value(1) });
×
1169
                        }
1170
                        DISPATCH();
6,415✔
1171
                    }
×
1172

1173
                    TARGET(DECREMENT)
1174
                    {
UNCOV
1175
                        UNPACK_ARGS();
×
1176
                        {
1177
                            Value* var = loadSymbol(primary_arg, context);
×
1178

1179
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
UNCOV
1180
                            if (var->valueType() == ValueType::Reference)
×
1181
                                var = var->reference();
×
1182

1183
                            if (var->valueType() == ValueType::Number)
×
1184
                                push(Value(var->number() - 1), context);
×
1185
                            else
1186
                                types::generateError(
×
1187
                                    "-",
×
1188
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
UNCOV
1189
                                    { *var, Value(1) });
×
1190
                        }
UNCOV
1191
                        DISPATCH();
×
1192
                    }
×
1193

1194
                    TARGET(STORE_TAIL)
1195
                    {
1196
                        UNPACK_ARGS();
×
1197
                        {
1198
                            Value* list = loadSymbol(primary_arg, context);
×
1199
                            Value tail = helper::tail(list);
×
UNCOV
1200
                            store(secondary_arg, &tail, context);
×
UNCOV
1201
                        }
×
UNCOV
1202
                        DISPATCH();
×
1203
                    }
×
1204

1205
                    TARGET(STORE_HEAD)
1206
                    {
1207
                        UNPACK_ARGS();
×
1208
                        {
1209
                            Value* list = loadSymbol(primary_arg, context);
×
1210
                            Value head = helper::head(list);
×
UNCOV
1211
                            store(secondary_arg, &head, context);
×
UNCOV
1212
                        }
×
UNCOV
1213
                        DISPATCH();
×
1214
                    }
×
1215

1216
                    TARGET(SET_VAL_TAIL)
1217
                    {
1218
                        UNPACK_ARGS();
×
1219
                        {
1220
                            Value* list = loadSymbol(primary_arg, context);
×
UNCOV
1221
                            Value tail = helper::tail(list);
×
UNCOV
1222
                            setVal(secondary_arg, &tail, context);
×
UNCOV
1223
                        }
×
UNCOV
1224
                        DISPATCH();
×
UNCOV
1225
                    }
×
1226

1227
                    TARGET(SET_VAL_HEAD)
1228
                    {
1229
                        UNPACK_ARGS();
×
1230
                        {
UNCOV
1231
                            Value* list = loadSymbol(primary_arg, context);
×
UNCOV
1232
                            Value head = helper::head(list);
×
UNCOV
1233
                            setVal(secondary_arg, &head, context);
×
UNCOV
1234
                        }
×
UNCOV
1235
                        DISPATCH();
×
1236
                    }
135✔
1237

1238
                    TARGET(CALL_BUILTIN)
1239
                    {
1240
                        UNPACK_ARGS();
135✔
1241
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1242
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
135✔
1243
                        if (!m_running)
128✔
UNCOV
1244
                            GOTO_HALT();
×
1245
                        DISPATCH();
128✔
1246
                    }
1247
#pragma endregion
1248
                }
16✔
1249
#if ARK_USE_COMPUTED_GOTOS
1250
            dispatch_end:
1251
                do
16✔
1252
                {
1253
                } while (false);
16✔
1254
#endif
1255
            }
1256
        }
38✔
1257
        catch (const std::exception& e)
1258
        {
1259
            if (fail_with_exception)
22✔
1260
                throw;
22✔
1261

UNCOV
1262
            fmt::println("{}", e.what());
×
UNCOV
1263
            backtrace(context);
×
1264
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1265
            // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1266
            m_exit_code = 0;
1267
#else
UNCOV
1268
            m_exit_code = 1;
×
1269
#endif
1270
        }
38✔
1271
        catch (...)
1272
        {
1273
            if (fail_with_exception)
×
1274
                throw;
×
1275

1276
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1277
            throw;
1278
#endif
1279
            fmt::println("Unknown error");
×
1280
            backtrace(context);
×
UNCOV
1281
            m_exit_code = 1;
×
1282
        }
44✔
1283

1284
        return m_exit_code;
16✔
1285
    }
46✔
1286

1287
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
×
1288
    {
×
1289
        for (auto& local : std::ranges::reverse_view(context.locals))
×
1290
        {
1291
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
×
UNCOV
1292
                return id;
×
1293
        }
×
UNCOV
1294
        return std::numeric_limits<uint16_t>::max();
×
UNCOV
1295
    }
×
1296

1297
    void VM::throwVMError(ErrorKind kind, const std::string& message)
16✔
1298
    {
16✔
1299
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
16✔
1300
    }
16✔
1301

UNCOV
1302
    void VM::backtrace(ExecutionContext& context) noexcept
×
1303
    {
×
1304
        const std::size_t saved_ip = context.ip;
×
1305
        const std::size_t saved_pp = context.pp;
×
UNCOV
1306
        const uint16_t saved_sp = context.sp;
×
1307

1308
        if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
×
1309
        {
1310
            // display call stack trace
UNCOV
1311
            const Scope old_scope = context.locals.back();
×
1312

1313
            while (context.fc != 0)
×
1314
            {
1315
                fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
×
1316
                if (context.pp != 0)
×
1317
                {
1318
                    const uint16_t id = findNearestVariableIdWithValue(
×
1319
                        Value(static_cast<PageAddr_t>(context.pp)),
×
1320
                        context);
×
1321

UNCOV
1322
                    if (id < m_state.m_symbols.size())
×
UNCOV
1323
                        fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)));
×
1324
                    else  // should never happen
1325
                        fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold)));
×
1326

UNCOV
1327
                    Value* ip;
×
1328
                    do
×
1329
                    {
1330
                        ip = popAndResolveAsPtr(context);
×
1331
                    } while (ip->valueType() != ValueType::InstPtr);
×
1332

UNCOV
1333
                    context.ip = ip->pageAddr();
×
UNCOV
1334
                    context.pp = pop(context)->pageAddr();
×
UNCOV
1335
                    returnFromFuncCall(context);
×
1336
                }
×
1337
                else
1338
                {
1339
                    fmt::println("In global scope");
×
1340
                    break;
×
1341
                }
1342

1343
                if (original_frame_count - context.fc > 7)
×
1344
                {
1345
                    fmt::println("...");
×
UNCOV
1346
                    break;
×
1347
                }
1348
            }
1349

1350
            // display variables values in the current scope
1351
            fmt::println("\nCurrent scope variables values:");
×
UNCOV
1352
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
×
1353
            {
1354
                fmt::println(
×
UNCOV
1355
                    "{} = {}",
×
1356
                    fmt::styled(m_state.m_symbols[old_scope.m_data[i].first], fmt::fg(fmt::color::cyan)),
×
1357
                    old_scope.m_data[i].second.toString(*this));
×
1358
            }
×
1359

1360
            while (context.fc != 1)
×
1361
            {
UNCOV
1362
                Value* tmp = pop(context);
×
UNCOV
1363
                if (tmp->valueType() == ValueType::InstPtr)
×
UNCOV
1364
                    --context.fc;
×
UNCOV
1365
                *tmp = m_no_value;
×
UNCOV
1366
            }
×
1367
            // pop the PP as well
UNCOV
1368
            pop(context);
×
UNCOV
1369
        }
×
1370

UNCOV
1371
        std::cerr << "At IP: " << (saved_ip / 4)  // dividing by 4 because the instructions are actually on 4 bytes
×
UNCOV
1372
                  << ", PP: " << saved_pp
×
UNCOV
1373
                  << ", SP: " << saved_sp
×
UNCOV
1374
                  << "\n";
×
UNCOV
1375
    }
×
1376
}
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