Coverage Report

Created: 2022-08-24 06:52

/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
}