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

ArkScript-lang / Ark / 29851449016

21 Jul 2026 05:05PM UTC coverage: 94.39% (+0.005%) from 94.385%
29851449016

push

github

SuperFola
fix(compiler): keep provenance information of IR blocks inside the IROptimiser

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

122 existing lines in 10 files now uncovered.

10700 of 11336 relevant lines covered (94.39%)

1164644.99 hits per line

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

91.44
/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/Utils/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) :
1,112✔
21
        Pass("IRCompiler", debug)
556✔
22
    {}
1,668✔
23

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

31
        // compute a list of unique filenames
32
        for (const auto& page : pages)
9,231✔
33
        {
34
            for (const auto& inst : page.data)
319,120✔
35
            {
36
                if (std::ranges::find(m_filenames, inst.filename()) == m_filenames.end() && inst.hasValidSourceLocation())
310,298✔
37
                    m_filenames.push_back(inst.filename());
657✔
38
            }
310,298✔
39
        }
8,822✔
40

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

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

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

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

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

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

68
    void IRCompiler::dumpToStream(std::ostream& stream) const
31✔
69
    {
31✔
70
        std::size_t index = 0;
31✔
71
        for (const auto& block : m_ir)
126✔
72
        {
73
            if (index == 0)
95✔
74
                // global scope
75
                fmt::println(stream, "global");
31✔
76
            else
77
            {
78
                fmt::println(
192✔
79
                    stream,
64✔
80
                    "page_{} ({} ({} argument{}) {}, {} instructions)",
64✔
81
                    index,
82
                    block.debugName(),
64✔
83
                    block.metadata.argument_count,
64✔
84
                    block.metadata.argument_count == 1 ? "" : "s",
64✔
85
                    block.metadataRepr(),
64✔
86
                    block.data.size());
64✔
87
            }
88

89
            for (const auto& entity : block.data)
2,145✔
90
            {
91
                switch (entity.kind())
2,050✔
92
                {
220✔
93
                    case IR::Kind::Label:
94
                        fmt::println(stream, ".L{}:", entity.label());
220✔
95
                        break;
419✔
96

97
                    case IR::Kind::Goto:
98
                        fmt::println(stream, "\t{} L{}", InstructionNames[entity.inst()], entity.label());
199✔
99
                        break;
213✔
100

101
                    case IR::Kind::GotoWithArg:
102
                        fmt::println(stream, "\t{} L{}, {}", InstructionNames[entity.inst()], entity.label(), entity.primaryArg());
14✔
103
                        break;
1,349✔
104

105
                    case IR::Kind::Opcode:
106
                        fmt::println(stream, "\t{} {}", InstructionNames[entity.inst()], entity.primaryArg());
1,335✔
107
                        break;
1,537✔
108

109
                    case IR::Kind::Opcode2Args:
110
                        fmt::println(stream, "\t{} {}, {}", InstructionNames[entity.inst()], entity.primaryArg(), entity.secondaryArg());
202✔
111
                        break;
282✔
112

113
                    case IR::Kind::Opcode3Args:
114
                        fmt::println(stream, "\t{} {}, {}, {}", InstructionNames[entity.inst()], entity.primaryArg(), entity.secondaryArg(), entity.tertiaryArg());
80✔
115
                        break;
80✔
116
                }
2,050✔
117
            }
2,050✔
118

119
            fmt::println(stream, "");
95✔
120
            ++index;
95✔
121
        }
95✔
122
    }
31✔
123

124
    const bytecode_t& IRCompiler::bytecode() const noexcept
409✔
125
    {
409✔
126
        return m_bytecode;
409✔
127
    }
128

129
    void IRCompiler::compile()
409✔
130
    {
409✔
131
        // push the different code segments
132
        for (std::size_t i = 0, end = m_ir.size(); i < end; ++i)
9,231✔
133
        {
134
            IR::Block& page = m_ir[i];
8,822✔
135
            // just in case we got too far, always add a HALT to be sure the
136
            // VM won't do anything crazy
137
            page.data.emplace_back(HALT);
8,822✔
138

139
            // push number of elements
140
            const std::size_t page_size = page.instructionCount();
8,822✔
141
            if (std::cmp_greater(page_size, MaxValue16Bits))
8,822✔
142
            {
UNCOV
143
                std::string message;
×
UNCOV
144
                if (i == 0)
×
UNCOV
145
                    message = fmt::format("Global scope exceeds the maximum number of instructions ({})", MaxValue16Bits);
×
UNCOV
146
                else if (page.metadata.name.has_value())
×
UNCOV
147
                    message = fmt::format("Function {} exceeds the maximum number of instructions ({})", page.metadata.name.value(), MaxValue16Bits);
×
148
                else
UNCOV
149
                    message = fmt::format("Anonymous function at page {} exceeds the maximum number of instructions ({})", i, MaxValue16Bits);
×
150

UNCOV
151
                throw std::overflow_error(message);
×
UNCOV
152
            }
×
153

154
            m_bytecode.push_back(CODE_SEGMENT_START);
8,822✔
155
            serializeOn2BytesToVecBE(page_size, m_bytecode);
8,822✔
156

157
            // register labels position
158
            uint16_t pos = 0;
8,822✔
159
            std::unordered_map<IR::label_t, uint16_t> label_to_position;
8,822✔
160
            for (const auto& inst : page.data)
327,942✔
161
            {
162
                switch (inst.kind())
319,120✔
163
                {
49,256✔
164
                    case IR::Kind::Label:
165
                        label_to_position[inst.label()] = pos;
49,256✔
166
                        break;
319,120✔
167

168
                    default:
169
                        ++pos;
269,864✔
170
                }
319,120✔
171
            }
319,120✔
172

173
            for (const auto& inst : page.data)
327,942✔
174
            {
175
                switch (inst.kind())
319,120✔
176
                {
41,206✔
177
                    case IR::Kind::Goto:
178
                        pushWord(Word(inst.inst(), label_to_position[inst.label()]));
41,206✔
179
                        break;
44,528✔
180

181
                    case IR::Kind::GotoWithArg:
182
                        pushWord(Word(inst.inst(), inst.primaryArg(), label_to_position[inst.label()]));
3,322✔
183
                        break;
228,658✔
184

185
                    case IR::Kind::Opcode:
186
                        [[fallthrough]];
187
                    case IR::Kind::Opcode2Args:
188
                        [[fallthrough]];
189
                    case IR::Kind::Opcode3Args:
190
                        pushWord(inst.bytecode());
225,336✔
191
                        break;
274,592✔
192

193
                    default:
194
                        break;
49,256✔
195
                }
319,120✔
196
            }
319,120✔
197
        }
8,822✔
198
    }
409✔
199

200
    void IRCompiler::pushWord(const Word& word)
269,864✔
201
    {
269,864✔
202
        m_bytecode.push_back(word.opcode);
269,864✔
203
        m_bytecode.push_back(word.byte_1);
269,864✔
204
        m_bytecode.push_back(word.byte_2);
269,864✔
205
        m_bytecode.push_back(word.byte_3);
269,864✔
206
    }
269,864✔
207

208
    void IRCompiler::pushFileHeader() noexcept
409✔
209
    {
409✔
210
        /*
211
            Generating headers:
212
                - lang name (to be sure we are executing an ArkScript file)
213
                    on 4 bytes (ark + padding)
214
                - version (major: 2 bytes, minor: 2 bytes, patch: 2 bytes)
215
                - timestamp (8 bytes, unix format)
216
        */
217

218
        m_bytecode.push_back('a');
409✔
219
        m_bytecode.push_back('r');
409✔
220
        m_bytecode.push_back('k');
409✔
221
        m_bytecode.push_back(0_u8);
409✔
222

223
        // push version
224
        for (const int n : std::array { ARK_VERSION_MAJOR, ARK_VERSION_MINOR, ARK_VERSION_PATCH })
1,636✔
225
            serializeOn2BytesToVecBE(n, m_bytecode);
1,227✔
226

227
        // push timestamp
228
        const long long timestamp = std::chrono::duration_cast<std::chrono::seconds>(
409✔
229
                                        std::chrono::system_clock::now().time_since_epoch())
409✔
230
                                        .count();
409✔
231
        for (long i = 0; i < 8; ++i)
3,681✔
232
        {
233
            const long shift = 8 * (7 - i);
3,272✔
234
            const auto ts_byte = static_cast<uint8_t>((timestamp & (0xffLL << shift)) >> shift);
3,272✔
235
            m_bytecode.push_back(ts_byte);
3,272✔
236
        }
3,272✔
237
    }
409✔
238

239
    void IRCompiler::pushSymbolTable(const std::vector<std::string>& symbols)
409✔
240
    {
409✔
241
        const std::size_t symbol_size = symbols.size();
409✔
242
        if (std::cmp_greater(symbol_size, MaxValue16Bits))
409✔
UNCOV
243
            throw std::overflow_error(fmt::format("Too many symbols: {}, exceeds the maximum size of {}", symbol_size, MaxValue16Bits));
×
244

245
        m_bytecode.push_back(SYM_TABLE_START);
409✔
246
        serializeOn2BytesToVecBE(symbol_size, m_bytecode);
409✔
247

248
        for (const auto& sym : symbols)
15,366✔
249
        {
250
            // push the string, null terminated
251
            std::ranges::transform(sym, std::back_inserter(m_bytecode), [](const char i) {
165,183✔
252
                return static_cast<uint8_t>(i);
150,226✔
253
            });
254
            m_bytecode.push_back(0_u8);
14,957✔
255
        }
14,957✔
256
    }
409✔
257

258
    void IRCompiler::pushValueTable(const std::vector<ValTableElem>& values)
409✔
259
    {
409✔
260
        const std::size_t value_size = values.size();
409✔
261
        if (std::cmp_greater(value_size, MaxValue16Bits))
409✔
UNCOV
262
            throw std::overflow_error(fmt::format("Too many values: {}, exceeds the maximum size of {}", value_size, MaxValue16Bits));
×
263

264
        m_bytecode.push_back(VAL_TABLE_START);
409✔
265
        serializeOn2BytesToVecBE(value_size, m_bytecode);
409✔
266

267
        for (const ValTableElem& val : values)
21,973✔
268
        {
269
            switch (val.type)
21,564✔
270
            {
2,332✔
271
                case ValTableElemType::Number:
272
                {
273
                    m_bytecode.push_back(NUMBER_TYPE);
2,332✔
274
                    const auto n = std::get<double>(val.value);
2,332✔
275
                    const auto [exponent, mantissa] = ieee754::serialize(n);
4,664✔
276
                    serializeToVecLE(exponent, m_bytecode);
2,332✔
277
                    serializeToVecLE(mantissa, m_bytecode);
2,332✔
278
                    break;
279
                }
13,145✔
280

281
                case ValTableElemType::String:
282
                {
283
                    m_bytecode.push_back(STRING_TYPE);
10,813✔
284
                    auto t = std::get<std::string>(val.value);
10,813✔
285
                    std::ranges::transform(t, std::back_inserter(m_bytecode), [](const char i) {
203,291✔
286
                        return static_cast<uint8_t>(i);
192,478✔
287
                    });
288
                    break;
289
                }
19,232✔
290

291
                case ValTableElemType::PageAddr:
292
                {
293
                    m_bytecode.push_back(FUNC_TYPE);
8,419✔
294
                    const std::size_t addr = std::get<std::size_t>(val.value);
8,419✔
295
                    serializeOn2BytesToVecBE(addr, m_bytecode);
8,419✔
296
                    break;
297
                }
8,419✔
298
            }
21,564✔
299

300
            m_bytecode.push_back(0_u8);
21,564✔
301
        }
21,564✔
302
    }
409✔
303

304
    void IRCompiler::pushFilenameTable()
409✔
305
    {
409✔
306
        if (std::cmp_greater(m_filenames.size(), MaxValue16Bits))
409✔
UNCOV
307
            throw std::overflow_error(fmt::format("Too many filenames: {}, exceeds the maximum size of {}", m_filenames.size(), MaxValue16Bits));
×
308

309
        m_bytecode.push_back(FILENAMES_TABLE_START);
409✔
310
        // push number of elements
311
        serializeOn2BytesToVecBE(m_filenames.size(), m_bytecode);
409✔
312

313
        for (const auto& name : m_filenames)
1,066✔
314
        {
315
            std::ranges::transform(name, std::back_inserter(m_bytecode), [](const char i) {
50,910✔
316
                return static_cast<uint8_t>(i);
50,253✔
317
            });
318
            m_bytecode.push_back(0_u8);
657✔
319
        }
657✔
320
    }
409✔
321

322
    void IRCompiler::pushInstLocTable(const std::vector<IR::Block>& pages)
409✔
323
    {
409✔
324
        std::vector<internal::InstLoc> locations;
409✔
325
        for (std::size_t i = 0, end = pages.size(); i < end; ++i)
9,231✔
326
        {
327
            const auto& page = pages[i];
8,822✔
328
            uint16_t ip = 0;
8,822✔
329

330
            for (const auto& inst : page.data)
319,120✔
331
            {
332
                if (inst.hasValidSourceLocation())
310,298✔
333
                {
334
                    // we are guaranteed to have a value since we listed all existing filenames in IRCompiler::process before,
335
                    // thus we do not have to check if std::ranges::find returned a valid iterator.
336
                    auto file_id = static_cast<uint16_t>(std::distance(m_filenames.begin(), std::ranges::find(m_filenames, inst.filename())));
190,871✔
337

338
                    std::optional<internal::InstLoc> prev = std::nullopt;
190,871✔
339
                    if (!locations.empty())
190,871✔
340
                        prev = locations.back();
190,463✔
341

342
                    // skip redundant instruction location
343
                    if (!(prev.has_value() && prev->filename_id == file_id && prev->line == inst.sourceLine() && prev->page_pointer == i))
190,871✔
344
                        locations.push_back(
79,272✔
345
                            { .page_pointer = static_cast<uint16_t>(i),
317,088✔
346
                              .inst_pointer = ip,
79,272✔
347
                              .filename_id = file_id,
79,272✔
348
                              .line = static_cast<uint32_t>(inst.sourceLine()) });
79,272✔
349
                }
190,871✔
350

351
                if (inst.kind() != IR::Kind::Label)
310,298✔
352
                    ++ip;
261,042✔
353
            }
310,298✔
354
        }
8,822✔
355

356
        m_bytecode.push_back(INST_LOC_TABLE_START);
409✔
357
        serializeOn2BytesToVecBE(locations.size(), m_bytecode);
409✔
358

359
        for (const auto& loc : locations)
79,681✔
360
        {
361
            serializeOn2BytesToVecBE(loc.page_pointer, m_bytecode);
79,272✔
362
            serializeOn2BytesToVecBE(loc.inst_pointer, m_bytecode);
79,272✔
363
            serializeOn2BytesToVecBE(loc.filename_id, m_bytecode);
79,272✔
364
            serializeToVecBE(loc.line, m_bytecode);
79,272✔
365
        }
79,272✔
366
    }
409✔
367
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc