ArkScript
A small, fast, functional and scripting language for video games
IROptimizer.cpp
Go to the documentation of this file.
2
3#include <cassert>
4#include <utility>
6
7namespace Ark::internal
8{
10 {
12 std::size_t offset;
13 };
14
15 IROptimizer::IROptimizer(const unsigned debug) :
16 m_logger("IROptimizer", debug)
17 {
19 Rule {
21 Rule {
23 Rule {
25 Rule {
27 Rule {
29 Rule {
31 Rule {
33 Rule {
34 { BUILTIN, CALL }, CALL_BUILTIN, [](const Entities& entities) {
35 return Builtins::builtins[entities[0].primaryArg()].second.isFunction();
36 } }
37 };
38
40 // LOAD_SYMBOL a / LOAD_SYMBOL_BY_INDEX index
41 // LOAD_CONST n (1)
42 // ADD / SUB
43 // ---> INCREMENT / DECREMENT a value
44 Rule {
45 { LOAD_CONST, LOAD_SYMBOL, ADD }, INCREMENT, [this](const Entities& e) {
46 return isPositiveNumberInlinable(e[0].primaryArg());
47 },
48 [this](const Entities& e) {
49 return std::make_pair(e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
50 } },
51 Rule { { LOAD_SYMBOL, LOAD_CONST, ADD }, INCREMENT, [this](const Entities& e) {
52 return isPositiveNumberInlinable(e[1].primaryArg());
53 },
54 [this](const Entities& e) {
55 return std::make_pair(e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
56 } },
57 Rule { { LOAD_SYMBOL, LOAD_CONST, SUB }, DECREMENT, [this](const Entities& e) {
58 return isPositiveNumberInlinable(e[1].primaryArg());
59 },
60 [this](const Entities& e) {
61 return std::make_pair(e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
62 } },
64 return isPositiveNumberInlinable(e[0].primaryArg());
65 },
66 [this](const Entities& e) {
67 return std::make_pair(e[1].primaryArg(), numberAsArg(e[0].primaryArg()));
68 } },
70 return isPositiveNumberInlinable(e[1].primaryArg());
71 },
72 [this](const Entities& e) {
73 return std::make_pair(e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
74 } },
76 return isPositiveNumberInlinable(e[1].primaryArg());
77 },
78 [this](const Entities& e) {
79 return std::make_pair(e[0].primaryArg(), numberAsArg(e[1].primaryArg()));
80 } },
81 // LOAD_SYMBOL list
82 // TAIL / HEAD
83 // STORE / SET_VAL a
84 // ---> STORE_TAIL list a ; STORE_HEAD ; SET_VAL_TAIL ; SET_VAL_HEAD
85 Rule { .expected = { LOAD_SYMBOL, TAIL, STORE }, .replacement = STORE_TAIL, .createReplacement = [](const Entities& e) {
86 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
87 } },
88 Rule { .expected = { LOAD_SYMBOL, TAIL, SET_VAL }, .replacement = SET_VAL_TAIL, .createReplacement = [](const Entities& e) {
89 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
90 } },
91 Rule { .expected = { LOAD_SYMBOL, HEAD, STORE }, .replacement = STORE_HEAD, .createReplacement = [](const Entities& e) {
92 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
93 } },
94 Rule { .expected = { LOAD_SYMBOL, HEAD, SET_VAL }, .replacement = SET_VAL_HEAD, .createReplacement = [](const Entities& e) {
95 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
96 } },
97 Rule { .expected = { LOAD_SYMBOL_BY_INDEX, TAIL, STORE }, .replacement = STORE_TAIL_BY_INDEX, .createReplacement = [](const Entities& e) {
98 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
99 } },
100 Rule { .expected = { LOAD_SYMBOL_BY_INDEX, TAIL, SET_VAL }, .replacement = SET_VAL_TAIL_BY_INDEX, .createReplacement = [](const Entities& e) {
101 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
102 } },
103 Rule { .expected = { LOAD_SYMBOL_BY_INDEX, HEAD, STORE }, .replacement = STORE_HEAD_BY_INDEX, .createReplacement = [](const Entities& e) {
104 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
105 } },
106 Rule { .expected = { LOAD_SYMBOL_BY_INDEX, HEAD, SET_VAL }, .replacement = SET_VAL_HEAD_BY_INDEX, .createReplacement = [](const Entities& e) {
107 return std::make_pair(e[0].primaryArg(), e[2].primaryArg());
108 } }
109 };
110 }
111
112 void IROptimizer::process(const std::vector<IR::Block>& pages, const std::vector<std::string>& symbols, const std::vector<ValTableElem>& values)
113 {
114 m_logger.traceStart("process");
115 m_symbols = symbols;
116 m_values = values;
117
118 auto map = []<typename T>(const std::optional<T>& opt, auto&& lambda) -> decltype(std::optional(lambda(opt.value()))) {
119 if (opt.has_value())
120 return lambda(opt.value());
121 return std::nullopt;
122 };
123
124 auto or_else = []<typename T>(const std::optional<T>& opt, auto&& lambda) -> std::optional<T> {
125 if (!opt.has_value())
126 return lambda();
127 return opt;
128 };
129
130 for (const auto& block : pages)
131 {
132 m_ir.emplace_back();
133 IR::Block& current_block = m_ir.back();
134
135 std::size_t i = 0;
136 const std::size_t end = block.size();
137
138 while (i < end)
139 {
140 std::optional<EntityWithOffset> maybe_compacted = std::nullopt;
141
142 if (i + 1 < end)
143 maybe_compacted = map(
144 replaceWithRules(m_ruleset_two, { block[i], block[i + 1] }),
145 [](const auto& entity) {
146 return std::make_optional<EntityWithOffset>(entity, 2);
147 });
148 if (i + 2 < end)
149 maybe_compacted = or_else(
150 maybe_compacted,
151 [&, this]() {
152 return map(
153 replaceWithRules(m_ruleset_three, { block[i], block[i + 1], block[i + 2] }),
154 [](const auto& entity) {
155 return std::make_optional<EntityWithOffset>(entity, 3);
156 });
157 });
158
159 if (maybe_compacted.has_value())
160 {
161 auto [entity, offset] = maybe_compacted.value();
162 current_block.emplace_back(entity);
163 i += offset;
164 }
165 else
166 {
167 current_block.emplace_back(block[i]);
168 ++i;
169 }
170 }
171 }
172
174 }
175
176 const std::vector<IR::Block>& IROptimizer::intermediateRepresentation() const noexcept
177 {
178 return m_ir;
179 }
180
181 bool IROptimizer::match(const std::vector<Instruction>& expected_insts, const Entities& entities) const
182 {
183 assert(expected_insts.size() == entities.size() && "Mismatching size between expected instructions and given entities");
184
185 for (std::size_t i = 0; i < expected_insts.size(); ++i)
186 {
187 if (expected_insts[i] != entities[i].inst())
188 return false;
189 }
190
191 return true;
192 }
193
194 std::optional<IR::Entity> IROptimizer::replaceWithRules(const std::vector<Rule>& rules, const Entities& entities)
195 {
196 for (auto&& entity : entities)
197 {
198 if (entity.primaryArg() > IR::MaxValueForDualArg)
199 return std::nullopt;
200 }
201
202 for (const auto& [expected, replacement, condition, createReplacement] : rules)
203 {
204 if (match(expected, entities) && condition(entities))
205 {
206 auto [first, second] = createReplacement(entities);
207 auto output = IR::Entity(replacement, first, second);
208
209 if (auto it = std::ranges::find_if(entities, [](const auto& entity) {
210 return entity.hasValidSourceLocation();
211 });
212 it != entities.end())
213 output.setSourceLocation(it->filename(), it->sourceLine());
214
215 return output;
216 }
217 }
218
219 return std::nullopt;
220 }
221
222 bool IROptimizer::isPositiveNumberInlinable(const uint16_t id) const
223 {
224 if (std::cmp_less(id, m_values.size()) && m_values[id].type == ValTableElemType::Number)
225 {
226 const double val = std::get<double>(m_values[id].value);
227 return val >= 0.0 &&
229 static_cast<double>(static_cast<long>(val)) == val;
230 }
231 return false;
232 }
233
234 uint16_t IROptimizer::numberAsArg(const uint16_t id) const
235 {
236 return static_cast<uint16_t>(std::get<double>(m_values[id].value));
237 }
238}
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 isPositiveNumberInlinable(uint16_t id) const
std::vector< Rule > m_ruleset_two
std::vector< IR::Block > m_ir
IROptimizer(unsigned debug)
Create a new IROptimizer.
uint16_t numberAsArg(uint16_t id) const
bool match(const std::vector< Instruction > &expected_insts, const Entities &entities) const
std::optional< IR::Entity > replaceWithRules(const std::vector< Rule > &rules, const Entities &entities)
const std::vector< IR::Block > & intermediateRepresentation() const noexcept
Return the IR blocks (one per scope)
std::vector< IR::Entity > Entities
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
std::vector< Rule > m_ruleset_three
void traceStart(std::string &&trace_name)
Definition Logger.hpp:74
ARK_API const std::vector< std::pair< std::string, Value > > builtins
std::vector< Entity > Block
Definition Entity.hpp:83
constexpr uint16_t MaxValueForDualArg
The maximum value an argument can have when an IR entity has two arguments.
Definition Entity.hpp:36