Coverage Report

Created: 2022-08-24 06:43

/src/solidity/libyul/optimiser/StackCompressor.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
/**
18
 * Optimisation stage that aggressively rematerializes certain variables ina a function to free
19
 * space on the stack until it is compilable.
20
 */
21
22
#include <libyul/optimiser/StackCompressor.h>
23
24
#include <libyul/optimiser/NameCollector.h>
25
#include <libyul/optimiser/Rematerialiser.h>
26
#include <libyul/optimiser/UnusedPruner.h>
27
#include <libyul/optimiser/Metrics.h>
28
#include <libyul/optimiser/Semantics.h>
29
30
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
31
#include <libyul/backends/evm/StackHelpers.h>
32
#include <libyul/backends/evm/StackLayoutGenerator.h>
33
34
#include <libyul/AsmAnalysis.h>
35
#include <libyul/AsmAnalysisInfo.h>
36
37
#include <libyul/CompilabilityChecker.h>
38
39
#include <libyul/AST.h>
40
41
#include <libsolutil/CommonData.h>
42
43
using namespace std;
44
using namespace solidity;
45
using namespace solidity::yul;
46
47
namespace
48
{
49
50
/**
51
 * Class that discovers all variables that can be fully eliminated by rematerialization,
52
 * and the corresponding approximate costs.
53
 *
54
 * Prerequisite: Disambiguator, Function Grouper
55
 */
56
class RematCandidateSelector: public DataFlowAnalyzer
57
{
58
public:
59
0
  explicit RematCandidateSelector(Dialect const& _dialect): DataFlowAnalyzer(_dialect, MemoryAndStorage::Ignore) {}
60
61
  /// @returns a map from function name to rematerialisation costs to a vector of variables to rematerialise
62
  /// and variables that occur in their expression.
63
  /// While the map is sorted by cost, the contained vectors are sorted by the order of occurrence.
64
  map<YulString, map<size_t, vector<YulString>>> candidates()
65
0
  {
66
0
    map<YulString, map<size_t, vector<YulString>>> cand;
67
0
    for (auto const& [functionName, candidate]: m_candidates)
68
0
    {
69
0
      if (size_t const* cost = util::valueOrNullptr(m_expressionCodeCost, candidate))
70
0
      {
71
0
        size_t numRef = m_numReferences[candidate];
72
0
        cand[functionName][*cost * numRef].emplace_back(candidate);
73
0
      }
74
0
    }
75
0
    return cand;
76
0
  }
77
78
  using DataFlowAnalyzer::operator();
79
  void operator()(FunctionDefinition& _function) override
80
0
  {
81
0
    yulAssert(m_currentFunctionName.empty());
82
0
    m_currentFunctionName = _function.name;
83
0
    DataFlowAnalyzer::operator()(_function);
84
0
    m_currentFunctionName = {};
85
0
  }
86
87
  void operator()(VariableDeclaration& _varDecl) override
88
0
  {
89
0
    DataFlowAnalyzer::operator()(_varDecl);
90
0
    if (_varDecl.variables.size() == 1)
91
0
    {
92
0
      YulString varName = _varDecl.variables.front().name;
93
0
      if (AssignedValue const* value = variableValue(varName))
94
0
      {
95
0
        yulAssert(!m_expressionCodeCost.count(varName), "");
96
0
        m_candidates.emplace_back(m_currentFunctionName, varName);
97
0
        m_expressionCodeCost[varName] = CodeCost::codeCost(m_dialect, *value->value);
98
0
      }
99
0
    }
100
0
  }
101
102
  void operator()(Assignment& _assignment) override
103
0
  {
104
0
    for (auto const& var: _assignment.variableNames)
105
0
      rematImpossible(var.name);
106
0
    DataFlowAnalyzer::operator()(_assignment);
107
0
  }
108
109
  // We use visit(Expression) because operator()(Identifier) would also
110
  // get called on left-hand-sides of assignments.
111
  void visit(Expression& _e) override
112
0
  {
113
0
    if (holds_alternative<Identifier>(_e))
114
0
    {
115
0
      YulString name = std::get<Identifier>(_e).name;
116
0
      if (m_expressionCodeCost.count(name))
117
0
      {
118
0
        if (!variableValue(name))
119
0
          rematImpossible(name);
120
0
        else
121
0
          ++m_numReferences[name];
122
0
      }
123
0
    }
124
0
    DataFlowAnalyzer::visit(_e);
125
0
  }
126
127
  /// Remove the variable from the candidate set.
128
  void rematImpossible(YulString _variable)
129
0
  {
130
0
    m_numReferences.erase(_variable);
131
0
    m_expressionCodeCost.erase(_variable);
132
0
  }
133
134
  YulString m_currentFunctionName = {};
135
136
  /// All candidate variables by function name, in order of occurrence.
137
  vector<pair<YulString, YulString>> m_candidates;
138
  /// Candidate variables and the code cost of their value.
139
  map<YulString, size_t> m_expressionCodeCost;
140
  /// Number of references to each candidate variable.
141
  map<YulString, size_t> m_numReferences;
142
};
143
144
/// Selects at most @a _numVariables among @a _candidates.
145
set<YulString> chooseVarsToEliminate(
146
  map<size_t, vector<YulString>> const& _candidates,
147
  size_t _numVariables
148
)
149
0
{
150
0
  set<YulString> varsToEliminate;
151
0
  for (auto&& [cost, candidates]: _candidates)
152
0
    for (auto&& candidate: candidates)
153
0
    {
154
0
      if (varsToEliminate.size() >= _numVariables)
155
0
        return varsToEliminate;
156
0
      varsToEliminate.insert(candidate);
157
0
    }
158
0
  return varsToEliminate;
159
0
}
160
161
void eliminateVariables(
162
  Dialect const& _dialect,
163
  Block& _ast,
164
  map<YulString, int> const& _numVariables,
165
  bool _allowMSizeOptimization
166
)
167
0
{
168
0
  RematCandidateSelector selector{_dialect};
169
0
  selector(_ast);
170
0
  map<YulString, map<size_t, vector<YulString>>> candidates = selector.candidates();
171
172
0
  set<YulString> varsToEliminate;
173
0
  for (auto const& [functionName, numVariables]: _numVariables)
174
0
  {
175
0
    yulAssert(numVariables > 0);
176
0
    varsToEliminate += chooseVarsToEliminate(candidates[functionName], static_cast<size_t>(numVariables));
177
0
  }
178
179
0
  Rematerialiser::run(_dialect, _ast, move(varsToEliminate));
180
  // Do not remove functions.
181
0
  set<YulString> allFunctions = NameCollector{_ast, NameCollector::OnlyFunctions}.names();
182
0
  UnusedPruner::runUntilStabilised(_dialect, _ast, _allowMSizeOptimization, nullptr, allFunctions);
183
0
}
184
185
void eliminateVariablesOptimizedCodegen(
186
  Dialect const& _dialect,
187
  Block& _ast,
188
  map<YulString, vector<StackLayoutGenerator::StackTooDeep>> const& _unreachables,
189
  bool _allowMSizeOptimization
190
)
191
2.30k
{
192
2.30k
  if (std::all_of(_unreachables.begin(), _unreachables.end(), [](auto const& _item) { return _item.second.empty(); }))
193
2.30k
    return;
194
195
0
  RematCandidateSelector selector{_dialect};
196
0
  selector(_ast);
197
198
0
  map<YulString, size_t> candidates;
199
0
  for (auto const& [functionName, candidatesInFunction]: selector.candidates())
200
0
    for (auto [cost, candidatesWithCost]: candidatesInFunction)
201
0
      for (auto candidate: candidatesWithCost)
202
0
        candidates[candidate] = cost;
203
204
0
  set<YulString> varsToEliminate;
205
206
  // TODO: this currently ignores the fact that variables may reference other variables we want to eliminate.
207
0
  for (auto const& [functionName, unreachables]: _unreachables)
208
0
    for (auto const& unreachable: unreachables)
209
0
    {
210
0
      map<size_t, vector<YulString>> suitableCandidates;
211
0
      size_t neededSlots = unreachable.deficit;
212
0
      for (auto varName: unreachable.variableChoices)
213
0
      {
214
0
        if (varsToEliminate.count(varName))
215
0
          --neededSlots;
216
0
        else if (size_t* cost = util::valueOrNullptr(candidates, varName))
217
0
          if (!util::contains(suitableCandidates[*cost], varName))
218
0
            suitableCandidates[*cost].emplace_back(varName);
219
0
      }
220
0
      for (auto candidatesByCost: suitableCandidates)
221
0
      {
222
0
        for (auto candidate: candidatesByCost.second)
223
0
          if (neededSlots--)
224
0
            varsToEliminate.emplace(candidate);
225
0
          else
226
0
            break;
227
0
        if (!neededSlots)
228
0
          break;
229
0
      }
230
0
    }
231
0
  Rematerialiser::run(_dialect, _ast, std::move(varsToEliminate), true);
232
  // Do not remove functions.
233
0
  set<YulString> allFunctions = NameCollector{_ast, NameCollector::OnlyFunctions}.names();
234
0
  UnusedPruner::runUntilStabilised(_dialect, _ast, _allowMSizeOptimization, nullptr, allFunctions);
235
0
}
236
237
}
238
239
bool StackCompressor::run(
240
  Dialect const& _dialect,
241
  Object& _object,
242
  bool _optimizeStackAllocation,
243
  size_t _maxIterations
244
)
245
5.08k
{
246
5.08k
  yulAssert(
247
5.08k
    _object.code &&
248
5.08k
    _object.code->statements.size() > 0 && holds_alternative<Block>(_object.code->statements.at(0)),
249
5.08k
    "Need to run the function grouper before the stack compressor."
250
5.08k
  );
251
5.08k
  bool usesOptimizedCodeGenerator = false;
252
5.08k
  if (auto evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
253
5.08k
    usesOptimizedCodeGenerator =
254
5.08k
      _optimizeStackAllocation &&
255
5.08k
      evmDialect->evmVersion().canOverchargeGasForCall() &&
256
5.08k
      evmDialect->providesObjectAccess();
257
5.08k
  bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code);
258
5.08k
  if (usesOptimizedCodeGenerator)
259
2.30k
  {
260
2.30k
    yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
261
2.30k
    unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code);
262
2.30k
    eliminateVariablesOptimizedCodegen(
263
2.30k
      _dialect,
264
2.30k
      *_object.code,
265
2.30k
      StackLayoutGenerator::reportStackTooDeep(*cfg),
266
2.30k
      allowMSizeOptimzation
267
2.30k
    );
268
2.30k
  }
269
2.78k
  else
270
2.78k
    for (size_t iterations = 0; iterations < _maxIterations; iterations++)
271
2.78k
    {
272
2.78k
      map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
273
2.78k
      if (stackSurplus.empty())
274
2.78k
        return true;
275
0
      eliminateVariables(
276
0
        _dialect,
277
0
        *_object.code,
278
0
        stackSurplus,
279
0
        allowMSizeOptimzation
280
0
      );
281
0
    }
282
2.30k
  return false;
283
5.08k
}
284