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