Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/ExpressionJoiner.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
 * Optimiser component that undoes what the ExpressionSplitter did, i.e.
20
 * it more or less inlines variable declarations.
21
 */
22
23
#include <libyul/optimiser/ExpressionJoiner.h>
24
25
#include <libyul/optimiser/FunctionGrouper.h>
26
#include <libyul/optimiser/NameCollector.h>
27
#include <libyul/optimiser/OptimizerUtilities.h>
28
#include <libyul/Exceptions.h>
29
#include <libyul/AST.h>
30
31
#include <libsolutil/CommonData.h>
32
33
#include <range/v3/view/reverse.hpp>
34
35
#include <limits>
36
37
using namespace solidity;
38
using namespace solidity::yul;
39
40
void ExpressionJoiner::run(OptimiserStepContext& _context, Block& _ast)
41
439k
{
42
439k
  ExpressionJoiner{_ast}(_ast);
43
439k
  FunctionGrouper::run(_context, _ast);
44
439k
}
45
46
47
void ExpressionJoiner::operator()(FunctionCall& _funCall)
48
10.0M
{
49
10.0M
  handleArguments(_funCall.arguments);
50
10.0M
}
51
52
void ExpressionJoiner::operator()(Block& _block)
53
4.50M
{
54
4.50M
  resetLatestStatementPointer();
55
17.9M
  for (size_t i = 0; i < _block.statements.size(); ++i)
56
13.4M
  {
57
13.4M
    visit(_block.statements[i]);
58
13.4M
    m_currentBlock = &_block;
59
13.4M
    m_latestStatementInBlock = i;
60
13.4M
  }
61
62
4.50M
  removeEmptyBlocks(_block);
63
4.50M
  resetLatestStatementPointer();
64
4.50M
}
65
66
void ExpressionJoiner::visit(Expression& _e)
67
29.2M
{
68
29.2M
  if (std::holds_alternative<Identifier>(_e))
69
7.80M
  {
70
7.80M
    Identifier const& identifier = std::get<Identifier>(_e);
71
7.80M
    if (isLatestStatementVarDeclJoinable(identifier))
72
2.77M
    {
73
2.77M
      VariableDeclaration& varDecl = std::get<VariableDeclaration>(*latestStatement());
74
2.77M
      _e = std::move(*varDecl.value);
75
76
      // Delete the variable declaration (also get the moved-from structure back into a sane state)
77
2.77M
      *latestStatement() = Block();
78
79
2.77M
      decrementLatestStatementPointer();
80
2.77M
    }
81
7.80M
  }
82
21.4M
  else
83
21.4M
    ASTModifier::visit(_e);
84
29.2M
}
85
86
ExpressionJoiner::ExpressionJoiner(Block& _ast)
87
439k
{
88
439k
  m_references = VariableReferencesCounter::countReferences(_ast);
89
439k
}
90
91
void ExpressionJoiner::handleArguments(std::vector<Expression>& _arguments)
92
10.0M
{
93
  // We have to fill from left to right, but we can only
94
  // fill if everything to the right is just an identifier
95
  // or a literal.
96
  // Also we only descend into function calls if everything
97
  // on the right is an identifier or literal.
98
99
10.0M
  size_t i = _arguments.size();
100
10.0M
  for (Expression const& arg: _arguments | ranges::views::reverse)
101
17.1M
  {
102
17.1M
    --i;
103
17.1M
    if (!std::holds_alternative<Identifier>(arg) && !std::holds_alternative<Literal>(arg))
104
1.79M
      break;
105
17.1M
  }
106
  // i points to the last element that is neither an identifier nor a literal,
107
  // or to the first element if all of them are identifiers or literals.
108
109
27.1M
  for (; i < _arguments.size(); ++i)
110
17.1M
    visit(_arguments.at(i));
111
10.0M
}
112
113
void ExpressionJoiner::decrementLatestStatementPointer()
114
2.77M
{
115
2.77M
  if (!m_currentBlock)
116
0
    return;
117
2.77M
  if (m_latestStatementInBlock > 0)
118
2.33M
    --m_latestStatementInBlock;
119
439k
  else
120
439k
    resetLatestStatementPointer();
121
2.77M
}
122
123
void ExpressionJoiner::resetLatestStatementPointer()
124
9.43M
{
125
9.43M
  m_currentBlock = nullptr;
126
9.43M
  m_latestStatementInBlock = std::numeric_limits<size_t>::max();
127
9.43M
}
128
129
Statement* ExpressionJoiner::latestStatement()
130
13.3M
{
131
13.3M
  if (!m_currentBlock)
132
1.31M
    return nullptr;
133
12.0M
  else
134
12.0M
    return &m_currentBlock->statements.at(m_latestStatementInBlock);
135
13.3M
}
136
137
bool ExpressionJoiner::isLatestStatementVarDeclJoinable(Identifier const& _identifier)
138
7.80M
{
139
7.80M
  Statement const* statement = latestStatement();
140
7.80M
  if (!statement || !std::holds_alternative<VariableDeclaration>(*statement))
141
3.67M
    return false;
142
4.13M
  VariableDeclaration const& varDecl = std::get<VariableDeclaration>(*statement);
143
4.13M
  if (varDecl.variables.size() != 1 || !varDecl.value)
144
77.3k
    return false;
145
4.05M
  assertThrow(varDecl.variables.size() == 1, OptimizerException, "");
146
4.05M
  assertThrow(varDecl.value, OptimizerException, "");
147
4.05M
  return varDecl.variables.at(0).name == _identifier.name && m_references[_identifier.name] == 1;
148
4.13M
}