/src/solidity/libyul/optimiser/SSATransform.cpp
Line | Count | Source |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** |
19 | | * Optimiser component that turns subsequent assignments to variable declarations |
20 | | * and assignments. |
21 | | */ |
22 | | |
23 | | #include <libyul/optimiser/SSATransform.h> |
24 | | |
25 | | #include <libyul/optimiser/NameCollector.h> |
26 | | #include <libyul/optimiser/NameDispenser.h> |
27 | | #include <libyul/AST.h> |
28 | | |
29 | | #include <libsolutil/CommonData.h> |
30 | | |
31 | | using namespace solidity; |
32 | | using namespace solidity::yul; |
33 | | using namespace solidity::langutil; |
34 | | |
35 | | namespace |
36 | | { |
37 | | |
38 | | /** |
39 | | * First step of SSA transform: Introduces new SSA variables for each assignment or |
40 | | * declaration of a variable to be replaced. |
41 | | */ |
42 | | class IntroduceSSA: public ASTModifier |
43 | | { |
44 | | public: |
45 | | explicit IntroduceSSA( |
46 | | NameDispenser& _nameDispenser, |
47 | | std::set<YulName> const& _variablesToReplace |
48 | | ): |
49 | 374k | m_nameDispenser(_nameDispenser), |
50 | 374k | m_variablesToReplace(_variablesToReplace) |
51 | 374k | { } |
52 | | |
53 | | void operator()(Block& _block) override; |
54 | | |
55 | | private: |
56 | | NameDispenser& m_nameDispenser; |
57 | | std::set<YulName> const& m_variablesToReplace; |
58 | | }; |
59 | | |
60 | | |
61 | | void IntroduceSSA::operator()(Block& _block) |
62 | 4.52M | { |
63 | 4.52M | util::iterateReplacing( |
64 | 4.52M | _block.statements, |
65 | 4.52M | [&](Statement& _s) -> std::optional<std::vector<Statement>> |
66 | 50.1M | { |
67 | 50.1M | if (std::holds_alternative<VariableDeclaration>(_s)) |
68 | 40.3M | { |
69 | 40.3M | VariableDeclaration& varDecl = std::get<VariableDeclaration>(_s); |
70 | 40.3M | if (varDecl.value) |
71 | 40.3M | visit(*varDecl.value); |
72 | | |
73 | 40.3M | bool needToReplaceSome = false; |
74 | 40.3M | for (auto const& var: varDecl.variables) |
75 | 40.7M | if (m_variablesToReplace.count(var.name)) |
76 | 701k | needToReplaceSome = true; |
77 | 40.3M | if (!needToReplaceSome) |
78 | 39.6M | return {}; |
79 | | |
80 | | // Replace "let a := v" by "let a_1 := v let a := a_1" |
81 | | // Replace "let a, b := v" by "let a_1, b_1 := v let a := a_1 let b := b_2" |
82 | 700k | langutil::DebugData::ConstPtr debugData = varDecl.debugData; |
83 | 700k | std::vector<Statement> statements; |
84 | 700k | statements.emplace_back(VariableDeclaration{debugData, {}, std::move(varDecl.value)}); |
85 | 700k | NameWithDebugDataList newVariables; |
86 | 700k | for (auto const& var: varDecl.variables) |
87 | 703k | { |
88 | 703k | YulName oldName = var.name; |
89 | 703k | YulName newName = m_nameDispenser.newName(oldName); |
90 | 703k | newVariables.emplace_back(NameWithDebugData{debugData, newName}); |
91 | 703k | statements.emplace_back(VariableDeclaration{ |
92 | 703k | debugData, |
93 | 703k | {NameWithDebugData{debugData, oldName}}, |
94 | 703k | std::make_unique<Expression>(Identifier{debugData, newName}) |
95 | 703k | }); |
96 | 703k | } |
97 | 700k | std::get<VariableDeclaration>(statements.front()).variables = std::move(newVariables); |
98 | 700k | return { std::move(statements) }; |
99 | 40.3M | } |
100 | 9.76M | else if (std::holds_alternative<Assignment>(_s)) |
101 | 1.16M | { |
102 | 1.16M | Assignment& assignment = std::get<Assignment>(_s); |
103 | 1.16M | visit(*assignment.value); |
104 | 1.16M | for (auto const& var: assignment.variableNames) |
105 | 1.16M | assertThrow(m_variablesToReplace.count(var.name), OptimizerException, ""); |
106 | | |
107 | | // Replace "a := v" by "let a_1 := v a := v" |
108 | | // Replace "a, b := v" by "let a_1, b_1 := v a := a_1 b := b_2" |
109 | 1.16M | langutil::DebugData::ConstPtr debugData = assignment.debugData; |
110 | 1.16M | std::vector<Statement> statements; |
111 | 1.16M | statements.emplace_back(VariableDeclaration{debugData, {}, std::move(assignment.value)}); |
112 | 1.16M | NameWithDebugDataList newVariables; |
113 | 1.16M | for (auto const& var: assignment.variableNames) |
114 | 1.16M | { |
115 | 1.16M | YulName oldName = var.name; |
116 | 1.16M | YulName newName = m_nameDispenser.newName(oldName); |
117 | 1.16M | newVariables.emplace_back(NameWithDebugData{debugData, newName}); |
118 | 1.16M | statements.emplace_back(Assignment{ |
119 | 1.16M | debugData, |
120 | 1.16M | {Identifier{debugData, oldName}}, |
121 | 1.16M | std::make_unique<Expression>(Identifier{debugData, newName}) |
122 | 1.16M | }); |
123 | 1.16M | } |
124 | 1.16M | std::get<VariableDeclaration>(statements.front()).variables = std::move(newVariables); |
125 | 1.16M | return { std::move(statements) }; |
126 | 1.16M | } |
127 | 8.59M | else |
128 | 8.59M | visit(_s); |
129 | 8.59M | return {}; |
130 | 50.1M | } |
131 | 4.52M | ); |
132 | 4.52M | } |
133 | | |
134 | | /** |
135 | | * Second step of SSA transform: Introduces new SSA variables at each control-flow join |
136 | | * and at the beginning of functions. |
137 | | */ |
138 | | class IntroduceControlFlowSSA: public ASTModifier |
139 | | { |
140 | | public: |
141 | | explicit IntroduceControlFlowSSA( |
142 | | NameDispenser& _nameDispenser, |
143 | | std::set<YulName> const& _variablesToReplace |
144 | | ): |
145 | 374k | m_nameDispenser(_nameDispenser), |
146 | 374k | m_variablesToReplace(_variablesToReplace) |
147 | 374k | { } |
148 | | |
149 | | void operator()(FunctionDefinition& _function) override; |
150 | | void operator()(ForLoop& _forLoop) override; |
151 | | void operator()(Switch& _switch) override; |
152 | | void operator()(Block& _block) override; |
153 | | |
154 | | private: |
155 | | NameDispenser& m_nameDispenser; |
156 | | std::set<YulName> const& m_variablesToReplace; |
157 | | /// Variables (that are to be replaced) currently in scope. |
158 | | std::set<YulName> m_variablesInScope; |
159 | | /// Variables that do not have a specific value. |
160 | | util::UniqueVector<YulName> m_variablesToReassign; |
161 | | }; |
162 | | |
163 | | void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function) |
164 | 974k | { |
165 | 974k | std::set<YulName> varsInScope; |
166 | 974k | std::swap(varsInScope, m_variablesInScope); |
167 | 974k | util::UniqueVector<YulName> toReassign; |
168 | 974k | std::swap(toReassign, m_variablesToReassign); |
169 | | |
170 | 974k | for (auto const& param: _function.parameters) |
171 | 1.10M | if (m_variablesToReplace.count(param.name)) |
172 | 31.3k | { |
173 | 31.3k | m_variablesInScope.insert(param.name); |
174 | 31.3k | m_variablesToReassign.pushBack(param.name); |
175 | 31.3k | } |
176 | | |
177 | 974k | ASTModifier::operator()(_function); |
178 | | |
179 | 974k | m_variablesInScope = std::move(varsInScope); |
180 | 974k | m_variablesToReassign = std::move(toReassign); |
181 | 974k | } |
182 | | |
183 | | void IntroduceControlFlowSSA::operator()(ForLoop& _for) |
184 | 543k | { |
185 | 543k | yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run."); |
186 | | |
187 | 543k | for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post)) |
188 | 693k | if (util::contains(m_variablesInScope,var)) |
189 | 559k | m_variablesToReassign.pushBack(var); |
190 | | |
191 | 543k | (*this)(_for.body); |
192 | 543k | (*this)(_for.post); |
193 | 543k | } |
194 | | |
195 | | void IntroduceControlFlowSSA::operator()(Switch& _switch) |
196 | 184k | { |
197 | 184k | yulAssert(m_variablesToReassign.empty(), ""); |
198 | | |
199 | 184k | util::UniqueVector<YulName> toReassign; |
200 | 184k | for (auto& c: _switch.cases) |
201 | 545k | { |
202 | 545k | (*this)(c.body); |
203 | 545k | toReassign.pushBack(m_variablesToReassign); |
204 | 545k | } |
205 | | |
206 | 184k | m_variablesToReassign.pushBack(toReassign); |
207 | 184k | } |
208 | | |
209 | | void IntroduceControlFlowSSA::operator()(Block& _block) |
210 | 3.97M | { |
211 | 3.97M | util::UniqueVector<YulName> variablesDeclaredHere; |
212 | 3.97M | util::UniqueVector<YulName> assignedVariables; |
213 | | |
214 | 3.97M | util::iterateReplacing( |
215 | 3.97M | _block.statements, |
216 | 3.97M | [&](Statement& _s) -> std::optional<std::vector<Statement>> |
217 | 52.0M | { |
218 | 52.0M | std::vector<Statement> toPrepend; |
219 | 52.0M | for (YulName toReassign: m_variablesToReassign) |
220 | 1.62M | { |
221 | 1.62M | YulName newName = m_nameDispenser.newName(toReassign); |
222 | 1.62M | toPrepend.emplace_back(VariableDeclaration{ |
223 | 1.62M | debugDataOf(_s), |
224 | 1.62M | {NameWithDebugData{debugDataOf(_s), newName}}, |
225 | 1.62M | std::make_unique<Expression>(Identifier{debugDataOf(_s), toReassign}) |
226 | 1.62M | }); |
227 | 1.62M | assignedVariables.pushBack(toReassign); |
228 | 1.62M | } |
229 | 52.0M | m_variablesToReassign.clear(); |
230 | | |
231 | 52.0M | if (std::holds_alternative<VariableDeclaration>(_s)) |
232 | 42.2M | { |
233 | 42.2M | VariableDeclaration& varDecl = std::get<VariableDeclaration>(_s); |
234 | 42.2M | for (auto const& var: varDecl.variables) |
235 | 42.6M | if (m_variablesToReplace.count(var.name)) |
236 | 701k | { |
237 | 701k | variablesDeclaredHere.pushBack(var.name); |
238 | 701k | m_variablesInScope.insert(var.name); |
239 | 701k | } |
240 | 42.2M | } |
241 | 9.76M | else if (std::holds_alternative<Assignment>(_s)) |
242 | 1.16M | { |
243 | 1.16M | Assignment& assignment = std::get<Assignment>(_s); |
244 | 1.16M | for (auto const& var: assignment.variableNames) |
245 | 1.16M | if (m_variablesToReplace.count(var.name)) |
246 | 1.16M | assignedVariables.pushBack(var.name); |
247 | 1.16M | } |
248 | 8.59M | else |
249 | 8.59M | visit(_s); |
250 | | |
251 | 52.0M | if (toPrepend.empty()) |
252 | 50.8M | return {}; |
253 | 1.17M | else |
254 | 1.17M | { |
255 | 1.17M | toPrepend.emplace_back(std::move(_s)); |
256 | 1.17M | return {std::move(toPrepend)}; |
257 | 1.17M | } |
258 | 52.0M | } |
259 | 3.97M | ); |
260 | | |
261 | 3.97M | m_variablesToReassign.pushBack(assignedVariables); |
262 | 3.97M | m_variablesInScope -= variablesDeclaredHere.contents(); |
263 | 3.97M | m_variablesToReassign.removeAll(variablesDeclaredHere.contents()); |
264 | 3.97M | } |
265 | | |
266 | | /** |
267 | | * Third step of SSA transform: Replace the references to variables-to-be-replaced |
268 | | * by their current values. |
269 | | */ |
270 | | class PropagateValues: public ASTModifier |
271 | | { |
272 | | public: |
273 | | explicit PropagateValues(std::set<YulName> const& _variablesToReplace): |
274 | 374k | m_variablesToReplace(_variablesToReplace) |
275 | 374k | { } |
276 | | |
277 | | void operator()(Identifier& _identifier) override; |
278 | | void operator()(VariableDeclaration& _varDecl) override; |
279 | | void operator()(Assignment& _assignment) override; |
280 | | void operator()(ForLoop& _for) override; |
281 | | void operator()(Block& _block) override; |
282 | | |
283 | | private: |
284 | | /// This is a set of all variables that are assigned to anywhere in the code. |
285 | | /// Variables that are only declared but never re-assigned are not touched. |
286 | | std::set<YulName> const& m_variablesToReplace; |
287 | | std::map<YulName, YulName> m_currentVariableValues; |
288 | | std::set<YulName> m_clearAtEndOfBlock; |
289 | | }; |
290 | | |
291 | | void PropagateValues::operator()(Identifier& _identifier) |
292 | 52.2M | { |
293 | 52.2M | if (m_currentVariableValues.count(_identifier.name)) |
294 | 3.44M | _identifier.name = m_currentVariableValues[_identifier.name]; |
295 | 52.2M | } |
296 | | |
297 | | void PropagateValues::operator()(VariableDeclaration& _varDecl) |
298 | 43.8M | { |
299 | 43.8M | ASTModifier::operator()(_varDecl); |
300 | | |
301 | 43.8M | if (_varDecl.variables.size() != 1) |
302 | 154k | return; |
303 | | |
304 | 43.7M | YulName variable = _varDecl.variables.front().name; |
305 | 43.7M | if (m_variablesToReplace.count(variable)) |
306 | 701k | { |
307 | | // `let a := a_1` - regular declaration of non-SSA variable |
308 | 701k | yulAssert(std::holds_alternative<Identifier>(*_varDecl.value), ""); |
309 | 701k | m_currentVariableValues[variable] = std::get<Identifier>(*_varDecl.value).name; |
310 | 701k | m_clearAtEndOfBlock.insert(variable); |
311 | 701k | } |
312 | 43.0M | else if (_varDecl.value && std::holds_alternative<Identifier>(*_varDecl.value)) |
313 | 24.9M | { |
314 | | // `let a_1 := a` - assignment to SSA variable after a branch. |
315 | 24.9M | YulName value = std::get<Identifier>(*_varDecl.value).name; |
316 | 24.9M | if (m_variablesToReplace.count(value)) |
317 | 1.62M | { |
318 | | // This is safe because `a_1` is not a "variable to replace" and thus |
319 | | // will not be re-assigned. |
320 | 1.62M | m_currentVariableValues[value] = variable; |
321 | 1.62M | m_clearAtEndOfBlock.insert(value); |
322 | 1.62M | } |
323 | 24.9M | } |
324 | 43.7M | } |
325 | | |
326 | | |
327 | | void PropagateValues::operator()(Assignment& _assignment) |
328 | 1.16M | { |
329 | 1.16M | visit(*_assignment.value); |
330 | | |
331 | 1.16M | if (_assignment.variableNames.size() != 1) |
332 | 0 | return; |
333 | 1.16M | YulName name = _assignment.variableNames.front().name; |
334 | 1.16M | if (!m_variablesToReplace.count(name)) |
335 | 0 | return; |
336 | | |
337 | 1.16M | yulAssert(_assignment.value && std::holds_alternative<Identifier>(*_assignment.value), ""); |
338 | 1.16M | m_currentVariableValues[name] = std::get<Identifier>(*_assignment.value).name; |
339 | 1.16M | m_clearAtEndOfBlock.insert(name); |
340 | 1.16M | } |
341 | | |
342 | | void PropagateValues::operator()(ForLoop& _for) |
343 | 543k | { |
344 | 543k | yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run."); |
345 | | |
346 | 543k | for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post)) |
347 | 693k | m_currentVariableValues.erase(var); |
348 | | |
349 | 543k | visit(*_for.condition); |
350 | 543k | (*this)(_for.body); |
351 | 543k | (*this)(_for.post); |
352 | 543k | } |
353 | | |
354 | | void PropagateValues::operator()(Block& _block) |
355 | 3.97M | { |
356 | 3.97M | std::set<YulName> clearAtParentBlock = std::move(m_clearAtEndOfBlock); |
357 | 3.97M | m_clearAtEndOfBlock.clear(); |
358 | | |
359 | 3.97M | ASTModifier::operator()(_block); |
360 | | |
361 | 3.97M | for (auto const& var: m_clearAtEndOfBlock) |
362 | 2.23M | m_currentVariableValues.erase(var); |
363 | | |
364 | 3.97M | m_clearAtEndOfBlock = std::move(clearAtParentBlock); |
365 | 3.97M | } |
366 | | |
367 | | } |
368 | | |
369 | | void SSATransform::run(OptimiserStepContext& _context, Block& _ast) |
370 | 374k | { |
371 | 374k | std::set<YulName> assignedVariables = assignedVariableNames(_ast); |
372 | 374k | IntroduceSSA{_context.dispenser, assignedVariables}(_ast); |
373 | 374k | IntroduceControlFlowSSA{_context.dispenser, assignedVariables}(_ast); |
374 | 374k | PropagateValues{assignedVariables}(_ast); |
375 | 374k | } |
376 | | |
377 | | |