/src/solidity/libyul/optimiser/FullInliner.cpp
Line | Count | Source (jump to first uncovered line) |
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 performs function inlining for arbitrary functions. |
20 | | */ |
21 | | |
22 | | #include <libyul/optimiser/FullInliner.h> |
23 | | |
24 | | #include <libyul/optimiser/ASTCopier.h> |
25 | | #include <libyul/optimiser/CallGraphGenerator.h> |
26 | | #include <libyul/optimiser/FunctionCallFinder.h> |
27 | | #include <libyul/optimiser/NameCollector.h> |
28 | | #include <libyul/optimiser/Metrics.h> |
29 | | #include <libyul/optimiser/SSAValueTracker.h> |
30 | | #include <libyul/optimiser/Semantics.h> |
31 | | #include <libyul/optimiser/CallGraphGenerator.h> |
32 | | #include <libyul/backends/evm/EVMDialect.h> |
33 | | #include <libyul/Exceptions.h> |
34 | | #include <libyul/AST.h> |
35 | | #include <libyul/Dialect.h> |
36 | | |
37 | | #include <libsolutil/CommonData.h> |
38 | | #include <libsolutil/Visitor.h> |
39 | | |
40 | | using namespace std; |
41 | | using namespace solidity; |
42 | | using namespace solidity::yul; |
43 | | |
44 | | void FullInliner::run(OptimiserStepContext& _context, Block& _ast) |
45 | 0 | { |
46 | 0 | FullInliner inliner{_ast, _context.dispenser, _context.dialect}; |
47 | 0 | inliner.run(Pass::InlineTiny); |
48 | 0 | inliner.run(Pass::InlineRest); |
49 | 0 | } |
50 | | |
51 | | FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& _dialect): |
52 | | m_ast(_ast), |
53 | | m_recursiveFunctions(CallGraphGenerator::callGraph(_ast).recursiveFunctions()), |
54 | | m_nameDispenser(_dispenser), |
55 | | m_dialect(_dialect) |
56 | 0 | { |
57 | | |
58 | | // Determine constants |
59 | 0 | SSAValueTracker tracker; |
60 | 0 | tracker(m_ast); |
61 | 0 | for (auto const& ssaValue: tracker.values()) |
62 | 0 | if (ssaValue.second && holds_alternative<Literal>(*ssaValue.second)) |
63 | 0 | m_constants.emplace(ssaValue.first); |
64 | | |
65 | | // Store size of global statements. |
66 | 0 | m_functionSizes[YulString{}] = CodeSize::codeSize(_ast); |
67 | 0 | map<YulString, size_t> references = ReferencesCounter::countReferences(m_ast); |
68 | 0 | for (auto& statement: m_ast.statements) |
69 | 0 | { |
70 | 0 | if (!holds_alternative<FunctionDefinition>(statement)) |
71 | 0 | continue; |
72 | 0 | FunctionDefinition& fun = std::get<FunctionDefinition>(statement); |
73 | 0 | m_functions[fun.name] = &fun; |
74 | 0 | if (LeaveFinder::containsLeave(fun)) |
75 | 0 | m_noInlineFunctions.insert(fun.name); |
76 | | // Always inline functions that are only called once. |
77 | 0 | if (references[fun.name] == 1) |
78 | 0 | m_singleUse.emplace(fun.name); |
79 | 0 | updateCodeSize(fun); |
80 | 0 | } |
81 | | |
82 | | // Check for memory guard. |
83 | 0 | vector<FunctionCall*> memoryGuardCalls = FunctionCallFinder::run( |
84 | 0 | _ast, |
85 | 0 | "memoryguard"_yulstring |
86 | 0 | ); |
87 | | // We will perform less aggressive inlining, if no ``memoryguard`` call is found. |
88 | 0 | if (!memoryGuardCalls.empty()) |
89 | 0 | m_hasMemoryGuard = true; |
90 | 0 | } |
91 | | |
92 | | void FullInliner::run(Pass _pass) |
93 | 0 | { |
94 | 0 | m_pass = _pass; |
95 | | |
96 | | // Note that the order of inlining can result in very different code. |
97 | | // Since AST IDs and thus function names depend on whether or not a contract |
98 | | // is compiled together with other source files, a change in AST IDs |
99 | | // should have as little an impact as possible. This is the case |
100 | | // if we handle inlining in source (and thus, for the IR generator, |
101 | | // function name) order. |
102 | | // We use stable_sort below to keep the inlining order of two functions |
103 | | // with the same depth. |
104 | 0 | map<YulString, size_t> depths = callDepths(); |
105 | 0 | vector<FunctionDefinition*> functions; |
106 | 0 | for (auto& statement: m_ast.statements) |
107 | 0 | if (holds_alternative<FunctionDefinition>(statement)) |
108 | 0 | functions.emplace_back(&std::get<FunctionDefinition>(statement)); |
109 | 0 | std::stable_sort(functions.begin(), functions.end(), [depths]( |
110 | 0 | FunctionDefinition const* _a, |
111 | 0 | FunctionDefinition const* _b |
112 | 0 | ) { |
113 | 0 | return depths.at(_a->name) < depths.at(_b->name); |
114 | 0 | }); |
115 | 0 | for (FunctionDefinition* fun: functions) |
116 | 0 | { |
117 | 0 | handleBlock(fun->name, fun->body); |
118 | 0 | updateCodeSize(*fun); |
119 | 0 | } |
120 | |
|
121 | 0 | for (auto& statement: m_ast.statements) |
122 | 0 | if (holds_alternative<Block>(statement)) |
123 | 0 | handleBlock({}, std::get<Block>(statement)); |
124 | 0 | } |
125 | | |
126 | | map<YulString, size_t> FullInliner::callDepths() const |
127 | 0 | { |
128 | 0 | CallGraph cg = CallGraphGenerator::callGraph(m_ast); |
129 | 0 | cg.functionCalls.erase(""_yulstring); |
130 | | |
131 | | // Remove calls to builtin functions. |
132 | 0 | for (auto& call: cg.functionCalls) |
133 | 0 | for (auto it = call.second.begin(); it != call.second.end();) |
134 | 0 | if (m_dialect.builtin(*it)) |
135 | 0 | it = call.second.erase(it); |
136 | 0 | else |
137 | 0 | ++it; |
138 | |
|
139 | 0 | map<YulString, size_t> depths; |
140 | 0 | size_t currentDepth = 0; |
141 | |
|
142 | 0 | while (true) |
143 | 0 | { |
144 | 0 | vector<YulString> removed; |
145 | 0 | for (auto it = cg.functionCalls.begin(); it != cg.functionCalls.end();) |
146 | 0 | { |
147 | 0 | auto const& [fun, callees] = *it; |
148 | 0 | if (callees.empty()) |
149 | 0 | { |
150 | 0 | removed.emplace_back(fun); |
151 | 0 | depths[fun] = currentDepth; |
152 | 0 | it = cg.functionCalls.erase(it); |
153 | 0 | } |
154 | 0 | else |
155 | 0 | ++it; |
156 | 0 | } |
157 | |
|
158 | 0 | for (auto& call: cg.functionCalls) |
159 | 0 | call.second -= removed; |
160 | |
|
161 | 0 | currentDepth++; |
162 | |
|
163 | 0 | if (removed.empty()) |
164 | 0 | break; |
165 | 0 | } |
166 | | |
167 | | // Only recursive functions left here. |
168 | 0 | for (auto const& fun: cg.functionCalls) |
169 | 0 | depths[fun.first] = currentDepth; |
170 | |
|
171 | 0 | return depths; |
172 | 0 | } |
173 | | |
174 | | bool FullInliner::shallInline(FunctionCall const& _funCall, YulString _callSite) |
175 | 0 | { |
176 | | // No recursive inlining |
177 | 0 | if (_funCall.functionName.name == _callSite) |
178 | 0 | return false; |
179 | | |
180 | 0 | FunctionDefinition* calledFunction = function(_funCall.functionName.name); |
181 | 0 | if (!calledFunction) |
182 | 0 | return false; |
183 | | |
184 | 0 | if (m_noInlineFunctions.count(_funCall.functionName.name) || recursive(*calledFunction)) |
185 | 0 | return false; |
186 | | |
187 | | // Inline really, really tiny functions |
188 | 0 | size_t size = m_functionSizes.at(calledFunction->name); |
189 | 0 | if (size <= 1) |
190 | 0 | return true; |
191 | | |
192 | | // In the first pass, only inline tiny functions. |
193 | 0 | if (m_pass == Pass::InlineTiny) |
194 | 0 | return false; |
195 | | |
196 | 0 | bool aggressiveInlining = true; |
197 | |
|
198 | 0 | if ( |
199 | 0 | EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&m_dialect); |
200 | 0 | !evmDialect || !evmDialect->providesObjectAccess() || evmDialect->evmVersion() <= langutil::EVMVersion::homestead() |
201 | 0 | ) |
202 | | // No aggressive inlining with the old code transform. |
203 | 0 | aggressiveInlining = false; |
204 | | |
205 | | // No aggressive inlining, if we cannot perform stack-to-memory. |
206 | 0 | if (!m_hasMemoryGuard || m_recursiveFunctions.count(_callSite)) |
207 | 0 | aggressiveInlining = false; |
208 | |
|
209 | 0 | if (!aggressiveInlining && m_functionSizes.at(_callSite) > 45) |
210 | 0 | return false; |
211 | | |
212 | 0 | if (m_singleUse.count(calledFunction->name)) |
213 | 0 | return true; |
214 | | |
215 | | // Constant arguments might provide a means for further optimization, so they cause a bonus. |
216 | 0 | bool constantArg = false; |
217 | 0 | for (auto const& argument: _funCall.arguments) |
218 | 0 | if (holds_alternative<Literal>(argument) || ( |
219 | 0 | holds_alternative<Identifier>(argument) && |
220 | 0 | m_constants.count(std::get<Identifier>(argument).name) |
221 | 0 | )) |
222 | 0 | { |
223 | 0 | constantArg = true; |
224 | 0 | break; |
225 | 0 | } |
226 | |
|
227 | 0 | return (size < (aggressiveInlining ? 8u : 6u) || (constantArg && size < (aggressiveInlining ? 16u : 12u))); |
228 | 0 | } |
229 | | |
230 | | void FullInliner::tentativelyUpdateCodeSize(YulString _function, YulString _callSite) |
231 | 0 | { |
232 | 0 | m_functionSizes.at(_callSite) += m_functionSizes.at(_function); |
233 | 0 | } |
234 | | |
235 | | void FullInliner::updateCodeSize(FunctionDefinition const& _fun) |
236 | 0 | { |
237 | 0 | m_functionSizes[_fun.name] = CodeSize::codeSize(_fun.body); |
238 | 0 | } |
239 | | |
240 | | void FullInliner::handleBlock(YulString _currentFunctionName, Block& _block) |
241 | 0 | { |
242 | 0 | InlineModifier{*this, m_nameDispenser, _currentFunctionName, m_dialect}(_block); |
243 | 0 | } |
244 | | |
245 | | bool FullInliner::recursive(FunctionDefinition const& _fun) const |
246 | 0 | { |
247 | 0 | map<YulString, size_t> references = ReferencesCounter::countReferences(_fun); |
248 | 0 | return references[_fun.name] > 0; |
249 | 0 | } |
250 | | |
251 | | void InlineModifier::operator()(Block& _block) |
252 | 0 | { |
253 | 0 | function<std::optional<vector<Statement>>(Statement&)> f = [&](Statement& _statement) -> std::optional<vector<Statement>> { |
254 | 0 | visit(_statement); |
255 | 0 | return tryInlineStatement(_statement); |
256 | 0 | }; |
257 | 0 | util::iterateReplacing(_block.statements, f); |
258 | 0 | } |
259 | | |
260 | | std::optional<vector<Statement>> InlineModifier::tryInlineStatement(Statement& _statement) |
261 | 0 | { |
262 | | // Only inline for expression statements, assignments and variable declarations. |
263 | 0 | Expression* e = std::visit(util::GenericVisitor{ |
264 | 0 | util::VisitorFallback<Expression*>{}, |
265 | 0 | [](ExpressionStatement& _s) { return &_s.expression; }, |
266 | 0 | [](Assignment& _s) { return _s.value.get(); }, |
267 | 0 | [](VariableDeclaration& _s) { return _s.value.get(); } |
268 | 0 | }, _statement); |
269 | 0 | if (e) |
270 | 0 | { |
271 | | // Only inline direct function calls. |
272 | 0 | FunctionCall* funCall = std::visit(util::GenericVisitor{ |
273 | 0 | util::VisitorFallback<FunctionCall*>{}, |
274 | 0 | [](FunctionCall& _e) { return &_e; } |
275 | 0 | }, *e); |
276 | 0 | if (funCall && m_driver.shallInline(*funCall, m_currentFunction)) |
277 | 0 | return performInline(_statement, *funCall); |
278 | 0 | } |
279 | 0 | return {}; |
280 | 0 | } |
281 | | |
282 | | vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionCall& _funCall) |
283 | 0 | { |
284 | 0 | vector<Statement> newStatements; |
285 | 0 | map<YulString, YulString> variableReplacements; |
286 | |
|
287 | 0 | FunctionDefinition* function = m_driver.function(_funCall.functionName.name); |
288 | 0 | assertThrow(!!function, OptimizerException, "Attempt to inline invalid function."); |
289 | | |
290 | 0 | m_driver.tentativelyUpdateCodeSize(function->name, m_currentFunction); |
291 | | |
292 | | // helper function to create a new variable that is supposed to model |
293 | | // an existing variable. |
294 | 0 | auto newVariable = [&](TypedName const& _existingVariable, Expression* _value) { |
295 | 0 | YulString newName = m_nameDispenser.newName(_existingVariable.name); |
296 | 0 | variableReplacements[_existingVariable.name] = newName; |
297 | 0 | VariableDeclaration varDecl{_funCall.debugData, {{_funCall.debugData, newName, _existingVariable.type}}, {}}; |
298 | 0 | if (_value) |
299 | 0 | varDecl.value = make_unique<Expression>(std::move(*_value)); |
300 | 0 | else |
301 | 0 | varDecl.value = make_unique<Expression>(m_dialect.zeroLiteralForType(varDecl.variables.front().type)); |
302 | 0 | newStatements.emplace_back(std::move(varDecl)); |
303 | 0 | }; |
304 | |
|
305 | 0 | for (size_t i = 0; i < _funCall.arguments.size(); ++i) |
306 | 0 | newVariable(function->parameters[i], &_funCall.arguments[i]); |
307 | 0 | for (auto const& var: function->returnVariables) |
308 | 0 | newVariable(var, nullptr); |
309 | |
|
310 | 0 | Statement newBody = BodyCopier(m_nameDispenser, variableReplacements)(function->body); |
311 | 0 | newStatements += std::move(std::get<Block>(newBody).statements); |
312 | |
|
313 | 0 | std::visit(util::GenericVisitor{ |
314 | 0 | util::VisitorFallback<>{}, |
315 | 0 | [&](Assignment& _assignment) |
316 | 0 | { |
317 | 0 | for (size_t i = 0; i < _assignment.variableNames.size(); ++i) |
318 | 0 | newStatements.emplace_back(Assignment{ |
319 | 0 | _assignment.debugData, |
320 | 0 | {_assignment.variableNames[i]}, |
321 | 0 | make_unique<Expression>(Identifier{ |
322 | 0 | _assignment.debugData, |
323 | 0 | variableReplacements.at(function->returnVariables[i].name) |
324 | 0 | }) |
325 | 0 | }); |
326 | 0 | }, |
327 | 0 | [&](VariableDeclaration& _varDecl) |
328 | 0 | { |
329 | 0 | for (size_t i = 0; i < _varDecl.variables.size(); ++i) |
330 | 0 | newStatements.emplace_back(VariableDeclaration{ |
331 | 0 | _varDecl.debugData, |
332 | 0 | {std::move(_varDecl.variables[i])}, |
333 | 0 | make_unique<Expression>(Identifier{ |
334 | 0 | _varDecl.debugData, |
335 | 0 | variableReplacements.at(function->returnVariables[i].name) |
336 | 0 | }) |
337 | 0 | }); |
338 | 0 | } |
339 | | // nothing to be done for expression statement |
340 | 0 | }, _statement); |
341 | 0 | return newStatements; |
342 | 0 | } |
343 | | |
344 | | Statement BodyCopier::operator()(VariableDeclaration const& _varDecl) |
345 | 0 | { |
346 | 0 | for (auto const& var: _varDecl.variables) |
347 | 0 | m_variableReplacements[var.name] = m_nameDispenser.newName(var.name); |
348 | 0 | return ASTCopier::operator()(_varDecl); |
349 | 0 | } |
350 | | |
351 | | Statement BodyCopier::operator()(FunctionDefinition const&) |
352 | 0 | { |
353 | 0 | assertThrow(false, OptimizerException, "Function hoisting has to be done before function inlining."); |
354 | 0 | return {}; |
355 | 0 | } |
356 | | |
357 | | YulString BodyCopier::translateIdentifier(YulString _name) |
358 | 0 | { |
359 | 0 | if (m_variableReplacements.count(_name)) |
360 | 0 | return m_variableReplacements.at(_name); |
361 | 0 | else |
362 | 0 | return _name; |
363 | 0 | } |