Coverage Report

Created: 2025-09-04 07:34

/src/solidity/libyul/optimiser/Semantics.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
 * Specific AST walkers that collect semantical facts.
20
 */
21
22
#include <libyul/optimiser/Semantics.h>
23
24
#include <libyul/optimiser/OptimizerUtilities.h>
25
#include <libyul/Exceptions.h>
26
#include <libyul/AST.h>
27
#include <libyul/Dialect.h>
28
#include <libyul/Utilities.h>
29
30
#include <libevmasm/SemanticInformation.h>
31
32
#include <libsolutil/CommonData.h>
33
#include <libsolutil/Algorithms.h>
34
35
#include <limits>
36
37
using namespace solidity;
38
using namespace solidity::yul;
39
40
41
SideEffectsCollector::SideEffectsCollector(
42
    Dialect const& _dialect,
43
    Expression const& _expression,
44
    std::map<FunctionHandle, SideEffects> const* _functionSideEffects
45
):
46
  SideEffectsCollector(_dialect, _functionSideEffects)
47
51.6M
{
48
51.6M
  visit(_expression);
49
51.6M
}
50
51
SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Statement const& _statement):
52
  SideEffectsCollector(_dialect)
53
0
{
54
0
  visit(_statement);
55
0
}
56
57
SideEffectsCollector::SideEffectsCollector(
58
  Dialect const& _dialect,
59
  Block const& _ast,
60
  std::map<FunctionHandle, SideEffects> const* _functionSideEffects
61
):
62
  SideEffectsCollector(_dialect, _functionSideEffects)
63
1.52M
{
64
1.52M
  operator()(_ast);
65
1.52M
}
66
67
SideEffectsCollector::SideEffectsCollector(
68
  Dialect const& _dialect,
69
  ForLoop const& _ast,
70
  std::map<FunctionHandle, SideEffects> const* _functionSideEffects
71
):
72
  SideEffectsCollector(_dialect, _functionSideEffects)
73
490k
{
74
490k
  operator()(_ast);
75
490k
}
76
77
void SideEffectsCollector::operator()(FunctionCall const& _functionCall)
78
92.4M
{
79
92.4M
  ASTWalker::operator()(_functionCall);
80
81
92.4M
  FunctionHandle functionHandle = functionNameToHandle(_functionCall.functionName);
82
92.4M
  if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect))
83
81.7M
    m_sideEffects += builtin->sideEffects;
84
10.7M
  else if (m_functionSideEffects && m_functionSideEffects->count(functionHandle))
85
8.78M
    m_sideEffects += m_functionSideEffects->at(functionHandle);
86
1.92M
  else
87
1.92M
    m_sideEffects += SideEffects::worst();
88
92.4M
}
89
90
bool MSizeFinder::containsMSize(Dialect const& _dialect, Block const& _ast)
91
1.24M
{
92
1.24M
  MSizeFinder finder(_dialect);
93
1.24M
  finder(_ast);
94
1.24M
  return finder.m_msizeFound;
95
1.24M
}
96
97
bool MSizeFinder::containsMSize(Object const& _object)
98
35.7k
{
99
35.7k
  yulAssert(_object.dialect());
100
35.7k
  if (containsMSize(*_object.dialect(), _object.code()->root()))
101
2.83k
    return true;
102
103
32.9k
  for (std::shared_ptr<ObjectNode> const& node: _object.subObjects)
104
21.0k
    if (auto const* object = dynamic_cast<Object const*>(node.get()))
105
10.3k
      if (containsMSize(*object))
106
346
        return true;
107
108
32.6k
  return false;
109
32.9k
}
110
111
void MSizeFinder::operator()(FunctionCall const& _functionCall)
112
44.8M
{
113
44.8M
  ASTWalker::operator()(_functionCall);
114
115
44.8M
  if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect))
116
38.0M
    if (builtin->isMSize)
117
150k
      m_msizeFound = true;
118
44.8M
}
119
120
std::map<FunctionHandle, SideEffects> SideEffectsPropagator::sideEffects(
121
  Dialect const& _dialect,
122
  CallGraph const& _directCallGraph
123
)
124
1.93M
{
125
  // Any loop currently makes a function non-movable, because
126
  // it could be a non-terminating loop.
127
  // The same is true for any function part of a call cycle.
128
  // In the future, we should refine that, because the property
129
  // is actually a bit different from "not movable".
130
131
1.93M
  std::map<FunctionHandle, SideEffects> ret;
132
1.93M
  for (auto const& function: _directCallGraph.functionsWithLoops)
133
1.88M
  {
134
1.88M
    ret[function].movable = false;
135
1.88M
    ret[function].canBeRemoved = false;
136
1.88M
    ret[function].canBeRemovedIfNoMSize = false;
137
1.88M
    ret[function].cannotLoop = false;
138
1.88M
  }
139
140
1.93M
  for (auto const& function: _directCallGraph.recursiveFunctions())
141
174k
  {
142
174k
    ret[function].movable = false;
143
174k
    ret[function].canBeRemoved = false;
144
174k
    ret[function].canBeRemovedIfNoMSize = false;
145
174k
    ret[function].cannotLoop = false;
146
174k
  }
147
148
1.93M
  for (auto const& call: _directCallGraph.functionCalls)
149
7.09M
  {
150
7.09M
    FunctionHandle funName = call.first;
151
7.09M
    SideEffects sideEffects;
152
59.1M
    auto _visit = [&, visited = std::set<FunctionHandle>{}](FunctionHandle _function, auto&& _recurse) mutable {
153
59.1M
      if (!visited.insert(_function).second)
154
18.8M
        return;
155
40.2M
      if (sideEffects == SideEffects::worst())
156
2.07M
        return;
157
38.1M
      if (BuiltinHandle const* builtinHandle = std::get_if<BuiltinHandle>(&_function))
158
28.3M
        sideEffects += _dialect.builtin(*builtinHandle).sideEffects;
159
9.83M
      else
160
9.83M
      {
161
9.83M
        if (ret.count(_function))
162
6.84M
          sideEffects += ret[_function];
163
9.83M
        for (FunctionHandle const& callee: _directCallGraph.functionCalls.at(_function))
164
30.5M
          _recurse(callee, _recurse);
165
9.83M
      }
166
38.1M
    };
167
7.09M
    for (auto const& _v: call.second)
168
28.6M
      _visit(_v, _visit);
169
7.09M
    ret[funName] += sideEffects;
170
7.09M
  }
171
1.93M
  return ret;
172
1.93M
}
173
174
MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expression):
175
  MovableChecker(_dialect)
176
46.7M
{
177
46.7M
  visit(_expression);
178
46.7M
}
179
180
void MovableChecker::operator()(Identifier const& _identifier)
181
137M
{
182
137M
  SideEffectsCollector::operator()(_identifier);
183
137M
  m_variableReferences.emplace(_identifier.name);
184
137M
}
185
186
void MovableChecker::visit(Statement const&)
187
0
{
188
0
  assertThrow(false, OptimizerException, "Movability for statement requested.");
189
0
}
190
191
std::pair<TerminationFinder::ControlFlow, size_t> TerminationFinder::firstUnconditionalControlFlowChange(
192
  std::vector<Statement> const& _statements
193
)
194
4.35M
{
195
37.4M
  for (size_t i = 0; i < _statements.size(); ++i)
196
33.8M
  {
197
33.8M
    ControlFlow controlFlow = controlFlowKind(_statements[i]);
198
33.8M
    if (controlFlow != ControlFlow::FlowOut)
199
779k
      return {controlFlow, i};
200
33.8M
  }
201
3.57M
  return {ControlFlow::FlowOut, std::numeric_limits<size_t>::max()};
202
4.35M
}
203
204
TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement)
205
35.7M
{
206
35.7M
  if (
207
35.7M
    std::holds_alternative<VariableDeclaration>(_statement) &&
208
35.7M
    std::get<VariableDeclaration>(_statement).value &&
209
35.7M
    containsNonContinuingFunctionCall(*std::get<VariableDeclaration>(_statement).value)
210
35.7M
  )
211
11.7k
    return ControlFlow::Terminate;
212
35.6M
  else if (
213
35.6M
    std::holds_alternative<Assignment>(_statement) &&
214
35.6M
    containsNonContinuingFunctionCall(*std::get<Assignment>(_statement).value)
215
35.6M
  )
216
600
    return ControlFlow::Terminate;
217
35.6M
  else if (
218
35.6M
    std::holds_alternative<ExpressionStatement>(_statement) &&
219
35.6M
    containsNonContinuingFunctionCall(std::get<ExpressionStatement>(_statement).expression)
220
35.6M
  )
221
730k
    return ControlFlow::Terminate;
222
34.9M
  else if (std::holds_alternative<Break>(_statement))
223
787k
    return ControlFlow::Break;
224
34.1M
  else if (std::holds_alternative<Continue>(_statement))
225
78.0k
    return ControlFlow::Continue;
226
34.0M
  else if (std::holds_alternative<Leave>(_statement))
227
231k
    return ControlFlow::Leave;
228
33.8M
  else
229
33.8M
    return ControlFlow::FlowOut;
230
35.7M
}
231
232
bool TerminationFinder::containsNonContinuingFunctionCall(Expression const& _expr)
233
50.8M
{
234
50.8M
  if (auto functionCall = std::get_if<FunctionCall>(&_expr))
235
10.8M
  {
236
10.8M
    for (auto const& arg: functionCall->arguments)
237
18.9M
      if (containsNonContinuingFunctionCall(arg))
238
2.07k
        return true;
239
240
10.8M
    if (BuiltinFunction const* builtin = resolveBuiltinFunction(functionCall->functionName, m_dialect))
241
9.42M
      return !builtin->controlFlowSideEffects.canContinue;
242
243
1.46M
    yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
244
1.46M
    auto const& name = std::get<Identifier>(functionCall->functionName).name;
245
1.46M
    if (m_functionSideEffects && m_functionSideEffects->count(name))
246
1.43M
      return !m_functionSideEffects->at(name).canContinue;
247
1.46M
  }
248
40.0M
  return false;
249
50.8M
}