Coverage Report

Created: 2026-09-14 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/optimiser/SSATransform.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 turns subsequent assignments to variable declarations
20
 * and assignments.
21
 */
22
23
#include <libyul/optimiser/SSATransform.h>
24
25
#include <libyul/optimiser/NameCollector.h>
26
#include <libyul/optimiser/NameDispenser.h>
27
#include <libyul/AST.h>
28
29
#include <libsolutil/CommonData.h>
30
31
using namespace solidity;
32
using namespace solidity::yul;
33
using namespace solidity::langutil;
34
35
namespace
36
{
37
38
/**
39
 * First step of SSA transform: Introduces new SSA variables for each assignment or
40
 * declaration of a variable to be replaced.
41
 */
42
class IntroduceSSA: public ASTModifier
43
{
44
public:
45
  explicit IntroduceSSA(
46
    NameDispenser& _nameDispenser,
47
    std::set<YulName> const& _variablesToReplace
48
  ):
49
418k
    m_nameDispenser(_nameDispenser),
50
418k
    m_variablesToReplace(_variablesToReplace)
51
418k
  { }
52
53
  void operator()(Block& _block) override;
54
55
private:
56
  NameDispenser& m_nameDispenser;
57
  std::set<YulName> const& m_variablesToReplace;
58
};
59
60
61
void IntroduceSSA::operator()(Block& _block)
62
5.34M
{
63
5.34M
  util::iterateReplacing(
64
5.34M
    _block.statements,
65
5.34M
    [&](Statement& _s) -> std::optional<std::vector<Statement>>
66
63.5M
    {
67
63.5M
      if (std::holds_alternative<VariableDeclaration>(_s))
68
51.6M
      {
69
51.6M
        VariableDeclaration& varDecl = std::get<VariableDeclaration>(_s);
70
51.6M
        if (varDecl.value)
71
51.6M
          visit(*varDecl.value);
72
73
51.6M
        bool needToReplaceSome = false;
74
51.6M
        for (auto const& var: varDecl.variables)
75
52.1M
          if (m_variablesToReplace.count(var.name))
76
893k
            needToReplaceSome = true;
77
51.6M
        if (!needToReplaceSome)
78
50.7M
          return {};
79
80
        // Replace "let a := v" by "let a_1 := v  let a := a_1"
81
        // Replace "let a, b := v" by "let a_1, b_1 := v  let a := a_1 let b := b_2"
82
893k
        langutil::DebugData::ConstPtr debugData = varDecl.debugData;
83
893k
        std::vector<Statement> statements;
84
893k
        statements.emplace_back(VariableDeclaration{debugData, {}, std::move(varDecl.value)});
85
893k
        NameWithDebugDataList newVariables;
86
893k
        for (auto const& var: varDecl.variables)
87
897k
        {
88
897k
          YulName oldName = var.name;
89
897k
          YulName newName = m_nameDispenser.newName(oldName);
90
897k
          newVariables.emplace_back(NameWithDebugData{debugData, newName});
91
897k
          statements.emplace_back(VariableDeclaration{
92
897k
            debugData,
93
897k
            {NameWithDebugData{debugData, oldName}},
94
897k
            std::make_unique<Expression>(Identifier{debugData, newName})
95
897k
          });
96
897k
        }
97
893k
        std::get<VariableDeclaration>(statements.front()).variables = std::move(newVariables);
98
893k
        return { std::move(statements) };
99
51.6M
      }
100
11.9M
      else if (std::holds_alternative<Assignment>(_s))
101
1.46M
      {
102
1.46M
        Assignment& assignment = std::get<Assignment>(_s);
103
1.46M
        visit(*assignment.value);
104
1.46M
        for (auto const& var: assignment.variableNames)
105
1.47M
          assertThrow(m_variablesToReplace.count(var.name), OptimizerException, "");
106
107
        // Replace "a := v" by "let a_1 := v  a := v"
108
        // Replace "a, b := v" by "let a_1, b_1 := v  a := a_1 b := b_2"
109
1.46M
        langutil::DebugData::ConstPtr debugData = assignment.debugData;
110
1.46M
        std::vector<Statement> statements;
111
1.46M
        statements.emplace_back(VariableDeclaration{debugData, {}, std::move(assignment.value)});
112
1.46M
        NameWithDebugDataList newVariables;
113
1.46M
        for (auto const& var: assignment.variableNames)
114
1.47M
        {
115
1.47M
          YulName oldName = var.name;
116
1.47M
          YulName newName = m_nameDispenser.newName(oldName);
117
1.47M
          newVariables.emplace_back(NameWithDebugData{debugData, newName});
118
1.47M
          statements.emplace_back(Assignment{
119
1.47M
            debugData,
120
1.47M
            {Identifier{debugData, oldName}},
121
1.47M
            std::make_unique<Expression>(Identifier{debugData, newName})
122
1.47M
          });
123
1.47M
        }
124
1.46M
        std::get<VariableDeclaration>(statements.front()).variables = std::move(newVariables);
125
1.46M
        return { std::move(statements) };
126
1.46M
      }
127
10.4M
      else
128
10.4M
        visit(_s);
129
10.4M
      return {};
130
63.5M
    }
131
5.34M
  );
132
5.34M
}
133
134
/**
135
 * Second step of SSA transform: Introduces new SSA variables at each control-flow join
136
 * and at the beginning of functions.
137
 */
138
class IntroduceControlFlowSSA: public ASTModifier
139
{
140
public:
141
  explicit IntroduceControlFlowSSA(
142
    NameDispenser& _nameDispenser,
143
    std::set<YulName> const& _variablesToReplace
144
  ):
145
418k
    m_nameDispenser(_nameDispenser),
146
418k
    m_variablesToReplace(_variablesToReplace)
147
418k
  { }
148
149
  void operator()(FunctionDefinition& _function) override;
150
  void operator()(ForLoop& _forLoop) override;
151
  void operator()(Switch& _switch) override;
152
  void operator()(Block& _block) override;
153
154
private:
155
  NameDispenser& m_nameDispenser;
156
  std::set<YulName> const& m_variablesToReplace;
157
  /// Variables (that are to be replaced) currently in scope.
158
  std::set<YulName> m_variablesInScope;
159
  /// Variables that do not have a specific value.
160
  util::UniqueVector<YulName> m_variablesToReassign;
161
};
162
163
void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
164
1.22M
{
165
1.22M
  std::set<YulName> varsInScope;
166
1.22M
  std::swap(varsInScope, m_variablesInScope);
167
1.22M
  util::UniqueVector<YulName> toReassign;
168
1.22M
  std::swap(toReassign, m_variablesToReassign);
169
170
1.22M
  for (auto const& param: _function.parameters)
171
1.42M
    if (m_variablesToReplace.count(param.name))
172
38.3k
    {
173
38.3k
      m_variablesInScope.insert(param.name);
174
38.3k
      m_variablesToReassign.pushBack(param.name);
175
38.3k
    }
176
177
1.22M
  ASTModifier::operator()(_function);
178
179
1.22M
  m_variablesInScope = std::move(varsInScope);
180
1.22M
  m_variablesToReassign = std::move(toReassign);
181
1.22M
}
182
183
void IntroduceControlFlowSSA::operator()(ForLoop& _for)
184
628k
{
185
628k
  yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
186
187
628k
  for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
188
862k
    if (util::contains(m_variablesInScope,var))
189
677k
      m_variablesToReassign.pushBack(var);
190
191
628k
  (*this)(_for.body);
192
628k
  (*this)(_for.post);
193
628k
}
194
195
void IntroduceControlFlowSSA::operator()(Switch& _switch)
196
221k
{
197
221k
  yulAssert(m_variablesToReassign.empty(), "");
198
199
221k
  util::UniqueVector<YulName> toReassign;
200
221k
  for (auto& c: _switch.cases)
201
679k
  {
202
679k
    (*this)(c.body);
203
679k
    toReassign.pushBack(m_variablesToReassign);
204
679k
  }
205
206
221k
  m_variablesToReassign.pushBack(toReassign);
207
221k
}
208
209
void IntroduceControlFlowSSA::operator()(Block& _block)
210
4.71M
{
211
4.71M
  util::UniqueVector<YulName> variablesDeclaredHere;
212
4.71M
  util::UniqueVector<YulName> assignedVariables;
213
214
4.71M
  util::iterateReplacing(
215
4.71M
    _block.statements,
216
4.71M
    [&](Statement& _s) -> std::optional<std::vector<Statement>>
217
65.9M
    {
218
65.9M
      std::vector<Statement> toPrepend;
219
65.9M
      for (YulName toReassign: m_variablesToReassign)
220
1.97M
      {
221
1.97M
        YulName newName = m_nameDispenser.newName(toReassign);
222
1.97M
        toPrepend.emplace_back(VariableDeclaration{
223
1.97M
          debugDataOf(_s),
224
1.97M
          {NameWithDebugData{debugDataOf(_s), newName}},
225
1.97M
          std::make_unique<Expression>(Identifier{debugDataOf(_s), toReassign})
226
1.97M
        });
227
1.97M
        assignedVariables.pushBack(toReassign);
228
1.97M
      }
229
65.9M
      m_variablesToReassign.clear();
230
231
65.9M
      if (std::holds_alternative<VariableDeclaration>(_s))
232
53.9M
      {
233
53.9M
        VariableDeclaration& varDecl = std::get<VariableDeclaration>(_s);
234
53.9M
        for (auto const& var: varDecl.variables)
235
54.4M
          if (m_variablesToReplace.count(var.name))
236
893k
          {
237
893k
            variablesDeclaredHere.pushBack(var.name);
238
893k
            m_variablesInScope.insert(var.name);
239
893k
          }
240
53.9M
      }
241
11.9M
      else if (std::holds_alternative<Assignment>(_s))
242
1.47M
      {
243
1.47M
        Assignment& assignment = std::get<Assignment>(_s);
244
1.47M
        for (auto const& var: assignment.variableNames)
245
1.47M
          if (m_variablesToReplace.count(var.name))
246
1.47M
            assignedVariables.pushBack(var.name);
247
1.47M
      }
248
10.4M
      else
249
10.4M
        visit(_s);
250
251
65.9M
      if (toPrepend.empty())
252
64.5M
        return {};
253
1.42M
      else
254
1.42M
      {
255
1.42M
        toPrepend.emplace_back(std::move(_s));
256
1.42M
        return {std::move(toPrepend)};
257
1.42M
      }
258
65.9M
    }
259
4.71M
  );
260
261
4.71M
  m_variablesToReassign.pushBack(assignedVariables);
262
4.71M
  m_variablesInScope -= variablesDeclaredHere.contents();
263
4.71M
  m_variablesToReassign.removeAll(variablesDeclaredHere.contents());
264
4.71M
}
265
266
/**
267
 * Third step of SSA transform: Replace the references to variables-to-be-replaced
268
 * by their current values.
269
 */
270
class PropagateValues: public ASTModifier
271
{
272
public:
273
  explicit PropagateValues(std::set<YulName> const& _variablesToReplace):
274
418k
    m_variablesToReplace(_variablesToReplace)
275
418k
  { }
276
277
  void operator()(Identifier& _identifier) override;
278
  void operator()(VariableDeclaration& _varDecl) override;
279
  void operator()(Assignment& _assignment) override;
280
  void operator()(ForLoop& _for) override;
281
  void operator()(Block& _block) override;
282
283
private:
284
  /// This is a set of all variables that are assigned to anywhere in the code.
285
  /// Variables that are only declared but never re-assigned are not touched.
286
  std::set<YulName> const& m_variablesToReplace;
287
  std::map<YulName, YulName> m_currentVariableValues;
288
  std::set<YulName> m_clearAtEndOfBlock;
289
};
290
291
void PropagateValues::operator()(Identifier& _identifier)
292
66.2M
{
293
66.2M
  if (m_currentVariableValues.count(_identifier.name))
294
4.25M
    _identifier.name = m_currentVariableValues[_identifier.name];
295
66.2M
}
296
297
void PropagateValues::operator()(VariableDeclaration& _varDecl)
298
55.9M
{
299
55.9M
  ASTModifier::operator()(_varDecl);
300
301
55.9M
  if (_varDecl.variables.size() != 1)
302
198k
    return;
303
304
55.7M
  YulName variable = _varDecl.variables.front().name;
305
55.7M
  if (m_variablesToReplace.count(variable))
306
893k
  {
307
    // `let a := a_1` - regular declaration of non-SSA variable
308
893k
    yulAssert(std::holds_alternative<Identifier>(*_varDecl.value), "");
309
893k
    m_currentVariableValues[variable] = std::get<Identifier>(*_varDecl.value).name;
310
893k
    m_clearAtEndOfBlock.insert(variable);
311
893k
  }
312
54.8M
  else if (_varDecl.value && std::holds_alternative<Identifier>(*_varDecl.value))
313
31.8M
  {
314
    // `let a_1 := a` - assignment to SSA variable after a branch.
315
31.8M
    YulName value = std::get<Identifier>(*_varDecl.value).name;
316
31.8M
    if (m_variablesToReplace.count(value))
317
1.97M
    {
318
      // This is safe because `a_1` is not a "variable to replace" and thus
319
      // will not be re-assigned.
320
1.97M
      m_currentVariableValues[value] = variable;
321
1.97M
      m_clearAtEndOfBlock.insert(value);
322
1.97M
    }
323
31.8M
  }
324
55.7M
}
325
326
327
void PropagateValues::operator()(Assignment& _assignment)
328
1.47M
{
329
1.47M
  visit(*_assignment.value);
330
331
1.47M
  if (_assignment.variableNames.size() != 1)
332
0
    return;
333
1.47M
  YulName name = _assignment.variableNames.front().name;
334
1.47M
  if (!m_variablesToReplace.count(name))
335
0
    return;
336
337
1.47M
  yulAssert(_assignment.value && std::holds_alternative<Identifier>(*_assignment.value), "");
338
1.47M
  m_currentVariableValues[name] = std::get<Identifier>(*_assignment.value).name;
339
1.47M
  m_clearAtEndOfBlock.insert(name);
340
1.47M
}
341
342
void PropagateValues::operator()(ForLoop& _for)
343
628k
{
344
628k
  yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
345
346
628k
  for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
347
862k
    m_currentVariableValues.erase(var);
348
349
628k
  visit(*_for.condition);
350
628k
  (*this)(_for.body);
351
628k
  (*this)(_for.post);
352
628k
}
353
354
void PropagateValues::operator()(Block& _block)
355
4.71M
{
356
4.71M
  std::set<YulName> clearAtParentBlock = std::move(m_clearAtEndOfBlock);
357
4.71M
  m_clearAtEndOfBlock.clear();
358
359
4.71M
  ASTModifier::operator()(_block);
360
361
4.71M
  for (auto const& var: m_clearAtEndOfBlock)
362
2.78M
    m_currentVariableValues.erase(var);
363
364
4.71M
  m_clearAtEndOfBlock = std::move(clearAtParentBlock);
365
4.71M
}
366
367
}
368
369
void SSATransform::run(OptimiserStepContext& _context, Block& _ast)
370
418k
{
371
418k
  std::set<YulName> assignedVariables = assignedVariableNames(_ast);
372
418k
  IntroduceSSA{_context.dispenser, assignedVariables}(_ast);
373
418k
  IntroduceControlFlowSSA{_context.dispenser, assignedVariables}(_ast);
374
418k
  PropagateValues{assignedVariables}(_ast);
375
418k
}
376
377