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

ArkScript-lang / Ark / 15229918267

24 May 2025 06:38PM UTC coverage: 86.765% (-0.1%) from 86.893%
15229918267

push

github

SuperFola
fix(ir optimizer): adding check that we have enough instructions to try an optimization

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

140 existing lines in 2 files now uncovered.

7185 of 8281 relevant lines covered (86.76%)

84401.34 hits per line

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

99.44
/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp
1
#include <Ark/Compiler/IntermediateRepresentation/IROptimizer.hpp>
2

3
#include <cassert>
4
#include <utility>
5
#include <ranges>
6

7
#include <Ark/Builtins/Builtins.hpp>
8

9
namespace Ark::internal
10
{
11
    IROptimizer::IROptimizer(const unsigned debug) :
548✔
12
        m_logger("IROptimizer", debug)
274✔
13
    {
274✔
14
        m_ruleset = {
12,604✔
15
            Rule { { LOAD_CONST, LOAD_CONST }, LOAD_CONST_LOAD_CONST },
274✔
16
            Rule { { LOAD_CONST, STORE }, LOAD_CONST_STORE },
274✔
17
            Rule { { LOAD_CONST, SET_VAL }, LOAD_CONST_SET_VAL },
274✔
18
            Rule { { LOAD_SYMBOL, STORE }, STORE_FROM },
274✔
19
            Rule { { LOAD_SYMBOL_BY_INDEX, STORE }, STORE_FROM_INDEX },
274✔
20
            Rule { { LOAD_SYMBOL, SET_VAL }, SET_VAL_FROM },
274✔
21
            Rule { { LOAD_SYMBOL_BY_INDEX, SET_VAL }, SET_VAL_FROM_INDEX },
274✔
22
            Rule {
274✔
23
                { BUILTIN, CALL }, CALL_BUILTIN, [](const Entities entities) {
859✔
24
                    return Builtins::builtins[entities[0].primaryArg()].second.isFunction();
585✔
25
                } },
26
            Rule { { LOAD_SYMBOL, CALL }, CALL_SYMBOL },
274✔
27
            Rule { { LOAD_SYMBOL, GET_FIELD }, GET_FIELD_FROM_SYMBOL },
274✔
28
            Rule { { LOAD_SYMBOL_BY_INDEX, GET_FIELD }, GET_FIELD_FROM_SYMBOL_INDEX },
274✔
29
            Rule { { LIST, STORE }, STORE_LIST },
274✔
30
            // LOAD_SYMBOL a / LOAD_SYMBOL_BY_INDEX index
31
            // LOAD_CONST n (1)
32
            // ADD / SUB
33
            // STORE
34
            // ---> INCREMENT_STORE / DECREMENT_STORE a value
35
            Rule { { LOAD_CONST, LOAD_SYMBOL, ADD, SET_VAL }, [this](const Entities e) {
717✔
36
                      return isPositiveNumberInlinable(e[0].primaryArg()) && e[1].primaryArg() == e[3].primaryArg();
443✔
37
                  },
38
                   [this](const Entities e) {
717✔
39
                       return IR::Entity(INCREMENT_STORE, e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
443✔
40
                   } },
41
            Rule { { LOAD_SYMBOL, LOAD_CONST, ADD, SET_VAL }, [this](const Entities e) {
325✔
42
                      return isPositiveNumberInlinable(e[1].primaryArg()) && e[1].primaryArg() == e[3].primaryArg();
51✔
43
                  },
44
                   [this](const Entities e) {
275✔
45
                       return IR::Entity(INCREMENT_STORE, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
1✔
46
                   } },
47
            Rule { { LOAD_SYMBOL, LOAD_CONST, SUB, SET_VAL }, [this](const Entities e) {
285✔
48
                      return isPositiveNumberInlinable(e[1].primaryArg()) && e[1].primaryArg() == e[3].primaryArg();
11✔
49
                  },
50
                   [this](const Entities e) {
275✔
51
                       return IR::Entity(DECREMENT_STORE, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
1✔
52
                   } },
53
            // without the final store, just increment/decrement
54
            Rule { { LOAD_CONST, LOAD_SYMBOL, ADD }, [this](const Entities e) {
275✔
55
                      return isPositiveNumberInlinable(e[0].primaryArg());
1✔
56
                  },
57
                   [this](const Entities e) {
275✔
58
                       return IR::Entity(INCREMENT, e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
1✔
59
                   } },
60
            Rule { { LOAD_SYMBOL, LOAD_CONST, ADD }, [this](const Entities e) {
324✔
61
                      return isPositiveNumberInlinable(e[1].primaryArg());
50✔
62
                  },
63
                   [this](const Entities e) {
318✔
64
                       return IR::Entity(INCREMENT, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
44✔
65
                   } },
66
            Rule { { LOAD_SYMBOL, LOAD_CONST, SUB }, [this](const Entities e) {
289✔
67
                      return isPositiveNumberInlinable(e[1].primaryArg());
15✔
68
                  },
69
                   [this](const Entities e) {
289✔
70
                       return IR::Entity(DECREMENT, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
15✔
71
                   } },
72
            Rule { { LOAD_CONST, LOAD_SYMBOL_BY_INDEX, ADD }, [this](const Entities e) {
328✔
73
                      return isPositiveNumberInlinable(e[0].primaryArg());
54✔
74
                  },
75
                   [this](const Entities e) {
328✔
76
                       return IR::Entity(INCREMENT_BY_INDEX, e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
54✔
77
                   } },
78
            Rule { { LOAD_SYMBOL_BY_INDEX, LOAD_CONST, ADD }, [this](const Entities e) {
293✔
79
                      return isPositiveNumberInlinable(e[1].primaryArg());
19✔
80
                  },
81
                   [this](const Entities e) {
280✔
82
                       return IR::Entity(INCREMENT_BY_INDEX, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
6✔
83
                   } },
84
            Rule { { LOAD_SYMBOL_BY_INDEX, LOAD_CONST, SUB }, [this](const Entities e) {
334✔
85
                      return isPositiveNumberInlinable(e[1].primaryArg());
60✔
86
                  },
87
                   [this](const Entities e) {
333✔
88
                       return IR::Entity(DECREMENT_BY_INDEX, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
59✔
89
                   } },
90
            // LOAD_SYMBOL list
91
            // TAIL / HEAD
92
            // STORE / SET_VAL a
93
            // ---> STORE_TAIL list a ; STORE_HEAD ; SET_VAL_TAIL ; SET_VAL_HEAD
94
            Rule { { LOAD_SYMBOL, TAIL, STORE }, [](const Entities e) {
275✔
95
                      return IR::Entity(STORE_TAIL, e[0].primaryArg(), e[2].primaryArg());
1✔
96
                  } },
97
            Rule { { LOAD_SYMBOL, TAIL, SET_VAL }, [](const Entities e) {
275✔
98
                      return IR::Entity(SET_VAL_TAIL, e[0].primaryArg(), e[2].primaryArg());
1✔
99
                  } },
100
            Rule { { LOAD_SYMBOL, HEAD, STORE }, [](const Entities e) {
275✔
101
                      return IR::Entity(STORE_HEAD, e[0].primaryArg(), e[2].primaryArg());
1✔
102
                  } },
103
            Rule { { LOAD_SYMBOL, HEAD, SET_VAL }, [](const Entities e) {
275✔
104
                      return IR::Entity(SET_VAL_HEAD, e[0].primaryArg(), e[2].primaryArg());
1✔
105
                  } },
106
            Rule { { LOAD_SYMBOL_BY_INDEX, TAIL, STORE }, [](const Entities e) {
276✔
107
                      return IR::Entity(STORE_TAIL_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
2✔
108
                  } },
109
            Rule { { LOAD_SYMBOL_BY_INDEX, TAIL, SET_VAL }, [](const Entities e) {
276✔
110
                      return IR::Entity(SET_VAL_TAIL_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
2✔
111
                  } },
112
            Rule { { LOAD_SYMBOL_BY_INDEX, HEAD, STORE }, [](const Entities e) {
277✔
113
                      return IR::Entity(STORE_HEAD_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
3✔
114
                  } },
115
            Rule { { LOAD_SYMBOL_BY_INDEX, HEAD, SET_VAL }, [](const Entities e) {
276✔
116
                      return IR::Entity(SET_VAL_HEAD_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
2✔
117
                  } },
118
            // LOAD_CONST id / LOAD_SYMBOL id
119
            // <comparison operator>
120
            // POP_JUMP_IF_(FALSE|TRUE)
121
            // ---> <OP>_(CONST|SYM)_JUMP_IF_(FALSE|TRUE)
122
            Rule { { LOAD_CONST, LT, POP_JUMP_IF_FALSE }, [](const Entities e) {
284✔
123
                      return IR::Entity::GotoWithArg(e[2], LT_CONST_JUMP_IF_FALSE, e[0].primaryArg());
10✔
124
                  } },
125
            Rule { { LOAD_CONST, LT, POP_JUMP_IF_TRUE }, [](const Entities e) {
295✔
126
                      return IR::Entity::GotoWithArg(e[2], LT_CONST_JUMP_IF_TRUE, e[0].primaryArg());
21✔
127
                  } },
128
            Rule { { LOAD_SYMBOL, LT, POP_JUMP_IF_FALSE }, [](const Entities e) {
413✔
129
                      return IR::Entity::GotoWithArg(e[2], LT_SYM_JUMP_IF_FALSE, e[0].primaryArg());
139✔
130
                  } },
131
            Rule { { LOAD_CONST, GT, POP_JUMP_IF_TRUE }, [](const Entities e) {
306✔
132
                      return IR::Entity::GotoWithArg(e[2], GT_CONST_JUMP_IF_TRUE, e[0].primaryArg());
32✔
133
                  } },
134
            Rule { { LOAD_CONST, GT, POP_JUMP_IF_FALSE }, [](const Entities e) {
282✔
135
                      return IR::Entity::GotoWithArg(e[2], GT_CONST_JUMP_IF_FALSE, e[0].primaryArg());
8✔
136
                  } },
137
            Rule { { LOAD_SYMBOL, GT, POP_JUMP_IF_FALSE }, [](const Entities e) {
275✔
138
                      return IR::Entity::GotoWithArg(e[2], GT_SYM_JUMP_IF_FALSE, e[0].primaryArg());
1✔
139
                  } },
140
            Rule { { LOAD_CONST, EQ, POP_JUMP_IF_TRUE }, [](const Entities e) {
476✔
141
                      return IR::Entity::GotoWithArg(e[2], EQ_CONST_JUMP_IF_TRUE, e[0].primaryArg());
202✔
142
                  } },
143
            Rule { { LOAD_SYMBOL_BY_INDEX, EQ, POP_JUMP_IF_TRUE }, [](const Entities e) {
331✔
144
                      return IR::Entity::GotoWithArg(e[2], EQ_SYM_INDEX_JUMP_IF_TRUE, e[0].primaryArg());
57✔
145
                  } },
146
            Rule { { LOAD_CONST, NEQ, POP_JUMP_IF_TRUE }, [](const Entities e) {
276✔
147
                      return IR::Entity::GotoWithArg(e[2], NEQ_CONST_JUMP_IF_TRUE, e[0].primaryArg());
2✔
148
                  } },
149
            Rule { { LOAD_SYMBOL, NEQ, POP_JUMP_IF_FALSE }, [](const Entities e) {
282✔
150
                      return IR::Entity::GotoWithArg(e[2], NEQ_SYM_JUMP_IF_FALSE, e[0].primaryArg());
8✔
151
                  } },
152
            // LOAD_SYMBOL id
153
            // LOAD_SYMBOL id2
154
            // AT
155
            // ---> AT_SYM_SYM id id2
156
            Rule { { LOAD_SYMBOL, LOAD_SYMBOL, AT }, AT_SYM_SYM },
274✔
157
            Rule { { LOAD_SYMBOL_BY_INDEX, LOAD_SYMBOL_BY_INDEX, AT }, AT_SYM_INDEX_SYM_INDEX },
274✔
158
            // LOAD_SYMBOL sym
159
            // TYPE
160
            // LOAD_CONST cst
161
            // EQ
162
            // ---> CHECK_TYPE_OF sym, cst
163
            // also works with LOAD_CONST cst, LOAD_SYMBOL sym, TYPE, EQ, but args will be flipped
164
            Rule { { LOAD_SYMBOL, TYPE, LOAD_CONST, EQ }, [](const Entities e) {
280✔
165
                      return IR::Entity(CHECK_TYPE_OF, e[0].primaryArg(), e[2].primaryArg());
6✔
166
                  } },
167
            Rule { { LOAD_CONST, LOAD_SYMBOL, TYPE, EQ }, [](const Entities e) {
275✔
168
                      return IR::Entity(CHECK_TYPE_OF, e[1].primaryArg(), e[0].primaryArg());
1✔
169
                  } },
170
            Rule { { LOAD_SYMBOL_BY_INDEX, TYPE, LOAD_CONST, EQ }, [](const Entities e) {
277✔
171
                      return IR::Entity(CHECK_TYPE_OF_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
3✔
172
                  } },
173
            Rule { { LOAD_CONST, LOAD_SYMBOL_BY_INDEX, TYPE, EQ }, [](const Entities e) {
317✔
174
                      return IR::Entity(CHECK_TYPE_OF_BY_INDEX, e[1].primaryArg(), e[0].primaryArg());
43✔
175
                  } },
176
        };
177
    }
274✔
178

179
    void IROptimizer::process(const std::vector<IR::Block>& pages, const std::vector<std::string>& symbols, const std::vector<ValTableElem>& values)
164✔
180
    {
164✔
181
        m_logger.traceStart("process");
164✔
182
        m_symbols = symbols;
164✔
183
        m_values = values;
164✔
184

185
        for (const auto& block : pages)
1,666✔
186
        {
187
            m_ir.emplace_back();
1,502✔
188
            IR::Block& current_block = m_ir.back();
1,502✔
189

190
            std::size_t i = 0;
1,502✔
191
            const std::size_t end = block.size();
1,502✔
192

193
            while (i < end)
38,631✔
194
            {
195
                std::optional<EntityWithOffset> maybe_compacted = replaceWithRules(
111,387✔
196
                    m_ruleset,
37,129✔
197
                    std::span(
37,129✔
198
                        block.begin() + static_cast<IR::Block::difference_type>(i),
37,129✔
199
                        block.size() - i));
37,129✔
200

201
                if (maybe_compacted.has_value())
37,129✔
202
                {
203
                    auto [entity, offset] = maybe_compacted.value();
18,106✔
204
                    current_block.emplace_back(entity);
9,053✔
205
                    i += offset;
9,053✔
206
                }
9,053✔
207
                else
208
                {
209
                    current_block.emplace_back(block[i]);
28,076✔
210
                    ++i;
28,076✔
211
                }
212
            }
37,129✔
213
        }
1,502✔
214

215
        m_logger.traceEnd();
164✔
216
    }
164✔
217

218
    const std::vector<IR::Block>& IROptimizer::intermediateRepresentation() const noexcept
164✔
219
    {
164✔
220
        return m_ir;
164✔
221
    }
222

223
    bool IROptimizer::match(const std::vector<Instruction>& expected_insts, const std::span<const IR::Entity> entities) const
1,354,839✔
224
    {
1,354,839✔
225
        if (expected_insts.size() > entities.size())
1,354,839✔
226
            return false;
107,084✔
227

228
        for (std::size_t i = 0; i < expected_insts.size(); ++i)
2,643,526✔
229
        {
230
            if (expected_insts[i] != entities[i].inst())
1,395,771✔
231
                return false;
1,238,619✔
232
        }
157,152✔
233

234
        return true;
9,136✔
235
    }
1,354,839✔
236

237
    bool IROptimizer::canBeOptimizedSafely(std::span<const IR::Entity> entities, std::size_t window_size) const
9,053✔
238
    {
9,053✔
239
        // check that we can actually safely apply the optimization on the given instructions
240
        return std::ranges::none_of(
9,053✔
241
            entities | std::ranges::views::take(window_size),
9,053✔
242
            [](const IR::Entity& entity) {
20,261✔
243
                return entity.primaryArg() > IR::MaxValueForDualArg;
20,261✔
244
            });
245
    }
246

247
    std::optional<EntityWithOffset> IROptimizer::replaceWithRules(const std::vector<Rule>& rules, const std::span<const IR::Entity> entities)
37,129✔
248
    {
37,129✔
249
        for (const auto& [expected, condition, createReplacement] : rules)
1,419,210✔
250
        {
251
            if (match(expected, entities) && condition(entities))
1,354,839✔
252
            {
253
                const std::size_t window_size = expected.size();
18,106✔
254
                if (!canBeOptimizedSafely(entities, window_size))
9,053✔
UNCOV
255
                    return std::nullopt;  // no need to try other optimizations, they won't be applied either
×
256

257
                auto output = createReplacement(entities);
18,106✔
258

259
                if (const auto it = std::ranges::find_if(entities, [](const auto& entity) {
34,755✔
260
                        return entity.hasValidSourceLocation();
25,702✔
261
                    });
262
                    it != entities.end())
18,106✔
263
                    output.setSourceLocation(it->filename(), it->sourceLine());
9,053✔
264

265
                return EntityWithOffset { output, window_size };
9,053✔
266
            }
9,053✔
267
        }
1,354,839✔
268

269
        return std::nullopt;
28,076✔
270
    }
37,129✔
271

272
    bool IROptimizer::isPositiveNumberInlinable(const uint16_t id) const
704✔
273
    {
704✔
274
        if (std::cmp_less(id, m_values.size()) && m_values[id].type == ValTableElemType::Number)
704✔
275
        {
276
            const double val = std::get<double>(m_values[id].value);
681✔
277
            return val >= 0.0 &&
1,362✔
278
                val < IR::MaxValueForDualArg &&
681✔
279
                static_cast<double>(static_cast<long>(val)) == val;
680✔
280
        }
681✔
281
        return false;
23✔
282
    }
704✔
283

284
    uint16_t IROptimizer::numberAsArg(const uint16_t id) const
624✔
285
    {
624✔
286
        return static_cast<uint16_t>(std::get<double>(m_values[id].value));
624✔
287
    }
288
}
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