Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/ControlFlowSimplifier.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
#include <libyul/optimiser/ControlFlowSimplifier.h>
19
#include <libyul/optimiser/Semantics.h>
20
#include <libyul/optimiser/OptimiserStep.h>
21
#include <libyul/AST.h>
22
#include <libyul/Utilities.h>
23
#include <libyul/Dialect.h>
24
#include <libsolutil/CommonData.h>
25
#include <libsolutil/Visitor.h>
26
27
#include <range/v3/action/remove_if.hpp>
28
29
using namespace solidity;
30
using namespace solidity::util;
31
using namespace solidity::yul;
32
33
using OptionalStatements = std::optional<std::vector<Statement>>;
34
35
namespace
36
{
37
38
ExpressionStatement makeDiscardCall(
39
  langutil::DebugData::ConstPtr const& _debugData,
40
  BuiltinHandle const& _discardFunction,
41
  Expression&& _expression
42
)
43
72.1k
{
44
72.1k
  return {_debugData, FunctionCall{
45
72.1k
    _debugData,
46
72.1k
    BuiltinName{_debugData, _discardFunction},
47
72.1k
    {std::move(_expression)}
48
72.1k
  }};
49
72.1k
}
50
51
void removeEmptyDefaultFromSwitch(Switch& _switchStmt)
52
309k
{
53
309k
  ranges::actions::remove_if(
54
309k
    _switchStmt.cases,
55
929k
    [](Case const& _case) { return !_case.value && _case.body.statements.empty(); }
56
309k
  );
57
309k
}
58
59
void removeEmptyCasesFromSwitch(Switch& _switchStmt)
60
309k
{
61
309k
  if (hasDefaultCase(_switchStmt))
62
215k
    return;
63
64
94.7k
  ranges::actions::remove_if(
65
94.7k
    _switchStmt.cases,
66
182k
    [](Case const& _case) { return _case.body.statements.empty(); }
67
94.7k
  );
68
94.7k
}
69
70
}
71
72
void ControlFlowSimplifier::run(OptimiserStepContext& _context, Block& _ast)
73
622k
{
74
622k
  ControlFlowSimplifier{_context.dialect}(_ast);
75
622k
}
76
77
void ControlFlowSimplifier::operator()(Block& _block)
78
8.57M
{
79
8.57M
  simplify(_block.statements);
80
8.57M
}
81
82
void ControlFlowSimplifier::operator()(FunctionDefinition& _funDef)
83
1.52M
{
84
1.52M
  ASTModifier::operator()(_funDef);
85
1.52M
  if (!_funDef.body.statements.empty() && std::holds_alternative<Leave>(_funDef.body.statements.back()))
86
15.3k
    _funDef.body.statements.pop_back();
87
1.52M
}
88
89
void ControlFlowSimplifier::visit(Statement& _st)
90
62.7M
{
91
62.7M
  if (std::holds_alternative<ForLoop>(_st))
92
1.33M
  {
93
1.33M
    ForLoop& forLoop = std::get<ForLoop>(_st);
94
1.33M
    yulAssert(forLoop.pre.statements.empty(), "");
95
96
1.33M
    size_t outerBreak = m_numBreakStatements;
97
1.33M
    size_t outerContinue = m_numContinueStatements;
98
1.33M
    m_numBreakStatements = 0;
99
1.33M
    m_numContinueStatements = 0;
100
101
1.33M
    ASTModifier::visit(_st);
102
103
1.33M
    if (!forLoop.body.statements.empty())
104
1.12M
    {
105
1.12M
      bool isTerminating = false;
106
1.12M
      TerminationFinder::ControlFlow controlFlow = TerminationFinder{m_dialect}.controlFlowKind(forLoop.body.statements.back());
107
1.12M
      if (controlFlow == TerminationFinder::ControlFlow::Break)
108
17.5k
      {
109
17.5k
        isTerminating = true;
110
17.5k
        --m_numBreakStatements;
111
17.5k
      }
112
1.10M
      else if (
113
1.10M
        controlFlow == TerminationFinder::ControlFlow::Terminate ||
114
1.08M
        controlFlow == TerminationFinder::ControlFlow::Leave
115
1.10M
      )
116
24.1k
        isTerminating = true;
117
118
1.12M
      if (isTerminating && m_numContinueStatements == 0 && m_numBreakStatements == 0)
119
11.9k
      {
120
11.9k
        If replacement{forLoop.debugData, std::move(forLoop.condition), std::move(forLoop.body)};
121
11.9k
        if (controlFlow == TerminationFinder::ControlFlow::Break)
122
6.17k
          replacement.body.statements.resize(replacement.body.statements.size() - 1);
123
11.9k
        _st = std::move(replacement);
124
11.9k
      }
125
1.12M
    }
126
127
1.33M
    m_numBreakStatements = outerBreak;
128
1.33M
    m_numContinueStatements = outerContinue;
129
1.33M
  }
130
61.3M
  else
131
61.3M
    ASTModifier::visit(_st);
132
62.7M
}
133
134
void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
135
8.66M
{
136
8.66M
  GenericVisitor visitor{
137
8.66M
    VisitorFallback<OptionalStatements>{},
138
8.66M
    [&](If& _ifStmt) -> OptionalStatements {
139
910k
      if (_ifStmt.body.statements.empty() && m_dialect.discardFunctionHandle())
140
30.1k
      {
141
30.1k
        OptionalStatements s = std::vector<Statement>{};
142
30.1k
        s->emplace_back(makeDiscardCall(
143
30.1k
          _ifStmt.debugData,
144
30.1k
          *m_dialect.discardFunctionHandle(),
145
30.1k
          std::move(*_ifStmt.condition)
146
30.1k
        ));
147
30.1k
        return s;
148
30.1k
      }
149
880k
      return {};
150
910k
    },
151
8.66M
    [&](Switch& _switchStmt) -> OptionalStatements {
152
309k
      removeEmptyDefaultFromSwitch(_switchStmt);
153
309k
      removeEmptyCasesFromSwitch(_switchStmt);
154
155
309k
      if (_switchStmt.cases.empty())
156
21.2k
        return reduceNoCaseSwitch(_switchStmt);
157
288k
      else if (_switchStmt.cases.size() == 1)
158
30.1k
        return reduceSingleCaseSwitch(_switchStmt);
159
160
258k
      return {};
161
309k
    }
162
8.66M
  };
163
8.66M
  iterateReplacing(
164
8.66M
    _statements,
165
8.66M
    [&](Statement& _stmt) -> OptionalStatements
166
62.7M
    {
167
62.7M
      OptionalStatements result = std::visit(visitor, _stmt);
168
62.7M
      if (result)
169
81.5k
        simplify(*result);
170
62.7M
      else
171
62.7M
        visit(_stmt);
172
62.7M
      return result;
173
62.7M
    }
174
8.66M
  );
175
8.66M
}
176
177
OptionalStatements ControlFlowSimplifier::reduceNoCaseSwitch(Switch& _switchStmt) const
178
21.2k
{
179
21.2k
  yulAssert(_switchStmt.cases.empty(), "Expected no case!");
180
21.2k
  std::optional<BuiltinHandle> discardFunctionHandle =
181
21.2k
    m_dialect.discardFunctionHandle();
182
21.2k
  if (!discardFunctionHandle)
183
0
    return {};
184
185
21.2k
  return make_vector<Statement>(makeDiscardCall(
186
21.2k
    debugDataOf(*_switchStmt.expression),
187
21.2k
    *discardFunctionHandle,
188
21.2k
    std::move(*_switchStmt.expression)
189
21.2k
  ));
190
21.2k
}
191
192
OptionalStatements ControlFlowSimplifier::reduceSingleCaseSwitch(Switch& _switchStmt) const
193
30.1k
{
194
30.1k
  yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
195
196
30.1k
  auto& switchCase = _switchStmt.cases.front();
197
30.1k
  langutil::DebugData::ConstPtr debugData = debugDataOf(*_switchStmt.expression);
198
30.1k
  if (switchCase.value)
199
9.38k
  {
200
9.38k
    if (!m_dialect.equalityFunctionHandle())
201
0
      return {};
202
9.38k
    BuiltinName const builtinName{debugData, *m_dialect.equalityFunctionHandle()};
203
9.38k
    return make_vector<Statement>(If{
204
9.38k
      std::move(_switchStmt.debugData),
205
9.38k
      std::make_unique<Expression>(FunctionCall{
206
9.38k
        debugData,
207
9.38k
        builtinName,
208
9.38k
        {std::move(*switchCase.value), std::move(*_switchStmt.expression)}
209
9.38k
      }),
210
9.38k
      std::move(switchCase.body)
211
9.38k
    });
212
9.38k
  }
213
20.7k
  else
214
20.7k
  {
215
20.7k
    if (!m_dialect.discardFunctionHandle())
216
0
      return {};
217
218
20.7k
    return make_vector<Statement>(
219
20.7k
      makeDiscardCall(
220
20.7k
        debugData,
221
20.7k
        *m_dialect.discardFunctionHandle(),
222
20.7k
        std::move(*_switchStmt.expression)
223
20.7k
      ),
224
20.7k
      std::move(switchCase.body)
225
20.7k
    );
226
20.7k
  }
227
30.1k
}
228