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

ArkScript-lang / Ark / 14647441324

24 Apr 2025 05:07PM UTC coverage: 83.165% (+2.7%) from 80.417%
14647441324

push

github

SuperFola
feat(vm): compressing identical traces when displaying the stacktrace of an error

21 of 21 new or added lines in 1 file covered. (100.0%)

270 existing lines in 10 files now uncovered.

6580 of 7912 relevant lines covered (83.16%)

79046.5 hits per line

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

94.31
/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp
1
#include <Ark/Compiler/IntermediateRepresentation/IRCompiler.hpp>
2

3
#include <chrono>
4
#include <utility>
5
#include <optional>
6
#include <unordered_map>
7
#include <Proxy/Picosha2.hpp>
8
#include <fmt/ostream.h>
9

10
#include <Ark/Constants.hpp>
11
#include <Ark/Literals.hpp>
12
#include <Ark/Compiler/IntermediateRepresentation/InstLoc.hpp>
13
#include <Ark/Compiler/Serialization/IntegerSerializer.hpp>
14
#include <Ark/Compiler/Serialization/IEEE754Serializer.hpp>
15

16
namespace Ark::internal
17
{
18
    using namespace literals;
19

20
    IRCompiler::IRCompiler(const unsigned debug) :
454✔
21
        m_logger("IRCompiler", debug)
227✔
22
    {}
454✔
23

24
    void IRCompiler::process(const std::vector<IR::Block>& pages, const std::vector<std::string>& symbols, const std::vector<ValTableElem>& values)
123✔
25
    {
123✔
26
        m_logger.traceStart("process");
123✔
27
        pushFileHeader();
123✔
28
        pushSymbolTable(symbols);
123✔
29
        pushValueTable(values);
123✔
30

31
        // compute a list of unique filenames
32
        for (const auto& page : pages)
1,527✔
33
        {
34
            for (const auto& inst : page)
42,227✔
35
            {
36
                if (std::ranges::find(m_filenames, inst.filename()) == m_filenames.end() && inst.hasValidSourceLocation())
40,823✔
37
                    m_filenames.push_back(inst.filename());
210✔
38
            }
40,823✔
39
        }
1,404✔
40

41
        pushFilenameTable();
123✔
42
        pushInstLocTable(pages);
123✔
43

44
        m_ir = pages;
123✔
45
        compile();
123✔
46

47
        if (m_ir.empty())
123✔
48
        {
49
            // code segment with a single instruction
UNCOV
50
            m_bytecode.push_back(CODE_SEGMENT_START);
×
UNCOV
51
            m_bytecode.push_back(0_u8);
×
UNCOV
52
            m_bytecode.push_back(1_u8);
×
53

UNCOV
54
            m_bytecode.push_back(0_u8);
×
UNCOV
55
            m_bytecode.push_back(HALT);
×
UNCOV
56
            m_bytecode.push_back(0_u8);
×
UNCOV
57
            m_bytecode.push_back(0_u8);
×
UNCOV
58
        }
×
59

60
        // generate a hash of the tables + bytecode
61
        std::vector<unsigned char> hash_out(picosha2::k_digest_size);
123✔
62
        picosha2::hash256(m_bytecode.begin() + bytecode::HeaderSize, m_bytecode.end(), hash_out);
123✔
63
        m_bytecode.insert(m_bytecode.begin() + bytecode::HeaderSize, hash_out.begin(), hash_out.end());
123✔
64

65
        m_logger.traceEnd();
123✔
66
    }
123✔
67

68
    void IRCompiler::dumpToStream(std::ostream& stream) const
11✔
69
    {
11✔
70
        std::size_t index = 0;
11✔
71
        for (const auto& block : m_ir)
37✔
72
        {
73
            fmt::println(stream, "page_{}", index);
26✔
74
            for (const auto& entity : block)
498✔
75
            {
76
                switch (entity.kind())
472✔
77
                {
24✔
78
                    case IR::Kind::Label:
79
                        fmt::println(stream, ".L{}:", entity.label());
24✔
80
                        break;
36✔
81

82
                    case IR::Kind::Goto:
83
                        fmt::println(stream, "\tGOTO L{}", entity.label());
12✔
84
                        break;
20✔
85

86
                    case IR::Kind::GotoIfTrue:
87
                        fmt::println(stream, "\tGOTO_IF_TRUE L{}", entity.label());
8✔
88
                        break;
12✔
89

90
                    case IR::Kind::GotoIfFalse:
91
                        fmt::println(stream, "\tGOTO_IF_FALSE L{}", entity.label());
4✔
92
                        break;
378✔
93

94
                    case IR::Kind::Opcode:
95
                        fmt::println(stream, "\t{} {}", InstructionNames[entity.inst()], entity.primaryArg());
374✔
96
                        break;
424✔
97

98
                    case IR::Kind::Opcode2Args:
99
                        fmt::println(stream, "\t{} {}, {}", InstructionNames[entity.inst()], entity.primaryArg(), entity.secondaryArg());
50✔
100
                        break;
50✔
101
                }
472✔
102
            }
472✔
103

104
            fmt::println(stream, "");
26✔
105
            ++index;
26✔
106
        }
26✔
107
    }
11✔
108

109
    const bytecode_t& IRCompiler::bytecode() const noexcept
123✔
110
    {
123✔
111
        return m_bytecode;
123✔
112
    }
113

114
    void IRCompiler::compile()
123✔
115
    {
123✔
116
        // push the different code segments
117
        for (std::size_t i = 0, end = m_ir.size(); i < end; ++i)
1,527✔
118
        {
119
            IR::Block& page = m_ir[i];
1,404✔
120
            // just in case we got too far, always add a HALT to be sure the
121
            // VM won't do anything crazy
122
            page.emplace_back(HALT);
1,404✔
123

124
            // push number of elements
125
            const auto page_size = std::ranges::count_if(page, [](const auto& a) {
43,631✔
126
                return a.kind() != IR::Kind::Label;
42,227✔
127
            });
128
            if (std::cmp_greater(page_size, std::numeric_limits<uint16_t>::max()))
1,404✔
UNCOV
129
                throw std::overflow_error(fmt::format("Size of page {} exceeds the maximum size of 2^16 - 1", i));
×
130

131
            m_bytecode.push_back(CODE_SEGMENT_START);
1,404✔
132
            serializeOn2BytesToVecBE(page_size, m_bytecode);
1,404✔
133

134
            // register labels position
135
            uint16_t pos = 0;
1,404✔
136
            std::unordered_map<IR::label_t, uint16_t> label_to_position;
1,404✔
137
            for (auto& inst : page)
43,631✔
138
            {
139
                switch (inst.kind())
42,227✔
140
                {
3,124✔
141
                    case IR::Kind::Label:
142
                        label_to_position[inst.label()] = pos;
3,124✔
143
                        break;
42,227✔
144

145
                    default:
146
                        ++pos;
39,103✔
147
                }
42,227✔
148
            }
42,227✔
149

150
            for (auto& inst : page)
43,631✔
151
            {
152
                switch (inst.kind())
42,227✔
153
                {
1,499✔
154
                    case IR::Kind::Goto:
155
                        pushWord(Word(JUMP, label_to_position[inst.label()]));
1,499✔
156
                        break;
2,515✔
157

158
                    case IR::Kind::GotoIfTrue:
159
                        pushWord(Word(POP_JUMP_IF_TRUE, label_to_position[inst.label()]));
1,016✔
160
                        break;
1,634✔
161

162
                    case IR::Kind::GotoIfFalse:
163
                        pushWord(Word(POP_JUMP_IF_FALSE, label_to_position[inst.label()]));
618✔
164
                        break;
36,588✔
165

166
                    case IR::Kind::Opcode:
167
                        [[fallthrough]];
168
                    case IR::Kind::Opcode2Args:
169
                        pushWord(inst.bytecode());
35,970✔
170
                        break;
39,094✔
171

172
                    default:
173
                        break;
3,124✔
174
                }
42,227✔
175
            }
42,227✔
176
        }
1,404✔
177
    }
123✔
178

179
    void IRCompiler::pushWord(const Word& word)
39,103✔
180
    {
39,103✔
181
        m_bytecode.push_back(word.opcode);
39,103✔
182
        m_bytecode.push_back(word.byte_1);
39,103✔
183
        m_bytecode.push_back(word.byte_2);
39,103✔
184
        m_bytecode.push_back(word.byte_3);
39,103✔
185
    }
39,103✔
186

187
    void IRCompiler::pushFileHeader() noexcept
123✔
188
    {
123✔
189
        /*
190
            Generating headers:
191
                - lang name (to be sure we are executing an ArkScript file)
192
                    on 4 bytes (ark + padding)
193
                - version (major: 2 bytes, minor: 2 bytes, patch: 2 bytes)
194
                - timestamp (8 bytes, unix format)
195
        */
196

197
        m_bytecode.push_back('a');
123✔
198
        m_bytecode.push_back('r');
123✔
199
        m_bytecode.push_back('k');
123✔
200
        m_bytecode.push_back(0_u8);
123✔
201

202
        // push version
203
        for (const int n : std::array { ARK_VERSION_MAJOR, ARK_VERSION_MINOR, ARK_VERSION_PATCH })
492✔
204
            serializeOn2BytesToVecBE(n, m_bytecode);
369✔
205

206
        // push timestamp
207
        const long long timestamp = std::chrono::duration_cast<std::chrono::seconds>(
123✔
208
                                        std::chrono::system_clock::now().time_since_epoch())
123✔
209
                                        .count();
123✔
210
        for (long i = 0; i < 8; ++i)
1,107✔
211
        {
212
            const long shift = 8 * (7 - i);
984✔
213
            const auto ts_byte = static_cast<uint8_t>((timestamp & (0xffLL << shift)) >> shift);
984✔
214
            m_bytecode.push_back(ts_byte);
984✔
215
        }
984✔
216
    }
123✔
217

218
    void IRCompiler::pushSymbolTable(const std::vector<std::string>& symbols)
123✔
219
    {
123✔
220
        const std::size_t symbol_size = symbols.size();
123✔
221
        if (symbol_size > std::numeric_limits<uint16_t>::max())
123✔
UNCOV
222
            throw std::overflow_error(fmt::format("Too many symbols: {}, exceeds the maximum size of 2^16 - 1", symbol_size));
×
223

224
        m_bytecode.push_back(SYM_TABLE_START);
123✔
225
        serializeOn2BytesToVecBE(symbol_size, m_bytecode);
123✔
226

227
        for (const auto& sym : symbols)
2,690✔
228
        {
229
            // push the string, null terminated
230
            std::ranges::transform(sym, std::back_inserter(m_bytecode), [](const char i) {
25,783✔
231
                return static_cast<uint8_t>(i);
23,216✔
232
            });
233
            m_bytecode.push_back(0_u8);
2,567✔
234
        }
2,567✔
235
    }
123✔
236

237
    void IRCompiler::pushValueTable(const std::vector<ValTableElem>& values)
123✔
238
    {
123✔
239
        const std::size_t value_size = values.size();
123✔
240
        if (value_size > std::numeric_limits<uint16_t>::max())
123✔
UNCOV
241
            throw std::overflow_error(fmt::format("Too many values: {}, exceeds the maximum size of 2^16 - 1", value_size));
×
242

243
        m_bytecode.push_back(VAL_TABLE_START);
123✔
244
        serializeOn2BytesToVecBE(value_size, m_bytecode);
123✔
245

246
        for (const ValTableElem& val : values)
3,239✔
247
        {
248
            switch (val.type)
3,116✔
249
            {
615✔
250
                case ValTableElemType::Number:
251
                {
252
                    m_bytecode.push_back(NUMBER_TYPE);
615✔
253
                    const auto n = std::get<double>(val.value);
615✔
254
                    const auto [exponent, mantissa] = ieee754::serialize(n);
615✔
255
                    serializeToVecLE(exponent, m_bytecode);
615✔
256
                    serializeToVecLE(mantissa, m_bytecode);
615✔
257
                    break;
258
                }
1,835✔
259

260
                case ValTableElemType::String:
261
                {
262
                    m_bytecode.push_back(STRING_TYPE);
1,220✔
263
                    auto t = std::get<std::string>(val.value);
1,220✔
264
                    std::ranges::transform(t, std::back_inserter(m_bytecode), [](const char i) {
22,318✔
265
                        return static_cast<uint8_t>(i);
21,098✔
266
                    });
267
                    break;
268
                }
2,501✔
269

270
                case ValTableElemType::PageAddr:
271
                {
272
                    m_bytecode.push_back(FUNC_TYPE);
1,281✔
273
                    const std::size_t addr = std::get<std::size_t>(val.value);
1,281✔
274
                    serializeOn2BytesToVecBE(addr, m_bytecode);
1,281✔
275
                    break;
276
                }
1,281✔
277
            }
3,116✔
278

279
            m_bytecode.push_back(0_u8);
3,116✔
280
        }
3,116✔
281
    }
123✔
282

283
    void IRCompiler::pushFilenameTable()
123✔
284
    {
123✔
285
        if (m_filenames.size() > std::numeric_limits<uint16_t>::max())
123✔
UNCOV
286
            throw std::overflow_error(fmt::format("Too many filenames: {}, exceeds the maximum size of 2^16 - 1", m_filenames.size()));
×
287

288
        m_bytecode.push_back(FILENAMES_TABLE_START);
123✔
289
        // push number of elements
290
        serializeOn2BytesToVecBE(m_filenames.size(), m_bytecode);
123✔
291

292
        for (const auto& name : m_filenames)
333✔
293
        {
294
            std::ranges::transform(name, std::back_inserter(m_bytecode), [](const char i) {
15,717✔
295
                return static_cast<uint8_t>(i);
15,507✔
296
            });
297
            m_bytecode.push_back(0_u8);
210✔
298
        }
210✔
299
    }
123✔
300

301
    void IRCompiler::pushInstLocTable(const std::vector<IR::Block>& pages)
123✔
302
    {
123✔
303
        std::vector<internal::InstLoc> locations;
123✔
304
        for (std::size_t i = 0, end = pages.size(); i < end; ++i)
1,527✔
305
        {
306
            const auto& page = pages[i];
1,404✔
307
            uint16_t ip = 0;
1,404✔
308

309
            for (const auto& inst : page)
42,227✔
310
            {
311
                if (inst.hasValidSourceLocation())
40,823✔
312
                {
313
                    // we are guaranteed to have a value since we listed all existing filenames in IRCompiler::process before,
314
                    // thus we do not have to check if std::ranges::find returned a valid iterator.
315
                    auto file_id = static_cast<uint16_t>(std::distance(m_filenames.begin(), std::ranges::find(m_filenames, inst.filename())));
14,911✔
316

317
                    std::optional<internal::InstLoc> prev = std::nullopt;
14,911✔
318
                    if (!locations.empty())
14,911✔
319
                        prev = locations.back();
14,788✔
320

321
                    // skip redundant instruction location
322
                    if (!(prev.has_value() && prev->filename_id == file_id && prev->line == inst.sourceLine() && prev->page_pointer == i))
14,911✔
323
                        locations.push_back(
8,579✔
324
                            { .page_pointer = static_cast<uint16_t>(i),
34,316✔
325
                              .inst_pointer = ip,
8,579✔
326
                              .filename_id = file_id,
8,579✔
327
                              .line = static_cast<uint32_t>(inst.sourceLine()) });
8,579✔
328
                }
14,911✔
329

330
                if (inst.kind() != IR::Kind::Label)
40,823✔
331
                    ++ip;
37,699✔
332
            }
40,823✔
333
        }
1,404✔
334

335
        m_bytecode.push_back(INST_LOC_TABLE_START);
123✔
336
        serializeOn2BytesToVecBE(locations.size(), m_bytecode);
123✔
337

338
        std::optional<internal::InstLoc> prev = std::nullopt;
123✔
339

340
        for (const auto& loc : locations)
8,702✔
341
        {
342
            serializeOn2BytesToVecBE(loc.page_pointer, m_bytecode);
8,579✔
343
            serializeOn2BytesToVecBE(loc.inst_pointer, m_bytecode);
8,579✔
344
            serializeOn2BytesToVecBE(loc.filename_id, m_bytecode);
8,579✔
345
            serializeToVecBE(loc.line, m_bytecode);
8,579✔
346

347
            prev = loc;
8,579✔
348
        }
8,579✔
349
    }
123✔
350
}
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