Coverage Report

Created: 2026-09-14 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/Semantics.cpp
Line
Count
Source
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
77.4M
  SideEffectsCollector(_dialect, _functionSideEffects)
47
77.4M
{
48
77.4M
  visit(_expression);
49
77.4M
}
50
51
SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Statement const& _statement):
52
0
  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
2.33M
  SideEffectsCollector(_dialect, _functionSideEffects)
63
2.33M
{
64
2.33M
  operator()(_ast);
65
2.33M
}
66
67
SideEffectsCollector::SideEffectsCollector(
68
  Dialect const& _dialect,
69
  ForLoop const& _ast,
70
  std::map<FunctionHandle, SideEffects> const* _functionSideEffects
71
):
72
713k
  SideEffectsCollector(_dialect, _functionSideEffects)
73
713k
{
74
713k
  operator()(_ast);
75
713k
}
76
77
void SideEffectsCollector::operator()(FunctionCall const& _functionCall)
78
155M
{
79
155M
  ASTWalker::operator()(_functionCall);
80
81
155M
  FunctionHandle functionHandle = functionNameToHandle(_functionCall.functionName);
82
155M
  if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect))
83
141M
    m_sideEffects += builtin->sideEffects;
84
13.7M
  else if (m_functionSideEffects && m_functionSideEffects->count(functionHandle))
85
11.5M
    m_sideEffects += m_functionSideEffects->at(functionHandle);
86
2.22M
  else
87
2.22M
    m_sideEffects += SideEffects::worst();
88
155M
}
89
90
bool MSizeFinder::containsMSize(Dialect const& _dialect, Block const& _ast)
91
2.03M
{
92
2.03M
  MSizeFinder finder(_dialect);
93
2.03M
  finder(_ast);
94
2.03M
  return finder.m_msizeFound;
95
2.03M
}
96
97
bool MSizeFinder::containsMSize(Object const& _object)
98
37.6k
{
99
37.6k
  yulAssert(_object.dialect());
100
37.6k
  if (containsMSize(*_object.dialect(), _object.code()->root()))
101
3.06k
    return true;
102
103
34.6k
  for (std::shared_ptr<ObjectNode> const& node: _object.subObjects)
104
20.2k
    if (auto const* object = dynamic_cast<Object const*>(node.get()))
105
9.88k
      if (containsMSize(*object))
106
238
        return true;
107
108
34.3k
  return false;
109
34.6k
}
110
111
void MSizeFinder::operator()(FunctionCall const& _functionCall)
112
67.4M
{
113
67.4M
  ASTWalker::operator()(_functionCall);
114
115
67.4M
  if (BuiltinFunction const* builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect))
116
58.5M
    if (builtin->isMSize)
117
458k
      m_msizeFound = true;
118
67.4M
}
119
120
std::map<FunctionHandle, SideEffects> SideEffectsPropagator::sideEffects(
121
  Dialect const& _dialect,
122
  CallGraph const& _directCallGraph
123
)
124
3.20M
{
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
3.20M
  std::map<FunctionHandle, SideEffects> ret;
132
3.20M
  for (auto const& function: _directCallGraph.functionsWithLoops)
133
2.47M
  {
134
2.47M
    ret[function].movable = false;
135
2.47M
    ret[function].canBeRemoved = false;
136
2.47M
    ret[function].canBeRemovedIfNoMSize = false;
137
2.47M
    ret[function].cannotLoop = false;
138
2.47M
  }
139
140
3.20M
  CallGraphCycles const callCycles = _directCallGraph.analyzeCallCycles();
141
3.20M
  for (auto const& function: callCycles.recursiveFunctions)
142
293k
  {
143
293k
    ret[function].movable = false;
144
293k
    ret[function].canBeRemoved = false;
145
293k
    ret[function].canBeRemovedIfNoMSize = false;
146
293k
    ret[function].cannotLoop = false;
147
293k
  }
148
149
3.20M
  for (auto const& call: _directCallGraph.functionCalls)
150
10.0M
  {
151
10.0M
    FunctionHandle funName = call.first;
152
10.0M
    SideEffects sideEffects;
153
85.3M
    auto _visit = [&, visited = std::set<FunctionHandle>{}](FunctionHandle _function, auto&& _recurse) mutable {
154
85.3M
      if (!visited.insert(_function).second)
155
26.7M
        return;
156
58.5M
      if (sideEffects == SideEffects::worst())
157
3.34M
        return;
158
55.2M
      if (BuiltinHandle const* builtinHandle = std::get_if<BuiltinHandle>(&_function))
159
42.0M
        sideEffects += _dialect.builtin(*builtinHandle).sideEffects;
160
13.1M
      else
161
13.1M
      {
162
13.1M
        if (ret.count(_function))
163
9.21M
          sideEffects += ret[_function];
164
13.1M
        for (FunctionHandle const& callee: _directCallGraph.functionCalls.at(_function))
165
42.5M
          _recurse(callee, _recurse);
166
13.1M
      }
167
55.2M
    };
168
10.0M
    for (auto const& _v: call.second)
169
42.7M
      _visit(_v, _visit);
170
10.0M
    ret[funName] += sideEffects;
171
10.0M
  }
172
3.20M
  return ret;
173
3.20M
}
174
175
MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expression):
176
91.3M
  MovableChecker(_dialect)
177
91.3M
{
178
91.3M
  visit(_expression);
179
91.3M
}
180
181
void MovableChecker::operator()(Identifier const& _identifier)
182
224M
{
183
224M
  SideEffectsCollector::operator()(_identifier);
184
224M
  m_variableReferences.emplace(_identifier.name);
185
224M
}
186
187
void MovableChecker::visit(Statement const&)
188
0
{
189
0
  assertThrow(false, OptimizerException, "Movability for statement requested.");
190
0
}
191
192
std::pair<TerminationFinder::ControlFlow, size_t> TerminationFinder::firstUnconditionalControlFlowChange(
193
  std::vector<Statement> const& _statements
194
)
195
6.23M
{
196
54.7M
  for (size_t i = 0; i < _statements.size(); ++i)
197
49.5M
  {
198
49.5M
    ControlFlow controlFlow = controlFlowKind(_statements[i]);
199
49.5M
    if (controlFlow != ControlFlow::FlowOut)
200
1.00M
      return {controlFlow, i};
201
49.5M
  }
202
5.22M
  return {ControlFlow::FlowOut, std::numeric_limits<size_t>::max()};
203
6.23M
}
204
205
TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement)
206
52.1M
{
207
52.1M
  if (
208
52.1M
    std::holds_alternative<VariableDeclaration>(_statement) &&
209
38.2M
    std::get<VariableDeclaration>(_statement).value &&
210
38.2M
    containsNonContinuingFunctionCall(*std::get<VariableDeclaration>(_statement).value)
211
52.1M
  )
212
18.0k
    return ControlFlow::Terminate;
213
52.1M
  else if (
214
52.1M
    std::holds_alternative<Assignment>(_statement) &&
215
2.07M
    containsNonContinuingFunctionCall(*std::get<Assignment>(_statement).value)
216
52.1M
  )
217
672
    return ControlFlow::Terminate;
218
52.1M
  else if (
219
52.1M
    std::holds_alternative<ExpressionStatement>(_statement) &&
220
6.23M
    containsNonContinuingFunctionCall(std::get<ExpressionStatement>(_statement).expression)
221
52.1M
  )
222
698k
    return ControlFlow::Terminate;
223
51.4M
  else if (std::holds_alternative<Break>(_statement))
224
1.44M
    return ControlFlow::Break;
225
50.0M
  else if (std::holds_alternative<Continue>(_statement))
226
175k
    return ControlFlow::Continue;
227
49.8M
  else if (std::holds_alternative<Leave>(_statement))
228
243k
    return ControlFlow::Leave;
229
49.5M
  else
230
49.5M
    return ControlFlow::FlowOut;
231
52.1M
}
232
233
bool TerminationFinder::containsNonContinuingFunctionCall(Expression const& _expr)
234
75.6M
{
235
75.6M
  if (auto functionCall = std::get_if<FunctionCall>(&_expr))
236
16.1M
  {
237
16.1M
    for (auto const& arg: functionCall->arguments)
238
29.0M
      if (containsNonContinuingFunctionCall(arg))
239
2.29k
        return true;
240
241
16.1M
    if (BuiltinFunction const* builtin = resolveBuiltinFunction(functionCall->functionName, m_dialect))
242
14.2M
      return !builtin->controlFlowSideEffects.canContinue;
243
244
1.89M
    yulAssert(std::holds_alternative<Identifier>(functionCall->functionName));
245
1.89M
    auto const& name = std::get<Identifier>(functionCall->functionName).name;
246
1.89M
    if (m_functionSideEffects && m_functionSideEffects->count(name))
247
1.86M
      return !m_functionSideEffects->at(name).canContinue;
248
1.89M
  }
249
59.5M
  return false;
250
75.6M
}