Coverage Report

Created: 2022-08-24 06:52

/src/solidity/libyul/optimiser/UnusedPruner.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 removes unused variables and functions.
19
 */
20
21
#include <libyul/optimiser/UnusedPruner.h>
22
23
#include <libyul/optimiser/CallGraphGenerator.h>
24
#include <libyul/optimiser/FunctionGrouper.h>
25
#include <libyul/optimiser/NameCollector.h>
26
#include <libyul/optimiser/Semantics.h>
27
#include <libyul/optimiser/OptimizerUtilities.h>
28
#include <libyul/Exceptions.h>
29
#include <libyul/AST.h>
30
#include <libyul/Dialect.h>
31
#include <libyul/SideEffects.h>
32
33
using namespace std;
34
using namespace solidity;
35
using namespace solidity::yul;
36
37
void UnusedPruner::run(OptimiserStepContext& _context, Block& _ast)
38
0
{
39
0
  UnusedPruner::runUntilStabilisedOnFullAST(_context.dialect, _ast, _context.reservedIdentifiers);
40
0
  FunctionGrouper::run(_context, _ast);
41
0
}
42
43
UnusedPruner::UnusedPruner(
44
  Dialect const& _dialect,
45
  Block& _ast,
46
  bool _allowMSizeOptimization,
47
  map<YulString, SideEffects> const* _functionSideEffects,
48
  set<YulString> const& _externallyUsedFunctions
49
):
50
  m_dialect(_dialect),
51
  m_allowMSizeOptimization(_allowMSizeOptimization),
52
  m_functionSideEffects(_functionSideEffects)
53
0
{
54
0
  m_references = ReferencesCounter::countReferences(_ast);
55
0
  for (auto const& f: _externallyUsedFunctions)
56
0
    ++m_references[f];
57
0
}
58
59
UnusedPruner::UnusedPruner(
60
  Dialect const& _dialect,
61
  FunctionDefinition& _function,
62
  bool _allowMSizeOptimization,
63
  set<YulString> const& _externallyUsedFunctions
64
):
65
  m_dialect(_dialect),
66
  m_allowMSizeOptimization(_allowMSizeOptimization)
67
0
{
68
0
  m_references = ReferencesCounter::countReferences(_function);
69
0
  for (auto const& f: _externallyUsedFunctions)
70
0
    ++m_references[f];
71
0
}
72
73
void UnusedPruner::operator()(Block& _block)
74
0
{
75
0
  for (auto&& statement: _block.statements)
76
0
    if (holds_alternative<FunctionDefinition>(statement))
77
0
    {
78
0
      FunctionDefinition& funDef = std::get<FunctionDefinition>(statement);
79
0
      if (!used(funDef.name))
80
0
      {
81
0
        subtractReferences(ReferencesCounter::countReferences(funDef.body));
82
0
        statement = Block{std::move(funDef.debugData), {}};
83
0
      }
84
0
    }
85
0
    else if (holds_alternative<VariableDeclaration>(statement))
86
0
    {
87
0
      VariableDeclaration& varDecl = std::get<VariableDeclaration>(statement);
88
      // Multi-variable declarations are special. We can only remove it
89
      // if all variables are unused and the right-hand-side is either
90
      // movable or it returns a single value. In the latter case, we
91
      // replace `let a := f()` by `pop(f())` (in pure Yul, this will be
92
      // `drop(f())`).
93
0
      if (std::none_of(
94
0
        varDecl.variables.begin(),
95
0
        varDecl.variables.end(),
96
0
        [&](TypedName const& _typedName) { return used(_typedName.name); }
97
0
      ))
98
0
      {
99
0
        if (!varDecl.value)
100
0
          statement = Block{std::move(varDecl.debugData), {}};
101
0
        else if (
102
0
          SideEffectsCollector(m_dialect, *varDecl.value, m_functionSideEffects).
103
0
          canBeRemoved(m_allowMSizeOptimization)
104
0
        )
105
0
        {
106
0
          subtractReferences(ReferencesCounter::countReferences(*varDecl.value));
107
0
          statement = Block{std::move(varDecl.debugData), {}};
108
0
        }
109
0
        else if (varDecl.variables.size() == 1 && m_dialect.discardFunction(varDecl.variables.front().type))
110
0
          statement = ExpressionStatement{varDecl.debugData, FunctionCall{
111
0
            varDecl.debugData,
112
0
            {varDecl.debugData, m_dialect.discardFunction(varDecl.variables.front().type)->name},
113
0
            {*std::move(varDecl.value)}
114
0
          }};
115
0
      }
116
0
    }
117
0
    else if (holds_alternative<ExpressionStatement>(statement))
118
0
    {
119
0
      ExpressionStatement& exprStmt = std::get<ExpressionStatement>(statement);
120
0
      if (
121
0
        SideEffectsCollector(m_dialect, exprStmt.expression, m_functionSideEffects).
122
0
        canBeRemoved(m_allowMSizeOptimization)
123
0
      )
124
0
      {
125
0
        subtractReferences(ReferencesCounter::countReferences(exprStmt.expression));
126
0
        statement = Block{std::move(exprStmt.debugData), {}};
127
0
      }
128
0
    }
129
130
0
  removeEmptyBlocks(_block);
131
132
0
  ASTModifier::operator()(_block);
133
0
}
134
135
void UnusedPruner::runUntilStabilised(
136
  Dialect const& _dialect,
137
  Block& _ast,
138
  bool _allowMSizeOptimization,
139
  map<YulString, SideEffects> const* _functionSideEffects,
140
  set<YulString> const& _externallyUsedFunctions
141
)
142
0
{
143
0
  while (true)
144
0
  {
145
0
    UnusedPruner pruner(
146
0
      _dialect, _ast, _allowMSizeOptimization, _functionSideEffects,
147
0
              _externallyUsedFunctions);
148
0
    pruner(_ast);
149
0
    if (!pruner.shouldRunAgain())
150
0
      return;
151
0
  }
152
0
}
153
154
void UnusedPruner::runUntilStabilisedOnFullAST(
155
  Dialect const& _dialect,
156
  Block& _ast,
157
  set<YulString> const& _externallyUsedFunctions
158
)
159
0
{
160
0
  map<YulString, SideEffects> functionSideEffects =
161
0
    SideEffectsPropagator::sideEffects(_dialect, CallGraphGenerator::callGraph(_ast));
162
0
  bool allowMSizeOptimization = !MSizeFinder::containsMSize(_dialect, _ast);
163
0
  runUntilStabilised(_dialect, _ast, allowMSizeOptimization, &functionSideEffects, _externallyUsedFunctions);
164
0
}
165
166
void UnusedPruner::runUntilStabilised(
167
  Dialect const& _dialect,
168
  FunctionDefinition& _function,
169
  bool _allowMSizeOptimization,
170
  set<YulString> const& _externallyUsedFunctions
171
)
172
0
{
173
0
  while (true)
174
0
  {
175
0
    UnusedPruner pruner(_dialect, _function, _allowMSizeOptimization, _externallyUsedFunctions);
176
0
    pruner(_function);
177
0
    if (!pruner.shouldRunAgain())
178
0
      return;
179
0
  }
180
0
}
181
182
bool UnusedPruner::used(YulString _name) const
183
0
{
184
0
  return m_references.count(_name) && m_references.at(_name) > 0;
185
0
}
186
187
void UnusedPruner::subtractReferences(map<YulString, size_t> const& _subtrahend)
188
0
{
189
0
  for (auto const& ref: _subtrahend)
190
0
  {
191
0
    assertThrow(m_references.count(ref.first), OptimizerException, "");
192
0
    assertThrow(m_references.at(ref.first) >= ref.second, OptimizerException, "");
193
0
    m_references[ref.first] -= ref.second;
194
0
    m_shouldRunAgain = true;
195
0
  }
196
0
}