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

ArkScript-lang / Ark / 13497462726

24 Feb 2025 11:57AM UTC coverage: 79.113% (+0.02%) from 79.091%
13497462726

Pull #512

github

web-flow
Merge 42233650c into d1bae63a9
Pull Request #512: Fix/exported symbols

22 of 23 new or added lines in 6 files covered. (95.65%)

1 existing line in 1 file now uncovered.

5848 of 7392 relevant lines covered (79.11%)

59723.21 hits per line

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

65.87
/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 :
201✔
80
        m_state(state), m_exit_code(0), m_running(false)
67✔
81
    {
67✔
82
        m_execution_contexts.emplace_back(std::make_unique<ExecutionContext>())->locals.reserve(4);
67✔
83
    }
67✔
84

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

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

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

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

105
        context.locals.clear();
68✔
106
        context.locals.emplace_back();
68✔
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)
91✔
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
    }
68✔
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
    Value VM::resolve(ExecutionContext* context, std::vector<Value>& n)
6✔
144
    {
6✔
145
        if (!n[0].isFunction())
6✔
NEW
146
            throw TypeError(fmt::format("VM::resolve couldn't resolve a non-function ({})", types_to_str[static_cast<std::size_t>(n[0].valueType())]));
×
147

148
        const std::size_t ip = context->ip;
6✔
149
        const std::size_t pp = context->pp;
6✔
150

151
        // convert and push arguments in reverse order
152
        for (auto it = n.begin() + 1, it_end = n.end(); it != it_end; ++it)
24✔
153
            push(*it, *context);
18✔
154
        push(n[0], *context);
6✔
155

156
        const std::size_t frames_count = context->fc;
6✔
157
        // call it
158
        call(*context, static_cast<uint16_t>(n.size() - 1));
6✔
159
        // reset instruction pointer, otherwise the safeRun method will start at ip = -1
160
        // without doing context.ip++ as intended (done right after the call() in the loop, but here
161
        // we start outside this loop)
162
        context->ip = 0;
6✔
163

164
        // run until the function returns
165
        safeRun(*context, /* untilFrameCount */ frames_count);
6✔
166

67✔
167
        // restore VM state
168
        context->ip = ip;
6✔
169
        context->pp = pp;
6✔
170

171
        // get result
172
        return *popAndResolveAsPtr(*context);
6✔
173
    }
6✔
174

175
    void VM::loadPlugin(const uint16_t id, ExecutionContext& context)
×
176
    {
×
177
        namespace fs = std::filesystem;
178

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

181
        std::string path = file;
×
182
        // bytecode loaded from file
183
        if (m_state.m_filename != ARK_NO_NAME_FILE)
×
184
            path = (fs::path(m_state.m_filename).parent_path() / fs::path(file)).relative_path().string();
×
185

186
        std::shared_ptr<SharedLibrary> lib;
×
187
        // if it exists alongside the .arkc file
188
        if (Utils::fileExists(path))
×
189
            lib = std::make_shared<SharedLibrary>(path);
×
190
        else
191
        {
192
            for (auto const& v : m_state.m_libenv)
×
193
            {
194
                std::string lib_path = (fs::path(v) / fs::path(file)).string();
×
195

196
                // if it's already loaded don't do anything
197
                if (std::ranges::find_if(m_shared_lib_objects, [&](const auto& val) {
×
UNCOV
198
                        return (val->path() == path || val->path() == lib_path);
×
199
                    }) != m_shared_lib_objects.end())
×
200
                    return;
×
201

202
                // check in lib_path
203
                if (Utils::fileExists(lib_path))
×
204
                {
205
                    lib = std::make_shared<SharedLibrary>(lib_path);
×
206
                    break;
×
207
                }
208
            }
×
209
        }
210

211
        if (!lib)
×
212
        {
213
            auto lib_path = std::accumulate(
×
214
                std::next(m_state.m_libenv.begin()),
×
215
                m_state.m_libenv.end(),
×
216
                m_state.m_libenv[0].string(),
×
217
                [](const std::string& a, const fs::path& b) -> std::string {
×
218
                    return a + "\n\t- " + b.string();
×
219
                });
×
220
            throwVMError(
×
221
                ErrorKind::Module,
222
                fmt::format("Could not find module '{}'. Searched under\n\t- {}\n\t- {}", file, path, lib_path));
×
223
        }
×
224

225
        m_shared_lib_objects.emplace_back(lib);
×
226

227
        // load the mapping from the dynamic library
228
        try
229
        {
230
            const mapping* map = m_shared_lib_objects.back()->get<mapping* (*)()>("getFunctionsMapping")();
×
231
            // load the mapping data
232
            std::size_t i = 0;
×
233
            while (map[i].name != nullptr)
×
234
            {
235
                // put it in the global frame, aka the first one
236
                auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
237
                if (it != m_state.m_symbols.end())
×
238
                    context.locals[0].push_back(static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)), Value(map[i].value));
×
239

240
                ++i;
×
241
            }
×
242
        }
×
243
        catch (const std::system_error& e)
244
        {
245
            throwVMError(
×
246
                ErrorKind::Module,
247
                fmt::format(
×
248
                    "An error occurred while loading module '{}': {}\nIt is most likely because the versions of the module and the language don't match.",
×
249
                    file, e.what()));
×
250
        }
×
251
    }
×
252

253
    void VM::exit(const int code) noexcept
×
254
    {
×
255
        m_exit_code = code;
×
256
        m_running = false;
×
257
    }
×
258

259
    ExecutionContext* VM::createAndGetContext()
6✔
260
    {
6✔
261
        const std::lock_guard lock(m_mutex);
6✔
262

263
        m_execution_contexts.push_back(std::make_unique<ExecutionContext>());
6✔
264
        ExecutionContext* ctx = m_execution_contexts.back().get();
6✔
265
        ctx->stacked_closure_scopes.emplace_back(nullptr);
6✔
266

267
        ctx->locals.reserve(m_execution_contexts.front()->locals.size());
6✔
268
        for (const auto& local : m_execution_contexts.front()->locals)
20✔
269
            ctx->locals.push_back(local);
14✔
270

271
        return ctx;
6✔
272
    }
6✔
273

274
    void VM::deleteContext(ExecutionContext* ec)
5✔
275
    {
5✔
276
        const std::lock_guard lock(m_mutex);
5✔
277

278
        const auto it =
5✔
279
            std::ranges::remove_if(
10✔
280
                m_execution_contexts,
5✔
281
                [ec](const std::unique_ptr<ExecutionContext>& ctx) {
21✔
282
                    return ctx.get() == ec;
16✔
283
                })
284
                .begin();
5✔
285
        m_execution_contexts.erase(it);
5✔
286
    }
5✔
287

288
    Future* VM::createFuture(std::vector<Value>& args)
6✔
289
    {
6✔
290
        ExecutionContext* ctx = createAndGetContext();
6✔
291

292
        // doing this after having created the context
293
        // because the context uses the mutex and we don't want a deadlock
294
        const std::lock_guard lock(m_mutex);
6✔
295
        m_futures.push_back(std::make_unique<Future>(ctx, this, args));
6✔
296

297
        return m_futures.back().get();
6✔
298
    }
6✔
299

300
    void VM::deleteFuture(Future* f)
×
301
    {
×
302
        const std::lock_guard lock(m_mutex);
×
303

304
        const auto it =
×
305
            std::ranges::remove_if(
×
306
                m_futures,
×
307
                [f](const std::unique_ptr<Future>& future) {
×
308
                    return future.get() == f;
×
309
                })
310
                .begin();
×
311
        m_futures.erase(it);
×
312
    }
×
313

314
    bool VM::forceReloadPlugins() const
×
315
    {
×
316
        // load the mapping from the dynamic library
317
        try
318
        {
319
            for (const auto& shared_lib : m_shared_lib_objects)
×
320
            {
321
                const mapping* map = shared_lib->template get<mapping* (*)()>("getFunctionsMapping")();
×
322
                // load the mapping data
323
                std::size_t i = 0;
×
324
                while (map[i].name != nullptr)
×
325
                {
326
                    // put it in the global frame, aka the first one
327
                    auto it = std::ranges::find(m_state.m_symbols, std::string(map[i].name));
×
328
                    if (it != m_state.m_symbols.end())
×
329
                        m_execution_contexts[0]->locals[0].push_back(
×
330
                            static_cast<uint16_t>(std::distance(m_state.m_symbols.begin(), it)),
×
331
                            Value(map[i].value));
1✔
332

333
                    ++i;
1✔
334
                }
×
335
            }
1✔
336

337
            return true;
×
338
        }
×
339
        catch (const std::system_error&)
340
        {
341
            return false;
×
342
        }
×
343
    }
×
344

345
    int VM::run(const bool fail_with_exception)
68✔
346
    {
68✔
347
        init();
68✔
348
        safeRun(*m_execution_contexts[0], 0, fail_with_exception);
68✔
349
        return m_exit_code;
68✔
350
    }
351

352
    int VM::safeRun(ExecutionContext& context, std::size_t untilFrameCount, bool fail_with_exception)
190✔
353
    {
190✔
354
#if ARK_USE_COMPUTED_GOTOS
355
#    define TARGET(op) TARGET_##op:
356
#    define DISPATCH_GOTO()            \
357
        _Pragma("GCC diagnostic push") \
358
            _Pragma("GCC diagnostic ignored \"-Wpedantic\"") goto* opcode_targets[inst];
359
        _Pragma("GCC diagnostic pop")
360
#    define GOTO_HALT() goto dispatch_end
361
#else
362
#    define TARGET(op) case op:
363
#    define DISPATCH_GOTO() goto dispatch_opcode
364
#    define GOTO_HALT() break
365
#endif
366

367
#define NEXTOPARG()                                                                      \
368
    do                                                                                   \
369
    {                                                                                    \
370
        inst = m_state.m_pages[context.pp][context.ip];                                  \
371
        padding = m_state.m_pages[context.pp][context.ip + 1];                           \
372
        arg = static_cast<uint16_t>((m_state.m_pages[context.pp][context.ip + 2] << 8) + \
373
                                    m_state.m_pages[context.pp][context.ip + 3]);        \
374
        context.ip += 4;                                                                 \
375
    } while (false)
376
#define DISPATCH() \
377
    NEXTOPARG();   \
378
    DISPATCH_GOTO();
379
#define UNPACK_ARGS()                                                                 \
380
    do                                                                                \
381
    {                                                                                 \
382
        secondary_arg = static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12); \
383
        primary_arg = arg & 0x0fff;                                                   \
384
    } while (false)
385

386
#if ARK_USE_COMPUTED_GOTOS
387
#    pragma GCC diagnostic push
388
#    pragma GCC diagnostic ignored "-Wpedantic"
389
            constexpr std::array opcode_targets = {
190✔
390
                &&TARGET_NOP,
391
                &&TARGET_LOAD_SYMBOL,
392
                &&TARGET_LOAD_CONST,
393
                &&TARGET_POP_JUMP_IF_TRUE,
394
                &&TARGET_STORE,
395
                &&TARGET_SET_VAL,
396
                &&TARGET_POP_JUMP_IF_FALSE,
397
                &&TARGET_JUMP,
398
                &&TARGET_RET,
399
                &&TARGET_HALT,
400
                &&TARGET_CALL,
401
                &&TARGET_CAPTURE,
402
                &&TARGET_BUILTIN,
403
                &&TARGET_DEL,
404
                &&TARGET_MAKE_CLOSURE,
405
                &&TARGET_GET_FIELD,
406
                &&TARGET_PLUGIN,
407
                &&TARGET_LIST,
408
                &&TARGET_APPEND,
409
                &&TARGET_CONCAT,
410
                &&TARGET_APPEND_IN_PLACE,
411
                &&TARGET_CONCAT_IN_PLACE,
412
                &&TARGET_POP_LIST,
413
                &&TARGET_POP_LIST_IN_PLACE,
414
                &&TARGET_SET_AT_INDEX,
415
                &&TARGET_SET_AT_2_INDEX,
416
                &&TARGET_POP,
417
                &&TARGET_DUP,
418
                &&TARGET_CREATE_SCOPE,
419
                &&TARGET_POP_SCOPE,
420
                &&TARGET_ADD,
421
                &&TARGET_SUB,
422
                &&TARGET_MUL,
423
                &&TARGET_DIV,
424
                &&TARGET_GT,
425
                &&TARGET_LT,
426
                &&TARGET_LE,
427
                &&TARGET_GE,
428
                &&TARGET_NEQ,
429
                &&TARGET_EQ,
430
                &&TARGET_LEN,
431
                &&TARGET_EMPTY,
432
                &&TARGET_TAIL,
433
                &&TARGET_HEAD,
434
                &&TARGET_ISNIL,
435
                &&TARGET_ASSERT,
436
                &&TARGET_TO_NUM,
437
                &&TARGET_TO_STR,
438
                &&TARGET_AT,
439
                &&TARGET_AT_AT,
440
                &&TARGET_MOD,
441
                &&TARGET_TYPE,
442
                &&TARGET_HASFIELD,
443
                &&TARGET_NOT,
444
                &&TARGET_LOAD_CONST_LOAD_CONST,
445
                &&TARGET_LOAD_CONST_STORE,
446
                &&TARGET_LOAD_CONST_SET_VAL,
447
                &&TARGET_STORE_FROM,
448
                &&TARGET_SET_VAL_FROM,
449
                &&TARGET_INCREMENT,
450
                &&TARGET_DECREMENT,
451
                &&TARGET_STORE_TAIL,
452
                &&TARGET_STORE_HEAD,
453
                &&TARGET_SET_VAL_TAIL,
454
                &&TARGET_SET_VAL_HEAD,
455
                &&TARGET_CALL_BUILTIN
456
            };
457
#    pragma GCC diagnostic pop
458
#endif
459

460
        try
461
        {
462
            uint8_t inst = 0;
190✔
463
            uint8_t padding = 0;
190✔
464
            uint16_t arg = 0;
190✔
465
            uint16_t primary_arg = 0;
190✔
466
            uint16_t secondary_arg = 0;
190✔
467

468
            m_running = true;
190✔
469

470
            DISPATCH();
190✔
471
            {
472
#if !ARK_USE_COMPUTED_GOTOS
473
            dispatch_opcode:
474
                switch (inst)
475
#endif
476
                {
×
477
#pragma region "Instructions"
478
                    TARGET(NOP)
479
                    {
480
                        DISPATCH();
×
481
                    }
536,819✔
482

483
                    TARGET(LOAD_SYMBOL)
484
                    {
485
                        push(loadSymbol(arg, context), context);
536,819✔
486
                        DISPATCH();
536,723✔
487
                    }
285,810✔
488

489
                    TARGET(LOAD_CONST)
490
                    {
491
                        push(loadConstAsPtr(arg), context);
285,810✔
492
                        DISPATCH();
285,810✔
493
                    }
284,973✔
494

495
                    TARGET(POP_JUMP_IF_TRUE)
496
                    {
497
                        if (Value boolean = *popAndResolveAsPtr(context); !!boolean)
385,291✔
498
                            context.ip = arg * 4;  // instructions are 4 bytes
100,318✔
499
                        DISPATCH();
284,973✔
500
                    }
376,161✔
501

502
                    TARGET(STORE)
503
                    {
504
                        store(arg, popAndResolveAsPtr(context), context);
376,161✔
505
                        DISPATCH();
376,161✔
506
                    }
12,569✔
507

508
                    TARGET(SET_VAL)
509
                    {
510
                        setVal(arg, popAndResolveAsPtr(context), context);
12,569✔
511
                        DISPATCH();
12,562✔
512
                    }
7,255✔
513

514
                    TARGET(POP_JUMP_IF_FALSE)
515
                    {
516
                        if (Value boolean = *popAndResolveAsPtr(context); !boolean)
7,621✔
517
                            context.ip = arg * 4;  // instructions are 4 bytes
366✔
518
                        DISPATCH();
7,254✔
519
                    }
191,784✔
520

521
                    TARGET(JUMP)
522
                    {
523
                        context.ip = arg * 4;  // instructions are 4 bytes
191,784✔
524
                        DISPATCH();
191,784✔
525
                    }
112,851✔
526

527
                    TARGET(RET)
528
                    {
529
                        {
530
                            Value ip_or_val = *popAndResolveAsPtr(context);
112,851✔
531
                            // no return value on the stack
532
                            if (ip_or_val.valueType() == ValueType::InstPtr) [[unlikely]]
112,851✔
533
                            {
534
                                context.ip = ip_or_val.pageAddr();
1,293✔
535
                                // we always push PP then IP, thus the next value
536
                                // MUST be the page pointer
537
                                context.pp = pop(context)->pageAddr();
1,293✔
538

539
                                returnFromFuncCall(context);
1,293✔
540
                                push(Builtins::nil, context);
1,293✔
541
                            }
1,293✔
542
                            // value on the stack
543
                            else [[likely]]
544
                            {
545
                                Value* ip;
546
                                do
111,558✔
547
                                {
548
                                    ip = popAndResolveAsPtr(context);
111,558✔
549
                                } while (ip->valueType() != ValueType::InstPtr);
111,558✔
550

551
                                context.ip = ip->pageAddr();
111,558✔
552
                                context.pp = pop(context)->pageAddr();
111,558✔
553

554
                                returnFromFuncCall(context);
111,558✔
555
                                push(std::move(ip_or_val), context);
111,558✔
556
                            }
557

558
                            if (context.fc <= untilFrameCount)
112,851✔
559
                                GOTO_HALT();
6✔
560
                        }
112,851✔
561

562
                        DISPATCH();
112,845✔
563
                    }
38✔
564

565
                    TARGET(HALT)
566
                    {
567
                        m_running = false;
38✔
568
                        GOTO_HALT();
38✔
569
                    }
116,948✔
570

571
                    TARGET(CALL)
572
                    {
573
                        // stack pointer + 2 because we push IP and PP
574
                        if (context.sp + 2u >= VMStackSize) [[unlikely]]
116,948✔
575
                            throwVMError(
1✔
576
                                ErrorKind::VM,
577
                                fmt::format(
2✔
578
                                    "Maximum recursion depth exceeded. You could consider rewriting your function `{}' to make use of tail-call optimization.",
1✔
579
                                    m_state.m_symbols[context.last_symbol]));
1✔
580
                        call(context, arg);
116,947✔
581
                        if (!m_running)
116,943✔
582
                            GOTO_HALT();
×
583
                        DISPATCH();
116,943✔
584
                    }
436✔
585

586
                    TARGET(CAPTURE)
587
                    {
588
                        if (!context.saved_scope)
436✔
589
                            context.saved_scope = Scope();
95✔
590

591
                        Value* ptr = (context.locals.back())[arg];
436✔
592
                        if (!ptr)
436✔
593
                            throwVMError(ErrorKind::Scope, fmt::format("Couldn't capture `{}' as it is currently unbound", m_state.m_symbols[arg]));
×
594
                        else
595
                        {
596
                            ptr = ptr->valueType() == ValueType::Reference ? ptr->reference() : ptr;
436✔
597
                            context.saved_scope.value().push_back(arg, *ptr);
436✔
598
                        }
599

600
                        DISPATCH();
436✔
601
                    }
235✔
602

603
                    TARGET(BUILTIN)
604
                    {
605
                        push(Builtins::builtins[arg].second, context);
235✔
606
                        DISPATCH();
235✔
607
                    }
1✔
608

609
                    TARGET(DEL)
610
                    {
611
                        if (Value* var = findNearestVariable(arg, context); var != nullptr)
1✔
612
                        {
613
                            if (var->valueType() == ValueType::User)
×
614
                                var->usertypeRef().del();
×
615
                            *var = Value();
×
616
                            DISPATCH();
×
617
                        }
618

619
                        throwVMError(ErrorKind::Scope, fmt::format("Can not delete unbound variable `{}'", m_state.m_symbols[arg]));
1✔
620
                    }
95✔
621

622
                    TARGET(MAKE_CLOSURE)
95✔
623
                    {
624
                        push(Value(Closure(context.saved_scope.value(), m_state.m_constants[arg].pageAddr())), context);
95✔
625
                        context.saved_scope.reset();
95✔
626
                        DISPATCH();
95✔
627
                    }
1,496✔
628

629
                    TARGET(GET_FIELD)
630
                    {
631
                        Value* var = popAndResolveAsPtr(context);
1,496✔
632
                        if (var->valueType() != ValueType::Closure)
1,496✔
633
                        {
634
                            if (context.last_symbol < m_state.m_symbols.size()) [[likely]]
1✔
635
                                throwVMError(
1✔
636
                                    ErrorKind::Type,
637
                                    fmt::format(
4✔
638
                                        "`{}' is a {}, not a Closure, can not get the field `{}' from it",
1✔
639
                                        m_state.m_symbols[context.last_symbol],
1✔
640
                                        types_to_str[static_cast<std::size_t>(var->valueType())],
1✔
641
                                        m_state.m_symbols[arg]));
1✔
642
                            else
643
                                throwVMError(ErrorKind::Type,
×
644
                                             fmt::format(
×
645
                                                 "{} is not a Closure, can not get the field `{}' from it",
×
646
                                                 types_to_str[static_cast<std::size_t>(var->valueType())],
×
647
                                                 m_state.m_symbols[arg]));
×
648
                        }
×
649

650
                        if (Value* field = var->refClosure().refScope()[arg]; field != nullptr)
1,495✔
651
                        {
652
                            // check for CALL instruction (the instruction because context.ip is already on the next instruction word)
653
                            if (m_state.m_pages[context.pp][context.ip] == CALL)
1,493✔
654
                                push(Value(Closure(var->refClosure().scopePtr(), field->pageAddr())), context);
671✔
655
                            else
656
                                push(field, context);
822✔
657
                        }
1,493✔
658
                        else
659
                        {
660
                            if (!var->refClosure().hasFieldEndingWith(m_state.m_symbols[arg], *this))
2✔
661
                                throwVMError(
1✔
662
                                    ErrorKind::Scope,
663
                                    fmt::format(
2✔
664
                                        "`{0}' isn't in the closure environment: {1}",
1✔
665
                                        m_state.m_symbols[arg],
1✔
666
                                        var->refClosure().toString(*this)));
1✔
667
                            throwVMError(
1✔
668
                                ErrorKind::Scope,
669
                                fmt::format(
2✔
670
                                    "`{0}' isn't in the closure environment: {1}. A variable in the package might have the same name as '{0}', "
1✔
671
                                    "and name resolution tried to fully qualify it. Rename either the variable or the capture to solve this",
672
                                    m_state.m_symbols[arg],
1✔
673
                                    var->refClosure().toString(*this)));
1✔
674
                        }
675
                        DISPATCH();
1,493✔
676
                    }
×
677

678
                    TARGET(PLUGIN)
679
                    {
680
                        loadPlugin(arg, context);
×
681
                        DISPATCH();
×
682
                    }
600✔
683

684
                    TARGET(LIST)
685
                    {
686
                        {
687
                            Value l(ValueType::List);
600✔
688
                            if (arg != 0)
600✔
689
                                l.list().reserve(arg);
352✔
690

691
                            for (uint16_t i = 0; i < arg; ++i)
1,557✔
692
                                l.push_back(*popAndResolveAsPtr(context));
957✔
693
                            push(std::move(l), context);
600✔
694
                        }
600✔
695
                        DISPATCH();
600✔
696
                    }
1,033✔
697

698
                    TARGET(APPEND)
699
                    {
700
                        {
701
                            Value* list = popAndResolveAsPtr(context);
1,033✔
702
                            if (list->valueType() != ValueType::List)
1,033✔
703
                                types::generateError(
×
704
                                    "append",
×
705
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
706
                                    { *list });
×
707

708
                            const auto size = static_cast<uint16_t>(list->constList().size());
1,033✔
709

710
                            Value obj { *list };
1,033✔
711
                            obj.list().reserve(size + arg);
1,033✔
712

713
                            for (uint16_t i = 0; i < arg; ++i)
2,066✔
714
                                obj.push_back(*popAndResolveAsPtr(context));
1,033✔
715
                            push(std::move(obj), context);
1,033✔
716
                        }
1,033✔
717
                        DISPATCH();
1,033✔
718
                    }
2✔
719

720
                    TARGET(CONCAT)
721
                    {
722
                        {
723
                            Value* list = popAndResolveAsPtr(context);
2✔
724
                            if (list->valueType() != ValueType::List)
2✔
725
                                types::generateError(
×
726
                                    "concat",
×
727
                                    { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
728
                                    { *list });
×
729

730
                            Value obj { *list };
2✔
731

732
                            for (uint16_t i = 0; i < arg; ++i)
4✔
733
                            {
734
                                Value* next = popAndResolveAsPtr(context);
2✔
735

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

742
                                std::ranges::copy(next->list(), std::back_inserter(obj.list()));
2✔
743
                            }
2✔
744
                            push(std::move(obj), context);
2✔
745
                        }
2✔
746
                        DISPATCH();
2✔
747
                    }
514✔
748

749
                    TARGET(APPEND_IN_PLACE)
750
                    {
751
                        Value* list = popAndResolveAsPtr(context);
514✔
752

753
                        if (list->valueType() != ValueType::List)
514✔
754
                            types::generateError(
×
755
                                "append!",
×
756
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
757
                                { *list });
×
758

759
                        for (uint16_t i = 0; i < arg; ++i)
1,028✔
760
                            list->push_back(*popAndResolveAsPtr(context));
514✔
761
                        DISPATCH();
514✔
762
                    }
40✔
763

764
                    TARGET(CONCAT_IN_PLACE)
765
                    {
766
                        Value* list = popAndResolveAsPtr(context);
40✔
767

768
                        if (list->valueType() != ValueType::List)
40✔
769
                            types::generateError(
×
770
                                "concat",
×
771
                                { { types::Contract { { types::Typedef("list", ValueType::List) } } } },
×
772
                                { *list });
×
773

774
                        for (uint16_t i = 0; i < arg; ++i)
110✔
775
                        {
776
                            Value* next = popAndResolveAsPtr(context);
70✔
777

778
                            if (list->valueType() != ValueType::List || next->valueType() != ValueType::List)
70✔
779
                                types::generateError(
×
780
                                    "concat!",
×
781
                                    { { types::Contract { { types::Typedef("dst", ValueType::List), types::Typedef("src", ValueType::List) } } } },
×
782
                                    { *list, *next });
×
783

784
                            std::ranges::copy(next->list(), std::back_inserter(list->list()));
70✔
785
                        }
70✔
786
                        DISPATCH();
40✔
787
                    }
4✔
788

789
                    TARGET(POP_LIST)
790
                    {
791
                        {
792
                            Value list = *popAndResolveAsPtr(context);
4✔
793
                            Value number = *popAndResolveAsPtr(context);
4✔
794

795
                            if (list.valueType() != ValueType::List || number.valueType() != ValueType::Number)
4✔
796
                                types::generateError(
×
797
                                    "pop",
×
798
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
799
                                    { list, number });
×
800

801
                            long idx = static_cast<long>(number.number());
4✔
802
                            idx = idx < 0 ? static_cast<long>(list.list().size()) + idx : idx;
4✔
803
                            if (std::cmp_greater_equal(idx, list.list().size()))
4✔
804
                                throwVMError(
1✔
805
                                    ErrorKind::Index,
806
                                    fmt::format("pop index ({}) out of range (list size: {})", idx, list.list().size()));
1✔
807

808
                            list.list().erase(list.list().begin() + idx);
3✔
809
                            push(list, context);
3✔
810
                        }
4✔
811
                        DISPATCH();
3✔
812
                    }
48✔
813

814
                    TARGET(POP_LIST_IN_PLACE)
815
                    {
816
                        {
817
                            Value* list = popAndResolveAsPtr(context);
48✔
818
                            Value number = *popAndResolveAsPtr(context);
48✔
819

820
                            if (list->valueType() != ValueType::List || number.valueType() != ValueType::Number)
48✔
821
                                types::generateError(
×
822
                                    "pop!",
×
823
                                    { { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("index", ValueType::Number) } } } },
×
824
                                    { *list, number });
×
825

826
                            long idx = static_cast<long>(number.number());
48✔
827
                            idx = idx < 0 ? static_cast<long>(list->list().size()) + idx : idx;
48✔
828
                            if (std::cmp_greater_equal(idx, list->list().size()))
48✔
829
                                throwVMError(
1✔
830
                                    ErrorKind::Index,
831
                                    fmt::format("pop! index ({}) out of range (list size: {})", idx, list->list().size()));
1✔
832

833
                            list->list().erase(list->list().begin() + idx);
47✔
834
                        }
48✔
835
                        DISPATCH();
47✔
836
                    }
488✔
837

838
                    TARGET(SET_AT_INDEX)
839
                    {
840
                        {
841
                            Value* list = popAndResolveAsPtr(context);
488✔
842
                            Value number = *popAndResolveAsPtr(context);
488✔
843
                            Value new_value = *popAndResolveAsPtr(context);
488✔
844

845
                            if (!list->isIndexable() || number.valueType() != ValueType::Number || (list->valueType() == ValueType::String && new_value.valueType() != ValueType::String))
488✔
846
                                types::generateError(
×
847
                                    "@=",
×
848
                                    { { types::Contract {
×
849
                                          { types::Typedef("list", ValueType::List),
×
850
                                            types::Typedef("index", ValueType::Number),
×
851
                                            types::Typedef("new_value", ValueType::Any) } } },
×
852
                                      { types::Contract {
×
853
                                          { types::Typedef("string", ValueType::String),
×
854
                                            types::Typedef("index", ValueType::Number),
×
855
                                            types::Typedef("char", ValueType::String) } } } },
×
856
                                    { *list, number });
×
857

858
                            const std::size_t size = list->valueType() == ValueType::List ? list->list().size() : list->stringRef().size();
488✔
859
                            long idx = static_cast<long>(number.number());
488✔
860
                            idx = idx < 0 ? static_cast<long>(size) + idx : idx;
488✔
861
                            if (std::cmp_greater_equal(idx, size))
488✔
862
                                throwVMError(
1✔
863
                                    ErrorKind::Index,
864
                                    fmt::format("@= index ({}) out of range (indexable size: {})", idx, size));
1✔
865

866
                            if (list->valueType() == ValueType::List)
487✔
867
                                list->list()[static_cast<std::size_t>(idx)] = new_value;
485✔
868
                            else
869
                                list->stringRef()[static_cast<std::size_t>(idx)] = new_value.string()[0];
2✔
870
                        }
488✔
871
                        DISPATCH();
487✔
872
                    }
8✔
873

874
                    TARGET(SET_AT_2_INDEX)
875
                    {
876
                        {
877
                            Value* list = popAndResolveAsPtr(context);
8✔
878
                            Value x = *popAndResolveAsPtr(context);
8✔
879
                            Value y = *popAndResolveAsPtr(context);
8✔
880
                            Value new_value = *popAndResolveAsPtr(context);
8✔
881

882
                            if (list->valueType() != ValueType::List || x.valueType() != ValueType::Number || y.valueType() != ValueType::Number)
8✔
883
                                types::generateError(
×
884
                                    "@@=",
×
885
                                    { { types::Contract {
×
886
                                        { types::Typedef("list", ValueType::List),
×
887
                                          types::Typedef("x", ValueType::Number),
×
888
                                          types::Typedef("y", ValueType::Number),
×
889
                                          types::Typedef("new_value", ValueType::Any) } } } },
×
890
                                    { *list, x, y });
×
891

892
                            long idx_y = static_cast<long>(x.number());
8✔
893
                            idx_y = idx_y < 0 ? static_cast<long>(list->list().size()) + idx_y : idx_y;
8✔
894
                            if (std::cmp_greater_equal(idx_y, list->list().size()))
8✔
895
                                throwVMError(
1✔
896
                                    ErrorKind::Index,
897
                                    fmt::format("@@= index (y: {}) out of range (list size: {})", idx_y, list->list().size()));
1✔
898

899
                            if (!list->list()[static_cast<std::size_t>(idx_y)].isIndexable() ||
11✔
900
                                (list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::String && new_value.valueType() != ValueType::String))
7✔
901
                                types::generateError(
×
902
                                    "@@=",
×
903
                                    { { types::Contract {
×
904
                                          { types::Typedef("list", ValueType::List),
×
905
                                            types::Typedef("x", ValueType::Number),
×
906
                                            types::Typedef("y", ValueType::Number),
×
907
                                            types::Typedef("new_value", ValueType::Any) } } },
×
908
                                      { types::Contract {
×
909
                                          { types::Typedef("string", ValueType::String),
×
910
                                            types::Typedef("x", ValueType::Number),
×
911
                                            types::Typedef("y", ValueType::Number),
×
912
                                            types::Typedef("char", ValueType::String) } } } },
×
913
                                    { *list, x, y });
×
914

915
                            const bool is_list = list->list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
7✔
916
                            const std::size_t size =
7✔
917
                                is_list
14✔
918
                                ? list->list()[static_cast<std::size_t>(idx_y)].list().size()
5✔
919
                                : list->list()[static_cast<std::size_t>(idx_y)].stringRef().size();
2✔
920

921
                            long idx_x = static_cast<long>(y.number());
7✔
922
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
7✔
923
                            if (std::cmp_greater_equal(idx_x, size))
7✔
924
                                throwVMError(
1✔
925
                                    ErrorKind::Index,
926
                                    fmt::format("@@= index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
927

928
                            if (is_list)
6✔
929
                                list->list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)] = new_value;
4✔
930
                            else
931
                                list->list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)] = new_value.string()[0];
2✔
932
                        }
8✔
933
                        DISPATCH();
6✔
934
                    }
2,017✔
935

936
                    TARGET(POP)
937
                    {
938
                        pop(context);
2,017✔
939
                        DISPATCH();
2,017✔
940
                    }
674✔
941

942
                    TARGET(DUP)
943
                    {
944
                        context.stack[context.sp] = context.stack[context.sp - 1];
674✔
945
                        ++context.sp;
674✔
946
                        DISPATCH();
674✔
947
                    }
326✔
948

949
                    TARGET(CREATE_SCOPE)
950
                    {
951
                        context.locals.emplace_back();
326✔
952
                        DISPATCH();
326✔
953
                    }
326✔
954

955
                    TARGET(POP_SCOPE)
956
                    {
957
                        context.locals.pop_back();
326✔
958
                        DISPATCH();
326✔
959
                    }
15,505✔
960

961
#pragma endregion
962

963
#pragma region "Operators"
964

965
                    TARGET(ADD)
966
                    {
967
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
15,505✔
968

969
                        if (a->valueType() == ValueType::Number && b->valueType() == ValueType::Number)
15,488✔
970
                            push(Value(a->number() + b->number()), context);
15,196✔
971
                        else if (a->valueType() == ValueType::String && b->valueType() == ValueType::String)
304✔
972
                            push(Value(a->string() + b->string()), context);
304✔
973
                        else
974
                            types::generateError(
×
975
                                "+",
×
976
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } },
×
977
                                    types::Contract { { types::Typedef("a", ValueType::String), types::Typedef("b", ValueType::String) } } } },
×
978
                                { *a, *b });
×
979
                        DISPATCH();
15,500✔
980
                    }
99✔
981

982
                    TARGET(SUB)
983
                    {
984
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
99✔
985

986
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
99✔
987
                            types::generateError(
×
988
                                "-",
×
989
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
990
                                { *a, *b });
×
991
                        push(Value(a->number() - b->number()), context);
99✔
992
                        DISPATCH();
99✔
993
                    }
1,309✔
994

995
                    TARGET(MUL)
996
                    {
997
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,309✔
998

999
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,309✔
1000
                            types::generateError(
×
1001
                                "*",
×
1002
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1003
                                { *a, *b });
×
1004
                        push(Value(a->number() * b->number()), context);
1,309✔
1005
                        DISPATCH();
1,309✔
1006
                    }
1,054✔
1007

1008
                    TARGET(DIV)
1009
                    {
1010
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
1,054✔
1011

1012
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
1,054✔
1013
                            types::generateError(
×
1014
                                "/",
×
1015
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1016
                                { *a, *b });
×
1017
                        auto d = b->number();
1,054✔
1018
                        if (d == 0)
1,054✔
1019
                            throwVMError(ErrorKind::DivisionByZero, fmt::format("Can not compute expression (/ {} {})", a->toString(*this), b->toString(*this)));
1✔
1020

1021
                        push(Value(a->number() / d), context);
1,053✔
1022
                        DISPATCH();
1,053✔
1023
                    }
172,484✔
1024

1025
                    TARGET(GT)
1026
                    {
1027
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
172,484✔
1028
                        push((*a != *b && !(*a < *b)) ? Builtins::trueSym : Builtins::falseSym, context);
172,484✔
1029
                        DISPATCH();
172,484✔
1030
                    }
28,309✔
1031

1032
                    TARGET(LT)
1033
                    {
1034
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
28,309✔
1035
                        push((*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
28,293✔
1036
                        DISPATCH();
28,297✔
1037
                    }
648✔
1038

1039
                    TARGET(LE)
1040
                    {
1041
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
648✔
1042
                        push((((*a < *b) || (*a == *b)) ? Builtins::trueSym : Builtins::falseSym), context);
648✔
1043
                        DISPATCH();
648✔
1044
                    }
283✔
1045

1046
                    TARGET(GE)
1047
                    {
1048
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
283✔
1049
                        push(!(*a < *b) ? Builtins::trueSym : Builtins::falseSym, context);
283✔
1050
                        DISPATCH();
283✔
1051
                    }
612✔
1052

1053
                    TARGET(NEQ)
1054
                    {
1055
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
612✔
1056
                        push((*a != *b) ? Builtins::trueSym : Builtins::falseSym, context);
612✔
1057
                        DISPATCH();
612✔
1058
                    }
87,785✔
1059

1060
                    TARGET(EQ)
1061
                    {
1062
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
87,785✔
1063
                        push((*a == *b) ? Builtins::trueSym : Builtins::falseSym, context);
87,785✔
1064
                        DISPATCH();
87,785✔
1065
                    }
2,023✔
1066

1067
                    TARGET(LEN)
1068
                    {
1069
                        Value* a = popAndResolveAsPtr(context);
2,023✔
1070

1071
                        if (a->valueType() == ValueType::List)
2,023✔
1072
                            push(Value(static_cast<int>(a->constList().size())), context);
1,820✔
1073
                        else if (a->valueType() == ValueType::String)
203✔
1074
                            push(Value(static_cast<int>(a->string().size())), context);
203✔
1075
                        else
1076
                            types::generateError(
×
1077
                                "len",
×
1078
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
1079
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1080
                                { *a });
×
1081
                        DISPATCH();
2,023✔
1082
                    }
390✔
1083

1084
                    TARGET(EMPTY)
1085
                    {
1086
                        Value* a = popAndResolveAsPtr(context);
390✔
1087

1088
                        if (a->valueType() == ValueType::List)
390✔
1089
                            push(a->constList().empty() ? Builtins::trueSym : Builtins::falseSym, context);
71✔
1090
                        else if (a->valueType() == ValueType::String)
319✔
1091
                            push(a->string().empty() ? Builtins::trueSym : Builtins::falseSym, context);
319✔
1092
                        else
1093
                            types::generateError(
×
1094
                                "empty?",
×
1095
                                { { types::Contract { { types::Typedef("value", ValueType::List) } },
×
1096
                                    types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1097
                                { *a });
×
1098
                        DISPATCH();
390✔
1099
                    }
334✔
1100

1101
                    TARGET(TAIL)
1102
                    {
1103
                        Value* a = popAndResolveAsPtr(context);
334✔
1104
                        push(helper::tail(a), context);
334✔
1105
                        DISPATCH();
334✔
1106
                    }
274✔
1107

1108
                    TARGET(HEAD)
1109
                    {
1110
                        Value* a = popAndResolveAsPtr(context);
274✔
1111
                        push(helper::head(a), context);
274✔
1112
                        DISPATCH();
274✔
1113
                    }
1,167✔
1114

1115
                    TARGET(ISNIL)
1116
                    {
1117
                        Value* a = popAndResolveAsPtr(context);
1,167✔
1118
                        push((*a == Builtins::nil) ? Builtins::trueSym : Builtins::falseSym, context);
1,167✔
1119
                        DISPATCH();
1,167✔
1120
                    }
66✔
1121

1122
                    TARGET(ASSERT)
1123
                    {
1124
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
66✔
1125

1126
                        if (b->valueType() != ValueType::String)
66✔
1127
                            types::generateError(
×
1128
                                "assert",
×
1129
                                { { types::Contract { { types::Typedef("expr", ValueType::Any), types::Typedef("message", ValueType::String) } } } },
×
1130
                                { *a, *b });
×
1131

1132
                        if (*a == Builtins::falseSym)
66✔
1133
                            throw AssertionFailed(b->stringRef());
×
1134
                        DISPATCH();
66✔
1135
                    }
13✔
1136

1137
                    TARGET(TO_NUM)
1138
                    {
1139
                        Value* a = popAndResolveAsPtr(context);
13✔
1140

1141
                        if (a->valueType() != ValueType::String)
13✔
1142
                            types::generateError(
×
1143
                                "toNumber",
×
1144
                                { { types::Contract { { types::Typedef("value", ValueType::String) } } } },
×
1145
                                { *a });
×
1146

1147
                        double val;
1148
                        if (Utils::isDouble(a->string(), &val))
13✔
1149
                            push(Value(val), context);
10✔
1150
                        else
1151
                            push(Builtins::nil, context);
3✔
1152
                        DISPATCH();
13✔
1153
                    }
16✔
1154

1155
                    TARGET(TO_STR)
1156
                    {
1157
                        Value* a = popAndResolveAsPtr(context);
16✔
1158
                        push(Value(a->toString(*this)), context);
16✔
1159
                        DISPATCH();
16✔
1160
                    }
4,940✔
1161

1162
                    TARGET(AT)
1163
                    {
1164
                        {
1165
                            Value* b = popAndResolveAsPtr(context);
4,940✔
1166
                            Value a = *popAndResolveAsPtr(context);  // be careful, it's not a pointer
4,936✔
1167

1168
                            if (b->valueType() != ValueType::Number)
4,952✔
1169
                                types::generateError(
×
1170
                                    "@",
×
1171
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
1172
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
1173
                                    { a, *b });
×
1174

1175
                            long idx = static_cast<long>(b->number());
4,952✔
1176

1177
                            if (a.valueType() == ValueType::List)
4,942✔
1178
                            {
1179
                                if (std::cmp_less(std::abs(idx), a.list().size()))
4,642✔
1180
                                    push(a.list()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.list().size()) + idx : idx)], context);
4,636✔
1181
                                else
1182
                                    throwVMError(
1✔
1183
                                        ErrorKind::Index,
1184
                                        fmt::format("{} out of range {} (length {})", idx, a.toString(*this), a.list().size()));
1✔
1185
                            }
4,641✔
1186
                            else if (a.valueType() == ValueType::String)
300✔
1187
                            {
1188
                                if (std::cmp_less(std::abs(idx), a.string().size()))
300✔
1189
                                    push(Value(std::string(1, a.string()[static_cast<std::size_t>(idx < 0 ? static_cast<long>(a.string().size()) + idx : idx)])), context);
299✔
1190
                                else
1191
                                    throwVMError(
1✔
1192
                                        ErrorKind::Index,
1193
                                        fmt::format("{} out of range \"{}\" (length {})", idx, a.string(), a.string().size()));
1✔
1194
                            }
299✔
1195
                            else
1196
                                types::generateError(
×
1197
                                    "@",
×
1198
                                    { { types::Contract { { types::Typedef("src", ValueType::List), types::Typedef("idx", ValueType::Number) } },
×
1199
                                        types::Contract { { types::Typedef("src", ValueType::String), types::Typedef("idx", ValueType::Number) } } } },
×
1200
                                    { a, *b });
×
1201
                        }
4,942✔
1202
                        DISPATCH();
4,940✔
1203
                    }
15✔
1204

1205
                    TARGET(AT_AT)
1206
                    {
1207
                        {
1208
                            Value* x = popAndResolveAsPtr(context);
15✔
1209
                            Value* y = popAndResolveAsPtr(context);
15✔
1210
                            Value list = *popAndResolveAsPtr(context);  // be careful, it's not a pointer
15✔
1211

1212
                            if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
15✔
1213
                                list.valueType() != ValueType::List)
15✔
1214
                                types::generateError(
×
1215
                                    "@@",
×
1216
                                    { { types::Contract {
×
1217
                                        { types::Typedef("src", ValueType::List),
×
1218
                                          types::Typedef("y", ValueType::Number),
×
1219
                                          types::Typedef("x", ValueType::Number) } } } },
×
1220
                                    { list, *y, *x });
×
1221

1222
                            long idx_y = static_cast<long>(y->number());
15✔
1223
                            idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
15✔
1224
                            if (std::cmp_greater_equal(idx_y, list.list().size()))
15✔
1225
                                throwVMError(
1✔
1226
                                    ErrorKind::Index,
1227
                                    fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));
1✔
1228

1229
                            const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
14✔
1230
                            const std::size_t size =
14✔
1231
                                is_list
28✔
1232
                                ? list.list()[static_cast<std::size_t>(idx_y)].list().size()
7✔
1233
                                : list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();
7✔
1234

1235
                            long idx_x = static_cast<long>(x->number());
14✔
1236
                            idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
14✔
1237
                            if (std::cmp_greater_equal(idx_x, size))
14✔
1238
                                throwVMError(
1✔
1239
                                    ErrorKind::Index,
1240
                                    fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));
1✔
1241

1242
                            if (is_list)
13✔
1243
                                push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
6✔
1244
                            else
1245
                                push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
7✔
1246
                        }
15✔
1247
                        DISPATCH();
13✔
1248
                    }
520✔
1249

1250
                    TARGET(MOD)
1251
                    {
1252
                        Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
520✔
1253
                        if (a->valueType() != ValueType::Number || b->valueType() != ValueType::Number)
520✔
1254
                            types::generateError(
×
1255
                                "mod",
×
1256
                                { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1257
                                { *a, *b });
×
1258
                        push(Value(std::fmod(a->number(), b->number())), context);
520✔
1259
                        DISPATCH();
520✔
1260
                    }
50✔
1261

1262
                    TARGET(TYPE)
1263
                    {
1264
                        Value* a = popAndResolveAsPtr(context);
50✔
1265
                        if (a == &m_undefined_value) [[unlikely]]
50✔
1266
                            types::generateError(
×
1267
                                "type",
×
1268
                                { { types::Contract { { types::Typedef("value", ValueType::Any) } } } },
×
1269
                                {});
×
1270

1271
                        push(Value(types_to_str[static_cast<unsigned>(a->valueType())]), context);
50✔
1272
                        DISPATCH();
50✔
1273
                    }
2✔
1274

1275
                    TARGET(HASFIELD)
1276
                    {
1277
                        {
1278
                            Value *field = popAndResolveAsPtr(context), *closure = popAndResolveAsPtr(context);
2✔
1279
                            if (closure->valueType() != ValueType::Closure || field->valueType() != ValueType::String)
2✔
1280
                                types::generateError(
×
1281
                                    "hasField",
×
1282
                                    { { types::Contract { { types::Typedef("closure", ValueType::Closure), types::Typedef("field", ValueType::String) } } } },
×
1283
                                    { *closure, *field });
×
1284

1285
                            auto it = std::find(m_state.m_symbols.begin(), m_state.m_symbols.end(), field->stringRef());
2✔
1286
                            if (it == m_state.m_symbols.end())
2✔
1287
                            {
1288
                                push(Builtins::falseSym, context);
1✔
1289
                                DISPATCH();
1✔
1290
                            }
1291

1292
                            auto id = static_cast<std::uint16_t>(std::distance(m_state.m_symbols.begin(), it));
1✔
1293
                            push(closure->refClosure().refScope()[id] != nullptr ? Builtins::trueSym : Builtins::falseSym, context);
1✔
1294
                        }
1295
                        DISPATCH();
1✔
1296
                    }
2,001✔
1297

1298
                    TARGET(NOT)
1299
                    {
1300
                        Value* a = popAndResolveAsPtr(context);
2,001✔
1301
                        push(!(*a) ? Builtins::trueSym : Builtins::falseSym, context);
2,001✔
1302
                        DISPATCH();
2,001✔
1303
                    }
681✔
1304

1305
#pragma endregion
1306

1307
#pragma region "Super Instructions"
1308
                    TARGET(LOAD_CONST_LOAD_CONST)
1309
                    {
1310
                        UNPACK_ARGS();
681✔
1311
                        push(loadConstAsPtr(primary_arg), context);
681✔
1312
                        push(loadConstAsPtr(secondary_arg), context);
681✔
1313
                        DISPATCH();
681✔
1314
                    }
1,201✔
1315

1316
                    TARGET(LOAD_CONST_STORE)
1317
                    {
1318
                        UNPACK_ARGS();
1,201✔
1319
                        store(secondary_arg, loadConstAsPtr(primary_arg), context);
1,201✔
1320
                        DISPATCH();
1,201✔
1321
                    }
76✔
1322

1323
                    TARGET(LOAD_CONST_SET_VAL)
1324
                    {
1325
                        UNPACK_ARGS();
76✔
1326
                        setVal(secondary_arg, loadConstAsPtr(primary_arg), context);
76✔
1327
                        DISPATCH();
75✔
1328
                    }
137✔
1329

1330
                    TARGET(STORE_FROM)
1331
                    {
1332
                        UNPACK_ARGS();
137✔
1333
                        store(secondary_arg, loadSymbol(primary_arg, context), context);
137✔
1334
                        DISPATCH();
136✔
1335
                    }
146✔
1336

1337
                    TARGET(SET_VAL_FROM)
1338
                    {
1339
                        UNPACK_ARGS();
146✔
1340
                        setVal(secondary_arg, loadSymbol(primary_arg, context), context);
146✔
1341
                        DISPATCH();
146✔
1342
                    }
96,232✔
1343

1344
                    TARGET(INCREMENT)
1345
                    {
1346
                        UNPACK_ARGS();
96,232✔
1347
                        {
1348
                            Value* var = loadSymbol(primary_arg, context);
96,232✔
1349

1350
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1351
                            if (var->valueType() == ValueType::Reference)
96,235✔
1352
                                var = var->reference();
×
1353

1354
                            if (var->valueType() == ValueType::Number)
96,235✔
1355
                                push(Value(var->number() + secondary_arg), context);
96,235✔
1356
                            else
1357
                                types::generateError(
×
1358
                                    "+",
×
1359
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1360
                                    { *var, Value(secondary_arg) });
×
1361
                        }
1362
                        DISPATCH();
96,235✔
1363
                    }
195,522✔
1364

1365
                    TARGET(DECREMENT)
1366
                    {
1367
                        UNPACK_ARGS();
195,522✔
1368
                        {
1369
                            Value* var = loadSymbol(primary_arg, context);
195,522✔
1370

1371
                            // use internal reference, shouldn't break anything so far, unless it's already a ref
1372
                            if (var->valueType() == ValueType::Reference)
195,522✔
1373
                                var = var->reference();
×
1374

1375
                            if (var->valueType() == ValueType::Number)
195,522✔
1376
                                push(Value(var->number() - secondary_arg), context);
195,522✔
1377
                            else
1378
                                types::generateError(
×
1379
                                    "-",
×
1380
                                    { { types::Contract { { types::Typedef("a", ValueType::Number), types::Typedef("b", ValueType::Number) } } } },
×
1381
                                    { *var, Value(secondary_arg) });
×
1382
                        }
1383
                        DISPATCH();
195,522✔
1384
                    }
1✔
1385

1386
                    TARGET(STORE_TAIL)
1387
                    {
1388
                        UNPACK_ARGS();
1✔
1389
                        {
1390
                            Value* list = loadSymbol(primary_arg, context);
1✔
1391
                            Value tail = helper::tail(list);
1✔
1392
                            store(secondary_arg, &tail, context);
1✔
1393
                        }
1✔
1394
                        DISPATCH();
1✔
1395
                    }
31✔
1396

1397
                    TARGET(STORE_HEAD)
1398
                    {
1399
                        UNPACK_ARGS();
31✔
1400
                        {
1401
                            Value* list = loadSymbol(primary_arg, context);
31✔
1402
                            Value head = helper::head(list);
31✔
1403
                            store(secondary_arg, &head, context);
31✔
1404
                        }
31✔
1405
                        DISPATCH();
31✔
1406
                    }
1✔
1407

1408
                    TARGET(SET_VAL_TAIL)
1409
                    {
1410
                        UNPACK_ARGS();
1✔
1411
                        {
1412
                            Value* list = loadSymbol(primary_arg, context);
1✔
1413
                            Value tail = helper::tail(list);
1✔
1414
                            setVal(secondary_arg, &tail, context);
1✔
1415
                        }
1✔
1416
                        DISPATCH();
1✔
1417
                    }
1✔
1418

1419
                    TARGET(SET_VAL_HEAD)
1420
                    {
1421
                        UNPACK_ARGS();
1✔
1422
                        {
1423
                            Value* list = loadSymbol(primary_arg, context);
1✔
1424
                            Value head = helper::head(list);
1✔
1425
                            setVal(secondary_arg, &head, context);
1✔
1426
                        }
1✔
1427
                        DISPATCH();
1✔
1428
                    }
590✔
1429

1430
                    TARGET(CALL_BUILTIN)
1431
                    {
1432
                        UNPACK_ARGS();
590✔
1433
                        // no stack size check because we do not push IP/PP since we are just calling a builtin
1434
                        callBuiltin(context, Builtins::builtins[primary_arg].second, secondary_arg);
590✔
1435
                        if (!m_running)
581✔
1436
                            GOTO_HALT();
×
1437
                        DISPATCH();
581✔
1438
                    }
1439
#pragma endregion
1440
                }
44✔
1441
#if ARK_USE_COMPUTED_GOTOS
1442
            dispatch_end:
1443
                do
44✔
1444
                {
1445
                } while (false);
44✔
1446
#endif
1447
            }
1448
        }
74✔
1449
        catch (const std::exception& e)
1450
        {
1451
            if (fail_with_exception)
30✔
1452
                throw;
30✔
1453

1454
            fmt::println("{}", e.what());
×
1455
            backtrace(context);
×
1456
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1457
            // don't report a "failed" exit code so that the fuzzers can more accurately triage crashes
1458
            m_exit_code = 0;
1459
#else
1460
            m_exit_code = 1;
×
1461
#endif
1462
        }
74✔
1463
        catch (...)
1464
        {
1465
            if (fail_with_exception)
×
1466
                throw;
×
1467

1468
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1469
            throw;
1470
#endif
1471
            fmt::println("Unknown error");
×
1472
            backtrace(context);
×
1473
            m_exit_code = 1;
×
1474
        }
60✔
1475

1476
        return m_exit_code;
44✔
1477
    }
282✔
1478

1479
    uint16_t VM::findNearestVariableIdWithValue(const Value& value, ExecutionContext& context) const noexcept
×
1480
    {
×
1481
        for (auto& local : std::ranges::reverse_view(context.locals))
×
1482
        {
1483
            if (const auto id = local.idFromValue(value); id < m_state.m_symbols.size())
×
1484
                return id;
×
1485
        }
×
1486
        return std::numeric_limits<uint16_t>::max();
×
1487
    }
×
1488

1489
    void VM::throwVMError(ErrorKind kind, const std::string& message)
22✔
1490
    {
22✔
1491
        throw std::runtime_error(std::string(errorKinds[static_cast<std::size_t>(kind)]) + ": " + message + "\n");
22✔
1492
    }
22✔
1493

1494
    void VM::backtrace(ExecutionContext& context) noexcept
×
1495
    {
×
1496
        const std::size_t saved_ip = context.ip;
×
1497
        const std::size_t saved_pp = context.pp;
×
1498
        const uint16_t saved_sp = context.sp;
×
1499

1500
        if (const uint16_t original_frame_count = context.fc; original_frame_count > 1)
×
1501
        {
1502
            // display call stack trace
1503
            const Scope old_scope = context.locals.back();
×
1504

1505
            while (context.fc != 0)
×
1506
            {
1507
                fmt::print("[{}] ", fmt::styled(context.fc, fmt::fg(fmt::color::cyan)));
×
1508
                if (context.pp != 0)
×
1509
                {
1510
                    const uint16_t id = findNearestVariableIdWithValue(
×
1511
                        Value(static_cast<PageAddr_t>(context.pp)),
×
1512
                        context);
×
1513

1514
                    if (id < m_state.m_symbols.size())
×
1515
                        fmt::println("In function `{}'", fmt::styled(m_state.m_symbols[id], fmt::fg(fmt::color::green)));
×
1516
                    else  // should never happen
1517
                        fmt::println("In function `{}'", fmt::styled("???", fmt::fg(fmt::color::gold)));
×
1518

1519
                    Value* ip;
×
1520
                    do
×
1521
                    {
1522
                        ip = popAndResolveAsPtr(context);
×
1523
                    } while (ip->valueType() != ValueType::InstPtr);
×
1524

1525
                    context.ip = ip->pageAddr();
×
1526
                    context.pp = pop(context)->pageAddr();
×
1527
                    returnFromFuncCall(context);
×
1528
                }
×
1529
                else
1530
                {
1531
                    fmt::println("In global scope");
×
1532
                    break;
×
1533
                }
1534

1535
                if (original_frame_count - context.fc > 7)
×
1536
                {
1537
                    fmt::println("...");
×
1538
                    break;
×
1539
                }
1540
            }
1541

1542
            // display variables values in the current scope
1543
            fmt::println("\nCurrent scope variables values:");
×
1544
            for (std::size_t i = 0, size = old_scope.size(); i < size; ++i)
×
1545
            {
1546
                fmt::println(
×
1547
                    "{} = {}",
×
1548
                    fmt::styled(m_state.m_symbols[old_scope.m_data[i].first], fmt::fg(fmt::color::cyan)),
×
1549
                    old_scope.m_data[i].second.toString(*this));
×
1550
            }
×
1551

1552
            while (context.fc != 1)
×
1553
            {
1554
                Value* tmp = pop(context);
×
1555
                if (tmp->valueType() == ValueType::InstPtr)
×
1556
                    --context.fc;
×
1557
                *tmp = m_no_value;
×
1558
            }
×
1559
            // pop the PP as well
1560
            pop(context);
×
1561
        }
×
1562

1563
        std::cerr << "At IP: " << (saved_ip / 4)  // dividing by 4 because the instructions are actually on 4 bytes
×
1564
                  << ", PP: " << saved_pp
×
1565
                  << ", SP: " << saved_sp
×
1566
                  << "\n";
×
1567
    }
×
1568
}
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