ArkScript
A small, lisp-inspired, functional scripting language
IROptimizer.cpp
Go to the documentation of this file.
2
3#include <cassert>
4#include <utility>
5#include <ranges>
6#include <algorithm>
7
9
10namespace Ark::internal
11{
12 IR::Entity fuseMathOps3(const std::span<const IR::Entity> e)
13 {
14 return IR::Entity(FUSED_MATH, e[0].inst(), e[1].inst(), e[2].inst());
15 }
16
17 IR::Entity fuseMathOps2(const std::span<const IR::Entity> e)
18 {
19 return IR::Entity(FUSED_MATH, e[0].inst(), e[1].inst(), NOP);
20 }
21
22 IROptimizer::IROptimizer(const unsigned debug) :
23 Pass("IROptimizer", debug)
24 {
25 m_ruleset = {
34 [](const Entities entities, const std::size_t start_idx) {
35 return start_idx == 0 && entities[5].inst() == RET;
36 },
37 [](const Entities e) {
38 return IR::Entity(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, e[3].primaryArg(), 1);
39 } },
41 [](const Entities entities, const std::size_t start_idx) {
42 return start_idx == 0 && entities[7].inst() == RET;
43 },
44 [](const Entities e) {
45 return IR::Entity(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, e[5].primaryArg(), 2);
46 } },
48 [](const Entities entities, const std::size_t start_idx) {
49 return start_idx == 0 && entities[9].inst() == RET;
50 },
51 [](const Entities e) {
52 return IR::Entity(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, e[7].primaryArg(), 3);
53 } },
56 Rule { { LIST, STORE }, STORE_LIST },
59 // LOAD_CONST, LOAD_FAST a, MUL, SET_VAL / LOAD_FAST a, LOAD_CONST, MUL, SET_VAL
60 // ---> MUL_SET_VAL a value
61 Rule { { LOAD_CONST, LOAD_FAST, MUL, SET_VAL }, [this](const Entities e, const std::size_t) {
62 return isSmallerNumberInlinable(e[0].primaryArg()) && e[1].primaryArg() == e[3].primaryArg();
63 },
64 [this](const Entities e) {
65 return IR::Entity(MUL_SET_VAL, e[1].primaryArg(), smallerNumberAsArg(e[0].primaryArg()));
66 } },
67 Rule { { LOAD_FAST, LOAD_CONST, MUL, SET_VAL }, [this](const Entities e, const std::size_t) {
68 return isSmallerNumberInlinable(e[1].primaryArg()) && e[0].primaryArg() == e[3].primaryArg();
69 },
70 [this](const Entities e) {
71 return IR::Entity(MUL_SET_VAL, e[0].primaryArg(), smallerNumberAsArg(e[1].primaryArg()));
72 } },
73 // LOAD_CONST, LOAD_FAST a, MUL / LOAD_FAST a, LOAD_CONST, MUL
74 // ---> MUL_(BY|BY_INDEX) a value
75 Rule { { LOAD_CONST, LOAD_FAST, MUL }, [this](const Entities e, const std::size_t) {
76 return isSmallerNumberInlinable(e[0].primaryArg());
77 },
78 [this](const Entities e) {
79 return IR::Entity(MUL_BY, e[1].primaryArg(), smallerNumberAsArg(e[0].primaryArg()));
80 } },
81 Rule { { LOAD_FAST, LOAD_CONST, MUL }, [this](const Entities e, const std::size_t) {
82 return isSmallerNumberInlinable(e[1].primaryArg());
83 },
84 [this](const Entities e) {
85 return IR::Entity(MUL_BY, e[0].primaryArg(), smallerNumberAsArg(e[1].primaryArg()));
86 } },
87 Rule { { LOAD_CONST, LOAD_FAST_BY_INDEX, MUL }, [this](const Entities e, const std::size_t) {
88 return isSmallerNumberInlinable(e[0].primaryArg());
89 },
90 [this](const Entities e) {
91 return IR::Entity(MUL_BY_INDEX, e[1].primaryArg(), smallerNumberAsArg(e[0].primaryArg()));
92 } },
93 Rule { { LOAD_FAST_BY_INDEX, LOAD_CONST, MUL }, [this](const Entities e, const std::size_t) {
94 return isSmallerNumberInlinable(e[1].primaryArg());
95 },
96 [this](const Entities e) {
97 return IR::Entity(MUL_BY_INDEX, e[0].primaryArg(), smallerNumberAsArg(e[1].primaryArg()));
98 } },
99 // (LOAD_FAST a | LOAD_FAST_BY_INDEX index), LOAD_CONST n (=1), (ADD | SUB), STORE
100 // ---> INCREMENT_STORE / DECREMENT_STORE a value
101 Rule { { LOAD_CONST, LOAD_FAST, ADD, SET_VAL }, [this](const Entities e, const std::size_t) {
102 return isPositiveNumberInlinable(e[0].primaryArg()) && e[1].primaryArg() == e[3].primaryArg();
103 },
104 [this](const Entities e) {
105 return IR::Entity(INCREMENT_STORE, e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
106 } },
107 Rule { { LOAD_FAST, LOAD_CONST, ADD, SET_VAL }, [this](const Entities e, const std::size_t) {
108 return isPositiveNumberInlinable(e[1].primaryArg()) && e[0].primaryArg() == e[3].primaryArg();
109 },
110 [this](const Entities e) {
111 return IR::Entity(INCREMENT_STORE, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
112 } },
113 Rule { { LOAD_FAST, LOAD_CONST, SUB, SET_VAL }, [this](const Entities e, const std::size_t) {
114 return isPositiveNumberInlinable(e[1].primaryArg()) && e[0].primaryArg() == e[3].primaryArg();
115 },
116 [this](const Entities e) {
117 return IR::Entity(DECREMENT_STORE, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
118 } },
119 // without the final store, just increment/decrement
120 Rule { { LOAD_CONST, LOAD_FAST, ADD }, [this](const Entities e, const std::size_t) {
121 return isPositiveNumberInlinable(e[0].primaryArg());
122 },
123 [this](const Entities e) {
124 return IR::Entity(INCREMENT, e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
125 } },
126 Rule { { LOAD_FAST, LOAD_CONST, ADD }, [this](const Entities e, const std::size_t) {
127 return isPositiveNumberInlinable(e[1].primaryArg());
128 },
129 [this](const Entities e) {
130 return IR::Entity(INCREMENT, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
131 } },
132 Rule { { LOAD_FAST, LOAD_CONST, SUB }, [this](const Entities e, const std::size_t) {
133 return isPositiveNumberInlinable(e[1].primaryArg());
134 },
135 [this](const Entities e) {
136 return IR::Entity(DECREMENT, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
137 } },
138 Rule { { LOAD_CONST, LOAD_FAST_BY_INDEX, ADD }, [this](const Entities e, const std::size_t) {
139 return isPositiveNumberInlinable(e[0].primaryArg());
140 },
141 [this](const Entities e) {
142 return IR::Entity(INCREMENT_BY_INDEX, e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
143 } },
144 Rule { { LOAD_FAST_BY_INDEX, LOAD_CONST, ADD }, [this](const Entities e, const std::size_t) {
145 return isPositiveNumberInlinable(e[1].primaryArg());
146 },
147 [this](const Entities e) {
148 return IR::Entity(INCREMENT_BY_INDEX, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
149 } },
150 Rule { { LOAD_FAST_BY_INDEX, LOAD_CONST, SUB }, [this](const Entities e, const std::size_t) {
151 return isPositiveNumberInlinable(e[1].primaryArg());
152 },
153 [this](const Entities e) {
154 return IR::Entity(DECREMENT_BY_INDEX, e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
155 } },
156 // LOAD_FAST list, (TAIL | HEAD), (STORE | SET_VAL a)
157 // ---> STORE_TAIL list a ; STORE_HEAD ; SET_VAL_TAIL ; SET_VAL_HEAD
158 Rule { { LOAD_FAST, TAIL, STORE }, [](const Entities e) {
159 return IR::Entity(STORE_TAIL, e[0].primaryArg(), e[2].primaryArg());
160 } },
161 Rule { { LOAD_FAST, TAIL, SET_VAL }, [](const Entities e) {
162 return IR::Entity(SET_VAL_TAIL, e[0].primaryArg(), e[2].primaryArg());
163 } },
164 Rule { { LOAD_FAST, HEAD, STORE }, [](const Entities e) {
165 return IR::Entity(STORE_HEAD, e[0].primaryArg(), e[2].primaryArg());
166 } },
167 Rule { { LOAD_FAST, HEAD, SET_VAL }, [](const Entities e) {
168 return IR::Entity(SET_VAL_HEAD, e[0].primaryArg(), e[2].primaryArg());
169 } },
170 Rule { { LOAD_FAST_BY_INDEX, TAIL, STORE }, [](const Entities e) {
171 return IR::Entity(STORE_TAIL_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
172 } },
173 Rule { { LOAD_FAST_BY_INDEX, TAIL, SET_VAL }, [](const Entities e) {
174 return IR::Entity(SET_VAL_TAIL_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
175 } },
176 Rule { { LOAD_FAST_BY_INDEX, HEAD, STORE }, [](const Entities e) {
177 return IR::Entity(STORE_HEAD_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
178 } },
179 Rule { { LOAD_FAST_BY_INDEX, HEAD, SET_VAL }, [](const Entities e) {
180 return IR::Entity(SET_VAL_HEAD_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
181 } },
182 // (LOAD_CONST id | LOAD_FAST id), <comparison operator>, POP_JUMP_IF_(FALSE|TRUE)
183 // ---> <OP>_(CONST|SYM)_JUMP_IF_(FALSE|TRUE)
184 Rule { { LOAD_CONST, LT, POP_JUMP_IF_FALSE }, [](const Entities e) {
185 return IR::Entity::GotoWithArg(e[2], LT_CONST_JUMP_IF_FALSE, e[0].primaryArg());
186 } },
187 Rule { { LOAD_CONST, LT, POP_JUMP_IF_TRUE }, [](const Entities e) {
188 return IR::Entity::GotoWithArg(e[2], LT_CONST_JUMP_IF_TRUE, e[0].primaryArg());
189 } },
190 Rule { { LOAD_FAST, LT, POP_JUMP_IF_FALSE }, [](const Entities e) {
191 return IR::Entity::GotoWithArg(e[2], LT_SYM_JUMP_IF_FALSE, e[0].primaryArg());
192 } },
193 Rule { { LOAD_CONST, GT, POP_JUMP_IF_TRUE }, [](const Entities e) {
194 return IR::Entity::GotoWithArg(e[2], GT_CONST_JUMP_IF_TRUE, e[0].primaryArg());
195 } },
196 Rule { { LOAD_CONST, GT, POP_JUMP_IF_FALSE }, [](const Entities e) {
197 return IR::Entity::GotoWithArg(e[2], GT_CONST_JUMP_IF_FALSE, e[0].primaryArg());
198 } },
199 Rule { { LOAD_FAST, GT, POP_JUMP_IF_FALSE }, [](const Entities e) {
200 return IR::Entity::GotoWithArg(e[2], GT_SYM_JUMP_IF_FALSE, e[0].primaryArg());
201 } },
202 Rule { { LOAD_CONST, EQ, POP_JUMP_IF_TRUE }, [](const Entities e) {
203 return IR::Entity::GotoWithArg(e[2], EQ_CONST_JUMP_IF_TRUE, e[0].primaryArg());
204 } },
205 Rule { { LOAD_FAST_BY_INDEX, EQ, POP_JUMP_IF_TRUE }, [](const Entities e) {
206 return IR::Entity::GotoWithArg(e[2], EQ_SYM_INDEX_JUMP_IF_TRUE, e[0].primaryArg());
207 } },
208 Rule { { LOAD_CONST, NEQ, POP_JUMP_IF_TRUE }, [](const Entities e) {
209 return IR::Entity::GotoWithArg(e[2], NEQ_CONST_JUMP_IF_TRUE, e[0].primaryArg());
210 } },
211 Rule { { LOAD_FAST, NEQ, POP_JUMP_IF_FALSE }, [](const Entities e) {
212 return IR::Entity::GotoWithArg(e[2], NEQ_SYM_JUMP_IF_FALSE, e[0].primaryArg());
213 } },
214 // LOAD_FAST id, LOAD_FAST id2, AT
215 // ---> AT_SYM_SYM id id2
219 // LOAD_FAST sym, TYPE, LOAD_CONST cst, EQ
220 // ---> CHECK_TYPE_OF sym, cst
221 // also works with LOAD_CONST cst, LOAD_FAST sym, TYPE, EQ, but args will be flipped
222 Rule { { LOAD_FAST, TYPE, LOAD_CONST, EQ }, [](const Entities e) {
223 return IR::Entity(CHECK_TYPE_OF, e[0].primaryArg(), e[2].primaryArg());
224 } },
225 Rule { { LOAD_CONST, LOAD_FAST, TYPE, EQ }, [](const Entities e) {
226 return IR::Entity(CHECK_TYPE_OF, e[1].primaryArg(), e[0].primaryArg());
227 } },
228 Rule { { LOAD_FAST_BY_INDEX, TYPE, LOAD_CONST, EQ }, [](const Entities e) {
229 return IR::Entity(CHECK_TYPE_OF_BY_INDEX, e[0].primaryArg(), e[2].primaryArg());
230 } },
231 Rule { { LOAD_CONST, LOAD_FAST_BY_INDEX, TYPE, EQ }, [](const Entities e) {
232 return IR::Entity(CHECK_TYPE_OF_BY_INDEX, e[1].primaryArg(), e[0].primaryArg());
233 } },
234 // ---
235 Rule { { LOAD_FAST_BY_INDEX, LEN, STORE }, [](const Entities e) {
236 return IR::Entity(STORE_LEN, e[0].primaryArg(), e[2].primaryArg());
237 } },
238 Rule { { LOAD_FAST, LEN, LT, POP_JUMP_IF_FALSE }, [](const Entities e) {
239 return IR::Entity::GotoWithArg(e[3], LT_LEN_SYM_JUMP_IF_FALSE, e[0].primaryArg());
240 } },
241 };
242
243 const auto math_ops = { ADD, SUB, MUL, DIV };
244 for (const auto& one : math_ops)
245 {
246 for (const auto& two : math_ops)
247 {
248 for (const auto& three : math_ops)
249 m_ruleset.emplace_back(Rule { { one, two, three }, fuseMathOps3 });
250 }
251 }
252
253 for (const auto& one : math_ops)
254 {
255 for (const auto& two : math_ops)
256 m_ruleset.emplace_back(Rule { { one, two }, fuseMathOps2 });
257 }
258
259 m_logger.debug("Loaded {} rules", m_ruleset.size());
260 }
261
262 void IROptimizer::process(const std::vector<IR::Block>& pages, const std::vector<std::string>& symbols, const std::vector<ValTableElem>& values)
263 {
264 m_logger.traceStart("process");
265 m_symbols = symbols;
266 m_values = values;
267
268 for (const auto& block : pages)
269 {
270 m_ir.emplace_back();
271 IR::Block& current_block = m_ir.back();
272
273 std::size_t i = 0;
274 const std::size_t end = block.size();
275
276 while (i < end)
277 {
278 std::optional<EntityWithOffset> maybe_compacted = replaceWithRules(
279 std::span(
280 block.begin() + static_cast<IR::Block::difference_type>(i),
281 block.size() - i),
282 i);
283
284 if (maybe_compacted.has_value())
285 {
286 auto [entity, offset] = maybe_compacted.value();
287 current_block.emplace_back(entity);
288 i += offset;
289 }
290 else
291 {
292 current_block.emplace_back(block[i]);
293 ++i;
294 }
295 }
296 }
297
299 }
300
301 const std::vector<IR::Block>& IROptimizer::intermediateRepresentation() const noexcept
302 {
303 return m_ir;
304 }
305
306 bool IROptimizer::match(const std::vector<Instruction>& expected_insts, const std::span<const IR::Entity> entities) const
307 {
308 if (expected_insts.size() > entities.size())
309 return false;
310
311 std::size_t i = 0;
312 while (i < expected_insts.size())
313 {
314 if (expected_insts[i] != entities[i].inst())
315 return false;
316 if (entities[i].kind() != IR::Kind::Label)
317 ++i;
318 }
319
320 return true;
321 }
322
323 bool IROptimizer::canBeOptimizedSafely(std::span<const IR::Entity> entities, std::size_t window_size) const
324 {
325 // check that we can actually safely apply the optimization on the given instructions
326 return std::ranges::none_of(
327 entities | std::ranges::views::take(window_size),
328 [](const IR::Entity& entity) {
329 return entity.primaryArg() > IR::MaxValueForDualArg;
330 });
331 }
332
333 std::optional<EntityWithOffset> IROptimizer::replaceWithRules(const std::span<const IR::Entity> entities, const std::size_t position_in_block)
334 {
335 for (const auto& [expected, condition, createReplacement] : m_ruleset)
336 {
337 if (match(expected, entities) && condition(entities, position_in_block))
338 {
339 const std::size_t window_size = expected.size();
340 if (!canBeOptimizedSafely(entities, window_size))
341 return std::nullopt; // no need to try other optimizations, they won't be applied either
342
343 auto output = createReplacement(entities);
344
345 if (const auto it = std::ranges::find_if(entities, [](const auto& entity) {
346 return entity.hasValidSourceLocation();
347 });
348 it != entities.end())
349 output.setSourceLocation(it->filename(), it->sourceLine());
350
351 return EntityWithOffset { output, window_size };
352 }
353 }
354
355 return std::nullopt;
356 }
357
358 bool IROptimizer::isPositiveNumberInlinable(const uint16_t id) const
359 {
360 if (std::cmp_less(id, m_values.size()) && m_values[id].type == ValTableElemType::Number)
361 {
362 const double val = std::get<double>(m_values[id].value);
363 return val >= 0.0 &&
365 static_cast<double>(static_cast<long>(val)) == val;
366 }
367 return false;
368 }
369
370 bool IROptimizer::isSmallerNumberInlinable(const uint16_t id) const
371 {
372 if (std::cmp_less(id, m_values.size()) && m_values[id].type == ValTableElemType::Number)
373 {
374 const double val = std::get<double>(m_values[id].value) + IR::MaxValueForSmallNumber;
375 return val >= 0.0 &&
377 static_cast<double>(static_cast<long>(val)) == val;
378 }
379 return false;
380 }
381
382 bool IROptimizer::isNumberEqualTo(const uint16_t id, const int number) const
383 {
384 if (std::cmp_less(id, m_values.size()) && m_values[id].type == ValTableElemType::Number)
385 {
386 const double val = std::get<double>(m_values[id].value);
387 return static_cast<double>(static_cast<long>(val)) == val &&
388 static_cast<int>(val) == number;
389 }
390 return false;
391 }
392
393 uint16_t IROptimizer::numberAsArg(const uint16_t id) const
394 {
395 return static_cast<uint16_t>(std::get<double>(m_values[id].value));
396 }
397
398 uint16_t IROptimizer::smallerNumberAsArg(const uint16_t id) const
399 {
400 return static_cast<uint16_t>(std::get<double>(m_values[id].value) + IR::MaxValueForSmallNumber);
401 }
402}
Host the declaration of all the ArkScript builtins.
Optimize IR based on IR entity grouped by 2 (or more)
std::vector< ValTableElem > m_values
bool isSmallerNumberInlinable(uint16_t id) const
std::optional< EntityWithOffset > replaceWithRules(std::span< const IR::Entity > entities, const std::size_t position_in_block)
bool isPositiveNumberInlinable(uint16_t id) const
std::vector< IR::Block > m_ir
IROptimizer(unsigned debug)
Create a new IROptimizer.
uint16_t numberAsArg(uint16_t id) const
bool canBeOptimizedSafely(std::span< const IR::Entity > entities, std::size_t window_size) const
std::span< const IR::Entity > Entities
const std::vector< IR::Block > & intermediateRepresentation() const noexcept
Return the IR blocks (one per scope)
std::vector< Rule > m_ruleset
void process(const std::vector< IR::Block > &pages, const std::vector< std::string > &symbols, const std::vector< ValTableElem > &values)
Turn a given IR into bytecode.
std::vector< std::string > m_symbols
bool match(const std::vector< Instruction > &expected_insts, std::span< const IR::Entity > entities) const
uint16_t smallerNumberAsArg(uint16_t id) const
bool isNumberEqualTo(uint16_t id, int number) const
static Entity GotoWithArg(const Entity &label, Instruction inst, uint16_t primary_arg)
Definition Entity.cpp:47
uint16_t primaryArg() const
Definition Entity.hpp:69
void debug(const Logger::MessageAndLocation &data, Args &&... args)
Write a debug level log using fmtlib.
Definition Logger.hpp:96
void traceStart(std::string &&trace_name)
Definition Logger.hpp:109
An interface to describe compiler passes.
Definition Pass.hpp:24
constexpr uint16_t MaxValueForSmallNumber
Definition Entity.hpp:37
std::vector< Entity > Block
Definition Entity.hpp:94
constexpr uint16_t MaxValueForDualArg
The maximum value an argument can have when an IR entity has two arguments.
Definition Entity.hpp:36
IR::Entity fuseMathOps3(const std::span< const IR::Entity > e)
IR::Entity fuseMathOps2(const std::span< const IR::Entity > e)
@ CALL_BUILTIN_WITHOUT_RETURN_ADDRESS