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

ArkScript-lang / Ark / 23209646190

17 Mar 2026 06:16PM UTC coverage: 93.659% (-0.05%) from 93.706%
23209646190

push

github

SuperFola
feat(vm): add a new super instruction CALL_SYMBOL_BY_INDEX

17 of 19 new or added lines in 3 files covered. (89.47%)

132 existing lines in 8 files now uncovered.

9601 of 10251 relevant lines covered (93.66%)

275534.05 hits per line

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

85.01
/src/arkreactor/Compiler/BytecodeReader.cpp
1
#include <Ark/Compiler/BytecodeReader.hpp>
2

3
#include <Ark/Compiler/Instructions.hpp>
4
#include <Ark/Builtins/Builtins.hpp>
5

6
#include <unordered_map>
7
#include <Proxy/Picosha2.hpp>
8
#include <Ark/Compiler/Serialization/IEEE754Serializer.hpp>
9
#include <Ark/Compiler/Serialization/IntegerSerializer.hpp>
10
#include <fmt/core.h>
11
#include <fmt/color.h>
12

13
namespace Ark
14
{
15
    using namespace Ark::internal;
16

17
    void BytecodeReader::feed(const bytecode_t& bytecode)
691✔
18
    {
691✔
19
        m_bytecode = bytecode;
691✔
20
    }
691✔
21

22
    void BytecodeReader::feed(const std::string& file)
1✔
23
    {
1✔
24
        std::ifstream ifs(file, std::ios::binary | std::ios::ate);
1✔
25
        if (!ifs.good())
1✔
26
            throw std::runtime_error(fmt::format("[BytecodeReader] Couldn't open file '{}'", file));
×
27

28
        const auto pos = ifs.tellg();
1✔
29
        // reserve appropriate number of bytes
30
        std::vector<char> temp(static_cast<std::size_t>(pos));
1✔
31
        ifs.seekg(0, std::ios::beg);
1✔
32
        ifs.read(&temp[0], pos);
1✔
33
        ifs.close();
1✔
34

35
        m_bytecode = bytecode_t(static_cast<std::size_t>(pos));
1✔
36
        for (std::size_t i = 0; i < static_cast<std::size_t>(pos); ++i)
194✔
37
            m_bytecode[i] = static_cast<uint8_t>(temp[i]);
193✔
38
    }
1✔
39

40
    bool BytecodeReader::checkMagic() const
2,747✔
41
    {
2,747✔
42
        return m_bytecode.size() >= bytecode::Magic.size() &&
5,493✔
43
            m_bytecode[0] == bytecode::Magic[0] &&
2,746✔
44
            m_bytecode[1] == bytecode::Magic[1] &&
2,358✔
45
            m_bytecode[2] == bytecode::Magic[2] &&
4,716✔
46
            m_bytecode[3] == bytecode::Magic[3];
2,358✔
47
    }
×
48

49
    Version BytecodeReader::version() const
286✔
50
    {
286✔
51
        if (!checkMagic() || m_bytecode.size() < bytecode::Magic.size() + bytecode::Version.size())
286✔
52
            return Version { 0, 0, 0 };
×
53

54
        return Version {
1,144✔
55
            .major = static_cast<uint16_t>((m_bytecode[4] << 8) + m_bytecode[5]),
286✔
56
            .minor = static_cast<uint16_t>((m_bytecode[6] << 8) + m_bytecode[7]),
286✔
57
            .patch = static_cast<uint16_t>((m_bytecode[8] << 8) + m_bytecode[9])
286✔
58
        };
59
    }
286✔
60

61
    unsigned long long BytecodeReader::timestamp() const
2✔
62
    {
2✔
63
        // 4 (ark\0) + version (2 bytes / number) + timestamp = 18 bytes
64
        if (!checkMagic() || m_bytecode.size() < bytecode::HeaderSize)
2✔
65
            return 0;
×
66

67
        // reading the timestamp in big endian
68
        using timestamp_t = unsigned long long;
×
69
        return (static_cast<timestamp_t>(m_bytecode[10]) << 56) +
6✔
70
            (static_cast<timestamp_t>(m_bytecode[11]) << 48) +
4✔
71
            (static_cast<timestamp_t>(m_bytecode[12]) << 40) +
4✔
72
            (static_cast<timestamp_t>(m_bytecode[13]) << 32) +
4✔
73
            (static_cast<timestamp_t>(m_bytecode[14]) << 24) +
4✔
74
            (static_cast<timestamp_t>(m_bytecode[15]) << 16) +
4✔
75
            (static_cast<timestamp_t>(m_bytecode[16]) << 8) +
4✔
76
            static_cast<timestamp_t>(m_bytecode[17]);
2✔
77
    }
2✔
78

79
    std::vector<unsigned char> BytecodeReader::sha256() const
285✔
80
    {
285✔
81
        if (!checkMagic() || m_bytecode.size() < bytecode::HeaderSize + picosha2::k_digest_size)
285✔
82
            return {};
×
83

84
        std::vector<unsigned char> sha(picosha2::k_digest_size);
285✔
85
        for (std::size_t i = 0; i < picosha2::k_digest_size; ++i)
9,405✔
86
            sha[i] = m_bytecode[bytecode::HeaderSize + i];
9,120✔
87
        return sha;
285✔
88
    }
570✔
89

90
    Symbols BytecodeReader::symbols() const
301✔
91
    {
301✔
92
        if (!checkMagic() || m_bytecode.size() < bytecode::HeaderSize + picosha2::k_digest_size ||
602✔
93
            m_bytecode[bytecode::HeaderSize + picosha2::k_digest_size] != SYM_TABLE_START)
301✔
94
            return {};
×
95

96
        std::size_t i = bytecode::HeaderSize + picosha2::k_digest_size + 1;
301✔
97
        const uint16_t size = readNumber(i);
301✔
98
        i++;
301✔
99

100
        Symbols block;
301✔
101
        block.start = bytecode::HeaderSize + picosha2::k_digest_size;
301✔
102
        block.symbols.reserve(size);
301✔
103

104
        for (uint16_t j = 0; j < size; ++j)
8,128✔
105
        {
106
            std::string content;
7,827✔
107
            while (m_bytecode[i] != 0)
89,930✔
108
                content.push_back(static_cast<char>(m_bytecode[i++]));
82,103✔
109
            i++;
7,827✔
110

111
            block.symbols.push_back(content);
7,827✔
112
        }
7,827✔
113

114
        block.end = i;
301✔
115
        return block;
301✔
116
    }
301✔
117

118
    Values BytecodeReader::values(const Symbols& symbols) const
301✔
119
    {
301✔
120
        if (!checkMagic())
301✔
121
            return {};
×
122

123
        std::size_t i = symbols.end;
301✔
124
        if (m_bytecode[i] != VAL_TABLE_START)
301✔
125
            return {};
×
126
        i++;
301✔
127

128
        const uint16_t size = readNumber(i);
301✔
129
        i++;
301✔
130
        Values block;
301✔
131
        block.start = symbols.end;
301✔
132
        block.values.reserve(size);
301✔
133

134
        for (uint16_t j = 0; j < size; ++j)
9,163✔
135
        {
136
            const uint8_t type = m_bytecode[i];
8,862✔
137
            i++;
8,862✔
138

139
            if (type == NUMBER_TYPE)
8,862✔
140
            {
141
                auto exp = deserializeLE<decltype(ieee754::DecomposedDouble::exponent)>(
2,206✔
142
                    m_bytecode.begin() + static_cast<std::vector<uint8_t>::difference_type>(i), m_bytecode.end());
1,103✔
143
                i += sizeof(decltype(exp));
1,103✔
144
                auto mant = deserializeLE<decltype(ieee754::DecomposedDouble::mantissa)>(
2,206✔
145
                    m_bytecode.begin() + static_cast<std::vector<uint8_t>::difference_type>(i), m_bytecode.end());
1,103✔
146
                i += sizeof(decltype(mant));
1,103✔
147

148
                const ieee754::DecomposedDouble d { exp, mant };
1,103✔
149
                double val = ieee754::deserialize(d);
1,103✔
150
                block.values.emplace_back(val);
1,103✔
151
            }
1,103✔
152
            else if (type == STRING_TYPE)
7,759✔
153
            {
154
                std::string val;
3,762✔
155
                while (m_bytecode[i] != 0)
71,234✔
156
                    val.push_back(static_cast<char>(m_bytecode[i++]));
67,472✔
157
                block.values.emplace_back(val);
3,762✔
158
            }
3,762✔
159
            else if (type == FUNC_TYPE)
3,997✔
160
            {
161
                const uint16_t addr = readNumber(i);
3,997✔
162
                i++;
3,997✔
163
                block.values.emplace_back(addr);
3,997✔
164
            }
3,997✔
165
            else
166
                throw std::runtime_error(fmt::format("Unknown value type: {:x}", type));
×
167
            i++;
8,862✔
168
        }
8,862✔
169

170
        block.end = i;
301✔
171
        return block;
301✔
172
    }
301✔
173

174
    Filenames BytecodeReader::filenames(const Ark::Values& values) const
299✔
175
    {
299✔
176
        if (!checkMagic())
299✔
177
            return {};
×
178

179
        std::size_t i = values.end;
299✔
180
        if (m_bytecode[i] != FILENAMES_TABLE_START)
299✔
181
            return {};
×
182
        i++;
299✔
183

184
        const uint16_t size = readNumber(i);
299✔
185
        i++;
299✔
186

187
        Filenames block;
299✔
188
        block.start = values.end;
299✔
189
        block.filenames.reserve(size);
299✔
190

191
        for (uint16_t j = 0; j < size; ++j)
745✔
192
        {
193
            std::string val;
446✔
194
            while (m_bytecode[i] != 0)
34,889✔
195
                val.push_back(static_cast<char>(m_bytecode[i++]));
34,443✔
196
            block.filenames.emplace_back(val);
446✔
197
            i++;
446✔
198
        }
446✔
199

200
        block.end = i;
299✔
201
        return block;
299✔
202
    }
299✔
203

204
    InstLocations BytecodeReader::instLocations(const Ark::Filenames& filenames) const
299✔
205
    {
299✔
206
        if (!checkMagic())
299✔
207
            return {};
×
208

209
        std::size_t i = filenames.end;
299✔
210
        if (m_bytecode[i] != INST_LOC_TABLE_START)
299✔
211
            return {};
×
212
        i++;
299✔
213

214
        const uint16_t size = readNumber(i);
299✔
215
        i++;
299✔
216

217
        InstLocations block;
299✔
218
        block.start = filenames.end;
299✔
219
        block.locations.reserve(size);
299✔
220

221
        for (uint16_t j = 0; j < size; ++j)
33,651✔
222
        {
223
            auto pp = readNumber(i);
33,352✔
224
            i++;
33,352✔
225

226
            auto ip = readNumber(i);
33,352✔
227
            i++;
33,352✔
228

229
            auto file_id = readNumber(i);
33,352✔
230
            i++;
33,352✔
231

232
            auto line = deserializeBE<uint32_t>(
66,704✔
233
                m_bytecode.begin() + static_cast<std::vector<uint8_t>::difference_type>(i), m_bytecode.end());
33,352✔
234
            i += 4;
33,352✔
235

236
            block.locations.push_back(
33,352✔
237
                { .page_pointer = pp,
133,408✔
238
                  .inst_pointer = ip,
33,352✔
239
                  .filename_id = file_id,
33,352✔
240
                  .line = line });
33,352✔
241
        }
33,352✔
242

243
        block.end = i;
299✔
244
        return block;
299✔
245
    }
299✔
246

247
    Code BytecodeReader::code(const InstLocations& instLocations) const
299✔
248
    {
299✔
249
        if (!checkMagic())
299✔
250
            return {};
×
251

252
        std::size_t i = instLocations.end;
299✔
253

254
        Code block;
299✔
255
        block.start = i;
299✔
256

257
        while (m_bytecode[i] == CODE_SEGMENT_START)
4,289✔
258
        {
259
            i++;
4,289✔
260
            const std::size_t size = readNumber(i) * 4;
4,289✔
261
            i++;
4,289✔
262

263
            block.pages.emplace_back().reserve(size);
4,289✔
264
            for (std::size_t j = 0; j < size; ++j)
484,425✔
265
                block.pages.back().push_back(m_bytecode[i++]);
480,136✔
266

267
            if (i == m_bytecode.size())
4,289✔
268
                break;
299✔
269
        }
4,289✔
270

271
        return block;
299✔
272
    }
299✔
273

274
    std::optional<InstLoc> BytecodeReader::findSourceLocation(const std::vector<InstLoc>& inst_locations, const std::size_t ip, const std::size_t pp) const
15✔
275
    {
15✔
276
        std::optional<InstLoc> match = std::nullopt;
15✔
277

278
        for (const auto location : inst_locations)
14,750✔
279
        {
280
            if (location.page_pointer == pp && !match)
14,735✔
281
                match = location;
15✔
282

283
            // select the best match: we want to find the location that's nearest our instruction pointer,
284
            // but not equal to it as the IP will always be pointing to the next instruction,
285
            // not yet executed. Thus, the erroneous instruction is the previous one.
286
            if (location.page_pointer == pp && match && location.inst_pointer < ip / 4)
14,735✔
287
                match = location;
10✔
288

289
            // early exit because we won't find anything better, as inst locations are ordered by ascending (pp, ip)
290
            if (location.page_pointer > pp || (location.page_pointer == pp && location.inst_pointer >= ip / 4))
14,735✔
291
                break;
10✔
292
        }
14,735✔
293

294
        return match;
15✔
295
    }
296

297
    void BytecodeReader::display(const BytecodeSegment segment,
2✔
298
                                 const std::optional<uint16_t> sStart,
299
                                 const std::optional<uint16_t> sEnd,
300
                                 const std::optional<uint16_t> cPage) const
301
    {
2✔
302
        if (!checkMagic())
2✔
303
        {
304
            fmt::println("Invalid format");
×
305
            return;
×
306
        }
307

308
        if (segment == BytecodeSegment::All || segment == BytecodeSegment::HeadersOnly)
2✔
309
        {
310
            auto [major, minor, patch] = version();
2✔
311
            fmt::println("Version:   {}.{}.{}", major, minor, patch);
2✔
312
            fmt::println("Timestamp: {}", timestamp());
1✔
313
            fmt::print("SHA256:    ");
1✔
314
            for (const auto sha = sha256(); unsigned char h : sha)
33✔
315
                fmt::print("{:02x}", h);
32✔
316
            fmt::print("\n\n");
1✔
317
        }
1✔
318

319
        // reading the different tables, one after another
320

321
        if ((sStart.has_value() && !sEnd.has_value()) || (!sStart.has_value() && sEnd.has_value()))
2✔
322
        {
323
            fmt::print(fmt::fg(fmt::color::red), "Both start and end parameter need to be provided together\n");
×
324
            return;
×
325
        }
326
        if (sStart.has_value() && sEnd.has_value() && sStart.value() >= sEnd.value())
2✔
327
        {
328
            fmt::print(fmt::fg(fmt::color::red), "Invalid slice start and end arguments\n");
×
329
            return;
×
330
        }
331

332
        const auto syms = symbols();
2✔
333
        const auto vals = values(syms);
2✔
334
        const auto files = filenames(vals);
2✔
335
        const auto inst_locs = instLocations(files);
2✔
336
        const auto code_block = code(inst_locs);
2✔
337

338
        // symbols table
339
        {
340
            std::size_t size = syms.symbols.size();
2✔
341
            std::size_t sliceSize = size;
2✔
342
            bool showSym = (segment == BytecodeSegment::All || segment == BytecodeSegment::Symbols);
2✔
343

344
            if (showSym && sStart.has_value() && sEnd.has_value() && (sStart.value() > size || sEnd.value() > size))
2✔
345
                fmt::print(fmt::fg(fmt::color::red), "Slice start or end can't be greater than the segment size: {}\n", size);
×
346
            else if (showSym && sStart.has_value() && sEnd.has_value())
2✔
347
                sliceSize = sEnd.value() - sStart.value() + 1;
×
348

349
            if (showSym || segment == BytecodeSegment::HeadersOnly)
2✔
350
                fmt::println("{} (length: {})", fmt::styled("Symbols table", fmt::fg(fmt::color::cyan)), sliceSize);
1✔
351

352
            for (std::size_t j = 0; j < size; ++j)
400✔
353
            {
354
                if (auto start = sStart; auto end = sEnd)
398✔
355
                    showSym = showSym && (j >= start.value() && j <= end.value());
×
356

357
                if (showSym)
398✔
358
                    fmt::println("{}) {}", j, syms.symbols[j]);
×
359
            }
398✔
360

361
            if (showSym)
2✔
362
                fmt::print("\n");
1✔
363
            if (segment == BytecodeSegment::Symbols)
2✔
364
                return;
×
365
        }
2✔
366

367
        // values table
368
        {
369
            std::size_t size = vals.values.size();
2✔
370
            std::size_t sliceSize = size;
2✔
371

372
            bool showVal = (segment == BytecodeSegment::All || segment == BytecodeSegment::Values);
2✔
373
            if (showVal && sStart.has_value() && sEnd.has_value() && (sStart.value() > size || sEnd.value() > size))
2✔
374
                fmt::print(fmt::fg(fmt::color::red), "Slice start or end can't be greater than the segment size: {}\n", size);
×
375
            else if (showVal && sStart.has_value() && sEnd.has_value())
2✔
376
                sliceSize = sEnd.value() - sStart.value() + 1;
×
377

378
            if (showVal || segment == BytecodeSegment::HeadersOnly)
2✔
379
                fmt::println("{} (length: {})", fmt::styled("Constants table", fmt::fg(fmt::color::cyan)), sliceSize);
1✔
380

381
            for (std::size_t j = 0; j < size; ++j)
1,129✔
382
            {
383
                if (auto start = sStart; auto end = sEnd)
1,127✔
384
                    showVal = showVal && (j >= start.value() && j <= end.value());
×
385

386
                if (showVal)
1,127✔
387
                {
388
                    switch (const auto val = vals.values[j]; val.valueType())
1✔
389
                    {
×
390
                        case ValueType::Number:
391
                            fmt::println("{}) (Number) {}", j, val.number());
×
392
                            break;
1✔
393
                        case ValueType::String:
394
                            fmt::println("{}) (String) {}", j, val.string());
1✔
395
                            break;
1✔
396
                        case ValueType::PageAddr:
397
                            fmt::println("{}) (PageAddr) {}", j, val.pageAddr());
×
398
                            break;
×
399
                        default:
400
                            fmt::print(fmt::fg(fmt::color::red), "Value type not handled: {}\n", std::to_string(val.valueType()));
×
401
                            break;
×
402
                    }
1✔
403
                }
1✔
404
            }
1,127✔
405

406
            if (showVal)
2✔
407
                fmt::print("\n");
1✔
408
            if (segment == BytecodeSegment::Values)
2✔
409
                return;
×
410
        }
2✔
411

412
        // inst locs + file
413
        {
414
            std::size_t size = inst_locs.locations.size();
2✔
415
            std::size_t sliceSize = size;
2✔
416

417
            bool showVal = (segment == BytecodeSegment::All || segment == BytecodeSegment::InstructionLocation);
2✔
418
            if (showVal && sStart.has_value() && sEnd.has_value() && (sStart.value() > size || sEnd.value() > size))
2✔
419
                fmt::print(fmt::fg(fmt::color::red), "Slice start or end can't be greater than the segment size: {}\n", size);
×
420
            else if (showVal && sStart.has_value() && sEnd.has_value())
2✔
421
                sliceSize = sEnd.value() - sStart.value() + 1;
×
422

423
            if (showVal || segment == BytecodeSegment::HeadersOnly)
2✔
424
                fmt::println("{} (length: {})", fmt::styled("Instruction locations table", fmt::fg(fmt::color::cyan)), sliceSize);
1✔
425
            if (showVal && size > 0)
2✔
426
                fmt::println(" PP, IP");
1✔
427

428
            for (std::size_t j = 0; j < size; ++j)
4,457✔
429
            {
430
                if (auto start = sStart; auto end = sEnd)
4,455✔
431
                    showVal = showVal && (j >= start.value() && j <= end.value());
×
432

433
                const auto& location = inst_locs.locations[j];
4,455✔
434
                if (showVal)
4,455✔
435
                    fmt::println("{:>3},{:>3} -> {}:{}", location.page_pointer, location.inst_pointer, files.filenames[location.filename_id], location.line);
1✔
436
            }
4,455✔
437

438
            if (showVal)
2✔
439
                fmt::print("\n");
1✔
440
        }
2✔
441

442
        const auto stringify_value = [](const Value& val) -> std::string {
3✔
443
            switch (val.valueType())
1✔
444
            {
×
445
                case ValueType::Number:
446
                    return fmt::format("{} (Number)", val.number());
1✔
447
                case ValueType::String:
448
                    return fmt::format("{} (String)", val.string());
1✔
449
                case ValueType::PageAddr:
450
                    return fmt::format("{} (PageAddr)", val.pageAddr());
×
451
                default:
452
                    return "";
×
453
            }
454
        };
1✔
455

456
        enum class ArgKind
457
        {
458
            Symbol,
459
            Constant,
460
            Builtin,
461
            Raw,  ///< eg: Stack index, jump address, number
462
            RawHex,
463
            ConstConst,
464
            ConstSym,
465
            SymConst,
466
            SymSym,
467
            BuiltinRaw,  ///< Builtin, number
468
            ConstRaw,    ///< Constant, number
469
            SymRaw,      ///< Symbol, number
470
            RawSym,      ///< Symbol index, symbol
471
            RawConst,    ///< Symbol index, constant
472
            RawRaw,      ///< Symbol index, symbol index
473
            RawRawRaw
474
        };
475

476
        struct Arg
477
        {
478
            ArgKind kind;
479
            uint8_t padding;
480
            uint16_t arg;
481

482
            [[nodiscard]] uint16_t primary() const
1✔
483
            {
1✔
484
                return arg & 0x0fff;
1✔
485
            }
486

487
            [[nodiscard]] uint16_t secondary() const
1✔
488
            {
1✔
489
                return static_cast<uint16_t>((padding << 4) | (arg & 0xf000) >> 12);
1✔
490
            }
491
        };
492

493
        const std::unordered_map<Instruction, ArgKind> arg_kinds = {
2✔
494
            { LOAD_FAST, ArgKind::Symbol },
495
            { LOAD_FAST_BY_INDEX, ArgKind::Raw },
496
            { LOAD_SYMBOL, ArgKind::Symbol },
497
            { LOAD_CONST, ArgKind::Constant },
498
            { POP_JUMP_IF_TRUE, ArgKind::Raw },
499
            { STORE, ArgKind::Symbol },
500
            { STORE_REF, ArgKind::Symbol },
501
            { SET_VAL, ArgKind::Symbol },
502
            { POP_JUMP_IF_FALSE, ArgKind::Raw },
503
            { JUMP, ArgKind::Raw },
504
            { PUSH_RETURN_ADDRESS, ArgKind::RawHex },
505
            { CALL, ArgKind::Raw },
506
            { CAPTURE, ArgKind::Symbol },
507
            { RENAME_NEXT_CAPTURE, ArgKind::Symbol },
508
            { BUILTIN, ArgKind::Builtin },
509
            { DEL, ArgKind::Symbol },
510
            { MAKE_CLOSURE, ArgKind::Constant },
511
            { GET_FIELD, ArgKind::Symbol },
512
            { PLUGIN, ArgKind::Constant },
513
            { LIST, ArgKind::Raw },
514
            { APPEND, ArgKind::Raw },
515
            { CONCAT, ArgKind::Raw },
516
            { APPEND_IN_PLACE, ArgKind::Raw },
517
            { CONCAT_IN_PLACE, ArgKind::Raw },
518
            { POP_LIST, ArgKind::Raw },
519
            { POP_LIST_IN_PLACE, ArgKind::Raw },
520
            { SET_AT_INDEX, ArgKind::Raw },
521
            { SET_AT_2_INDEX, ArgKind::Raw },
522
            { RESET_SCOPE_JUMP, ArgKind::Raw },
523
            { LOAD_CONST_LOAD_CONST, ArgKind::ConstConst },
524
            { LOAD_CONST_STORE, ArgKind::ConstSym },
525
            { LOAD_CONST_SET_VAL, ArgKind::ConstSym },
526
            { STORE_FROM, ArgKind::SymSym },
527
            { STORE_FROM_INDEX, ArgKind::RawSym },
528
            { SET_VAL_FROM, ArgKind::SymSym },
529
            { SET_VAL_FROM_INDEX, ArgKind::RawSym },
530
            { INCREMENT, ArgKind::SymRaw },
531
            { INCREMENT_BY_INDEX, ArgKind::RawRaw },
532
            { INCREMENT_STORE, ArgKind::RawRaw },
533
            { DECREMENT, ArgKind::SymRaw },
534
            { DECREMENT_BY_INDEX, ArgKind::RawRaw },
535
            { DECREMENT_STORE, ArgKind::SymRaw },
536
            { STORE_TAIL, ArgKind::SymSym },
537
            { STORE_TAIL_BY_INDEX, ArgKind::RawSym },
538
            { STORE_HEAD, ArgKind::SymSym },
539
            { STORE_HEAD_BY_INDEX, ArgKind::RawSym },
540
            { STORE_LIST, ArgKind::RawSym },
541
            { SET_VAL_TAIL, ArgKind::SymSym },
542
            { SET_VAL_TAIL_BY_INDEX, ArgKind::RawSym },
543
            { SET_VAL_HEAD, ArgKind::SymSym },
544
            { SET_VAL_HEAD_BY_INDEX, ArgKind::RawSym },
545
            { CALL_BUILTIN, ArgKind::BuiltinRaw },
546
            { CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, ArgKind::BuiltinRaw },
547
            { LT_CONST_JUMP_IF_FALSE, ArgKind::ConstRaw },
548
            { LT_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw },
549
            { LT_SYM_JUMP_IF_FALSE, ArgKind::SymRaw },
550
            { GT_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw },
551
            { GT_CONST_JUMP_IF_FALSE, ArgKind::ConstRaw },
552
            { GT_SYM_JUMP_IF_FALSE, ArgKind::SymRaw },
553
            { EQ_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw },
554
            { EQ_SYM_INDEX_JUMP_IF_TRUE, ArgKind::SymRaw },
555
            { NEQ_CONST_JUMP_IF_TRUE, ArgKind::ConstRaw },
556
            { NEQ_SYM_JUMP_IF_FALSE, ArgKind::SymRaw },
557
            { CALL_SYMBOL, ArgKind::SymRaw },
558
            { CALL_SYMBOL_BY_INDEX, ArgKind::RawRaw },
559
            { CALL_CURRENT_PAGE, ArgKind::SymRaw },
560
            { GET_FIELD_FROM_SYMBOL, ArgKind::SymSym },
561
            { GET_FIELD_FROM_SYMBOL_INDEX, ArgKind::RawSym },
562
            { AT_SYM_SYM, ArgKind::SymSym },
563
            { AT_SYM_INDEX_SYM_INDEX, ArgKind::RawRaw },
564
            { AT_SYM_INDEX_CONST, ArgKind::RawConst },
565
            { CHECK_TYPE_OF, ArgKind::SymConst },
566
            { CHECK_TYPE_OF_BY_INDEX, ArgKind::RawConst },
567
            { APPEND_IN_PLACE_SYM, ArgKind::SymRaw },
568
            { APPEND_IN_PLACE_SYM_INDEX, ArgKind::RawRaw },
569
            { STORE_LEN, ArgKind::RawSym },
570
            { LT_LEN_SYM_JUMP_IF_FALSE, ArgKind::SymRaw },
571
            { MUL_BY, ArgKind::RawRaw },
572
            { MUL_BY_INDEX, ArgKind::RawRaw },
573
            { MUL_SET_VAL, ArgKind::RawRaw },
574
            { FUSED_MATH, ArgKind::RawRawRaw }
575
        };
576

577
        const auto builtin_name = [](const uint16_t idx) {
4✔
578
            return Builtins::builtins[idx].first;
2✔
579
        };
580
        const auto value_str = [&stringify_value, &vals](const uint16_t idx) {
3✔
581
            return stringify_value(vals.values[idx]);
1✔
582
        };
583
        const auto symbol_name = [&syms](const uint16_t idx) {
5✔
584
            return syms.symbols[idx];
3✔
585
        };
586

587
        const auto color_print_inst = [=](const std::string& name, std::optional<Arg> arg = std::nullopt) {
17✔
588
            fmt::print("{}", fmt::styled(name, fmt::fg(fmt::color::gold)));
15✔
589
            if (arg.has_value())
15✔
590
            {
591
                constexpr auto sym_color = fmt::fg(fmt::color::green);
11✔
592
                constexpr auto const_color = fmt::fg(fmt::color::magenta);
11✔
593
                constexpr auto raw_color = fmt::fg(fmt::color::red);
11✔
594

595
                switch (auto [kind, _, idx] = arg.value(); kind)
16✔
596
                {
3✔
597
                    case ArgKind::Symbol:
598
                        fmt::print(sym_color, " {}\n", symbol_name(idx));
3✔
599
                        break;
4✔
600
                    case ArgKind::Constant:
601
                        fmt::print(const_color, " {}\n", value_str(idx));
1✔
602
                        break;
2✔
603
                    case ArgKind::Builtin:
604
                        fmt::print(" {}\n", builtin_name(idx));
1✔
605
                        break;
4✔
606
                    case ArgKind::Raw:
607
                        fmt::print(raw_color, " ({})\n", idx);
6✔
608
                        break;
4✔
609
                    case ArgKind::RawHex:
610
                        fmt::print(raw_color, " ({:#x})\n", idx);
2✔
611
                        break;
1✔
612
                    case ArgKind::ConstConst:
613
                        fmt::print(" {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(value_str(arg->secondary()), const_color));
×
UNCOV
614
                        break;
×
615
                    case ArgKind::ConstSym:
616
                        fmt::print(" {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(symbol_name(arg->secondary()), sym_color));
×
UNCOV
617
                        break;
×
618
                    case ArgKind::SymConst:
UNCOV
619
                        fmt::print(" {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(value_str(arg->secondary()), const_color));
×
UNCOV
620
                        break;
×
621
                    case ArgKind::SymSym:
UNCOV
622
                        fmt::print(" {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(symbol_name(arg->secondary()), sym_color));
×
623
                        break;
1✔
624
                    case ArgKind::BuiltinRaw:
625
                        fmt::print(" {}, {}\n", builtin_name(arg->primary()), fmt::styled(arg->secondary(), raw_color));
1✔
626
                        break;
1✔
627
                    case ArgKind::ConstRaw:
628
                        fmt::print(" {}, {}\n", fmt::styled(value_str(arg->primary()), const_color), fmt::styled(arg->secondary(), raw_color));
×
UNCOV
629
                        break;
×
630
                    case ArgKind::SymRaw:
631
                        fmt::print(" {}, {}\n", fmt::styled(symbol_name(arg->primary()), sym_color), fmt::styled(arg->secondary(), raw_color));
×
UNCOV
632
                        break;
×
633
                    case ArgKind::RawSym:
634
                        fmt::print(" {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(symbol_name(arg->secondary()), sym_color));
×
UNCOV
635
                        break;
×
636
                    case ArgKind::RawConst:
UNCOV
637
                        fmt::print(" {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(value_str(arg->secondary()), const_color));
×
UNCOV
638
                        break;
×
639
                    case ArgKind::RawRaw:
UNCOV
640
                        fmt::print(" {}, {}\n", fmt::styled(arg->primary(), raw_color), fmt::styled(arg->secondary(), raw_color));
×
641
                        break;
1✔
642
                    case ArgKind::RawRawRaw:
643
                        fmt::print(" {}, {}, {}\n", fmt::styled(arg->padding, raw_color), fmt::styled((arg->arg & 0xff00) >> 8, raw_color), fmt::styled(arg->arg & 0x00ff, raw_color));
1✔
644
                        break;
1✔
645
                }
11✔
646
            }
11✔
647
            else
648
                fmt::print("\n");
4✔
649
        };
15✔
650

651
        if (segment == BytecodeSegment::All || segment == BytecodeSegment::Code || segment == BytecodeSegment::HeadersOnly)
2✔
652
        {
653
            uint16_t pp = 0;
2✔
654

655
            for (const auto& page : code_block.pages)
201✔
656
            {
657
                bool displayCode = true;
199✔
658

659
                if (auto wanted_page = cPage)
397✔
660
                    displayCode = pp == wanted_page.value();
198✔
661

662
                if (displayCode)
199✔
663
                    fmt::println(
2✔
664
                        "{} {} (length: {})",
2✔
665
                        fmt::styled("Code segment", fmt::fg(fmt::color::magenta)),
2✔
666
                        fmt::styled(pp, fmt::fg(fmt::color::magenta)),
2✔
667
                        page.size());
2✔
668

669
                if (page.empty())
199✔
670
                {
UNCOV
671
                    if (displayCode)
×
UNCOV
672
                        fmt::print("NOP");
×
UNCOV
673
                }
×
674
                else if (cPage.value_or(pp) == pp && segment != BytecodeSegment::HeadersOnly)
199✔
675
                {
676
                    if (sStart.has_value() && sEnd.has_value() && ((sStart.value() > page.size()) || (sEnd.value() > page.size())))
2✔
677
                    {
UNCOV
678
                        fmt::print(fmt::fg(fmt::color::red), "Slice start or end can't be greater than the segment size: {}\n", page.size());
×
UNCOV
679
                        return;
×
680
                    }
681

682
                    std::optional<InstLoc> previous_loc = std::nullopt;
2✔
683

684
                    for (std::size_t j = sStart.value_or(0), end = sEnd.value_or(page.size()); j < end; j += 4)
17✔
685
                    {
686
                        const uint8_t inst = page[j];
15✔
687
                        const uint8_t padding = page[j + 1];
15✔
688
                        const auto arg = static_cast<uint16_t>((page[j + 2] << 8) + page[j + 3]);
15✔
689

690
                        auto maybe_loc = findSourceLocation(inst_locs.locations, j, pp);
15✔
691

692
                        // location
693
                        // we want to print it only when it changed, either the file, the line, or both
694
                        if (maybe_loc && (!previous_loc || maybe_loc != previous_loc))
15✔
695
                        {
696
                            if (!previous_loc || previous_loc->filename_id != maybe_loc->filename_id)
2✔
697
                                fmt::println("{}", files.filenames[maybe_loc->filename_id]);
2✔
698
                            fmt::print("{:>4}", maybe_loc->line + 1);
2✔
699
                            previous_loc = maybe_loc;
2✔
700
                        }
2✔
701
                        else
702
                            fmt::print("    ");
13✔
703
                        // instruction number
704
                        fmt::print(fmt::fg(fmt::color::cyan), "{:>4x}", j / 4);
15✔
705
                        // padding inst arg arg
706
                        fmt::print(" {:02x} {:02x} {:02x} {:02x} ", inst, padding, page[j + 2], page[j + 3]);
15✔
707

708
                        if (const auto idx = static_cast<std::size_t>(inst); idx < InstructionNames.size())
30✔
709
                        {
710
                            const auto inst_name = InstructionNames[idx];
15✔
711
                            if (const auto iinst = static_cast<Instruction>(inst); arg_kinds.contains(iinst))
30✔
712
                                color_print_inst(inst_name, Arg { arg_kinds.at(iinst), padding, arg });
11✔
713
                            else
714
                                color_print_inst(inst_name);
4✔
715
                        }
15✔
716
                        else
UNCOV
717
                            fmt::println("Unknown instruction");
×
718
                    }
15✔
719
                }
2✔
720
                if (displayCode && segment != BytecodeSegment::HeadersOnly)
199✔
721
                    fmt::print("\n");
2✔
722

723
                ++pp;
199✔
724
            }
199✔
725
        }
2✔
726
    }
2✔
727

728
    uint16_t BytecodeReader::readNumber(std::size_t& i) const
109,542✔
729
    {
109,542✔
730
        const auto x = static_cast<uint16_t>(m_bytecode[i] << 8);
109,542✔
731
        const uint16_t y = m_bytecode[++i];
109,542✔
732
        return x + y;
219,084✔
733
    }
109,542✔
734
}
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