Coverage Report

Created: 2026-06-30 07:08

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
374k
    m_nameDispenser(_nameDispenser),
50
374k
    m_variablesToReplace(_variablesToReplace)
51
374k
  { }
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
4.52M
{
63
4.52M
  util::iterateReplacing(
64
4.52M
    _block.statements,
65
4.52M
    [&](Statement& _s) -> std::optional<std::vector<Statement>>
66
50.1M
    {
67
50.1M
      if (std::holds_alternative<VariableDeclaration>(_s))
68
40.3M
      {
69
40.3M
        VariableDeclaration& varDecl = std::get<VariableDeclaration>(_s);
70
40.3M
        if (varDecl.value)
71
40.3M
          visit(*varDecl.value);
72
73
40.3M
        bool needToReplaceSome = false;
74
40.3M
        for (auto const& var: varDecl.variables)
75
40.7M
          if (m_variablesToReplace.count(var.name))
76
701k
            needToReplaceSome = true;
77
40.3M
        if (!needToReplaceSome)
78
39.6M
          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
700k
        langutil::DebugData::ConstPtr debugData = varDecl.debugData;
83
700k
        std::vector<Statement> statements;
84
700k
        statements.emplace_back(VariableDeclaration{debugData, {}, std::move(varDecl.value)});
85
700k
        NameWithDebugDataList newVariables;
86
700k
        for (auto const& var: varDecl.variables)
87
703k
        {
88
703k
          YulName oldName = var.name;
89
703k
          YulName newName = m_nameDispenser.newName(oldName);
90
703k
          newVariables.emplace_back(NameWithDebugData{debugData, newName});
91
703k
          statements.emplace_back(VariableDeclaration{
92
703k
            debugData,
93
703k
            {NameWithDebugData{debugData, oldName}},
94
703k
            std::make_unique<Expression>(Identifier{debugData, newName})
95
703k
          });
96
703k
        }
97
700k
        std::get<VariableDeclaration>(statements.front()).variables = std::move(newVariables);
98
700k
        return { std::move(statements) };
99
40.3M
      }
100
9.76M
      else if (std::holds_alternative<Assignment>(_s))
101
1.16M
      {
102
1.16M
        Assignment& assignment = std::get<Assignment>(_s);
103
1.16M
        visit(*assignment.value);
104
1.16M
        for (auto const& var: assignment.variableNames)
105
1.16M
          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.16M
        langutil::DebugData::ConstPtr debugData = assignment.debugData;
110
1.16M
        std::vector<Statement> statements;
111
1.16M
        statements.emplace_back(VariableDeclaration{debugData, {}, std::move(assignment.value)});
112
1.16M
        NameWithDebugDataList newVariables;
113
1.16M
        for (auto const& var: assignment.variableNames)
114
1.16M
        {
115
1.16M
          YulName oldName = var.name;
116
1.16M
          YulName newName = m_nameDispenser.newName(oldName);
117
1.16M
          newVariables.emplace_back(NameWithDebugData{debugData, newName});
118
1.16M
          statements.emplace_back(Assignment{
119
1.16M
            debugData,
120
1.16M
            {Identifier{debugData, oldName}},
121
1.16M
            std::make_unique<Expression>(Identifier{debugData, newName})
122
1.16M
          });
123
1.16M
        }
124
1.16M
        std::get<VariableDeclaration>(statements.front()).variables = std::move(newVariables);
125
1.16M
        return { std::move(statements) };
126
1.16M
      }
127
8.59M
      else
128
8.59M
        visit(_s);
129
8.59M
      return {};
130
50.1M
    }
131
4.52M
  );
132
4.52M
}
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
374k
    m_nameDispenser(_nameDispenser),
146
374k
    m_variablesToReplace(_variablesToReplace)
147
374k
  { }
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
974k
{
165
974k
  std::set<YulName> varsInScope;
166
974k
  std::swap(varsInScope, m_variablesInScope);
167
974k
  util::UniqueVector<YulName> toReassign;
168
974k
  std::swap(toReassign, m_variablesToReassign);
169
170
974k
  for (auto const& param: _function.parameters)
171
1.10M
    if (m_variablesToReplace.count(param.name))
172
31.3k
    {
173
31.3k
      m_variablesInScope.insert(param.name);
174
31.3k
      m_variablesToReassign.pushBack(param.name);
175
31.3k
    }
176
177
974k
  ASTModifier::operator()(_function);
178
179
974k
  m_variablesInScope = std::move(varsInScope);
180
974k
  m_variablesToReassign = std::move(toReassign);
181
974k
}
182
183
void IntroduceControlFlowSSA::operator()(ForLoop& _for)
184
543k
{
185
543k
  yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
186
187
543k
  for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
188
693k
    if (util::contains(m_variablesInScope,var))
189
559k
      m_variablesToReassign.pushBack(var);
190
191
543k
  (*this)(_for.body);
192
543k
  (*this)(_for.post);
193
543k
}
194
195
void IntroduceControlFlowSSA::operator()(Switch& _switch)
196
184k
{
197
184k
  yulAssert(m_variablesToReassign.empty(), "");
198
199
184k
  util::UniqueVector<YulName> toReassign;
200
184k
  for (auto& c: _switch.cases)
201
545k
  {
202
545k
    (*this)(c.body);
203
545k
    toReassign.pushBack(m_variablesToReassign);
204
545k
  }
205
206
184k
  m_variablesToReassign.pushBack(toReassign);
207
184k
}
208
209
void IntroduceControlFlowSSA::operator()(Block& _block)
210
3.97M
{
211
3.97M
  util::UniqueVector<YulName> variablesDeclaredHere;
212
3.97M
  util::UniqueVector<YulName> assignedVariables;
213
214
3.97M
  util::iterateReplacing(
215
3.97M
    _block.statements,
216
3.97M
    [&](Statement& _s) -> std::optional<std::vector<Statement>>
217
52.0M
    {
218
52.0M
      std::vector<Statement> toPrepend;
219
52.0M
      for (YulName toReassign: m_variablesToReassign)
220
1.62M
      {
221
1.62M
        YulName newName = m_nameDispenser.newName(toReassign);
222
1.62M
        toPrepend.emplace_back(VariableDeclaration{
223
1.62M
          debugDataOf(_s),
224
1.62M
          {NameWithDebugData{debugDataOf(_s), newName}},
225
1.62M
          std::make_unique<Expression>(Identifier{debugDataOf(_s), toReassign})
226
1.62M
        });
227
1.62M
        assignedVariables.pushBack(toReassign);
228
1.62M
      }
229
52.0M
      m_variablesToReassign.clear();
230
231
52.0M
      if (std::holds_alternative<VariableDeclaration>(_s))
232
42.2M
      {
233
42.2M
        VariableDeclaration& varDecl = std::get<VariableDeclaration>(_s);
234
42.2M
        for (auto const& var: varDecl.variables)
235
42.6M
          if (m_variablesToReplace.count(var.name))
236
701k
          {
237
701k
            variablesDeclaredHere.pushBack(var.name);
238
701k
            m_variablesInScope.insert(var.name);
239
701k
          }
240
42.2M
      }
241
9.76M
      else if (std::holds_alternative<Assignment>(_s))
242
1.16M
      {
243
1.16M
        Assignment& assignment = std::get<Assignment>(_s);
244
1.16M
        for (auto const& var: assignment.variableNames)
245
1.16M
          if (m_variablesToReplace.count(var.name))
246
1.16M
            assignedVariables.pushBack(var.name);
247
1.16M
      }
248
8.59M
      else
249
8.59M
        visit(_s);
250
251
52.0M
      if (toPrepend.empty())
252
50.8M
        return {};
253
1.17M
      else
254
1.17M
      {
255
1.17M
        toPrepend.emplace_back(std::move(_s));
256
1.17M
        return {std::move(toPrepend)};
257
1.17M
      }
258
52.0M
    }
259
3.97M
  );
260
261
3.97M
  m_variablesToReassign.pushBack(assignedVariables);
262
3.97M
  m_variablesInScope -= variablesDeclaredHere.contents();
263
3.97M
  m_variablesToReassign.removeAll(variablesDeclaredHere.contents());
264
3.97M
}
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
374k
    m_variablesToReplace(_variablesToReplace)
275
374k
  { }
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
52.2M
{
293
52.2M
  if (m_currentVariableValues.count(_identifier.name))
294
3.44M
    _identifier.name = m_currentVariableValues[_identifier.name];
295
52.2M
}
296
297
void PropagateValues::operator()(VariableDeclaration& _varDecl)
298
43.8M
{
299
43.8M
  ASTModifier::operator()(_varDecl);
300
301
43.8M
  if (_varDecl.variables.size() != 1)
302
154k
    return;
303
304
43.7M
  YulName variable = _varDecl.variables.front().name;
305
43.7M
  if (m_variablesToReplace.count(variable))
306
701k
  {
307
    // `let a := a_1` - regular declaration of non-SSA variable
308
701k
    yulAssert(std::holds_alternative<Identifier>(*_varDecl.value), "");
309
701k
    m_currentVariableValues[variable] = std::get<Identifier>(*_varDecl.value).name;
310
701k
    m_clearAtEndOfBlock.insert(variable);
311
701k
  }
312
43.0M
  else if (_varDecl.value && std::holds_alternative<Identifier>(*_varDecl.value))
313
24.9M
  {
314
    // `let a_1 := a` - assignment to SSA variable after a branch.
315
24.9M
    YulName value = std::get<Identifier>(*_varDecl.value).name;
316
24.9M
    if (m_variablesToReplace.count(value))
317
1.62M
    {
318
      // This is safe because `a_1` is not a "variable to replace" and thus
319
      // will not be re-assigned.
320
1.62M
      m_currentVariableValues[value] = variable;
321
1.62M
      m_clearAtEndOfBlock.insert(value);
322
1.62M
    }
323
24.9M
  }
324
43.7M
}
325
326
327
void PropagateValues::operator()(Assignment& _assignment)
328
1.16M
{
329
1.16M
  visit(*_assignment.value);
330
331
1.16M
  if (_assignment.variableNames.size() != 1)
332
0
    return;
333
1.16M
  YulName name = _assignment.variableNames.front().name;
334
1.16M
  if (!m_variablesToReplace.count(name))
335
0
    return;
336
337
1.16M
  yulAssert(_assignment.value && std::holds_alternative<Identifier>(*_assignment.value), "");
338
1.16M
  m_currentVariableValues[name] = std::get<Identifier>(*_assignment.value).name;
339
1.16M
  m_clearAtEndOfBlock.insert(name);
340
1.16M
}
341
342
void PropagateValues::operator()(ForLoop& _for)
343
543k
{
344
543k
  yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
345
346
543k
  for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
347
693k
    m_currentVariableValues.erase(var);
348
349
543k
  visit(*_for.condition);
350
543k
  (*this)(_for.body);
351
543k
  (*this)(_for.post);
352
543k
}
353
354
void PropagateValues::operator()(Block& _block)
355
3.97M
{
356
3.97M
  std::set<YulName> clearAtParentBlock = std::move(m_clearAtEndOfBlock);
357
3.97M
  m_clearAtEndOfBlock.clear();
358
359
3.97M
  ASTModifier::operator()(_block);
360
361
3.97M
  for (auto const& var: m_clearAtEndOfBlock)
362
2.23M
    m_currentVariableValues.erase(var);
363
364
3.97M
  m_clearAtEndOfBlock = std::move(clearAtParentBlock);
365
3.97M
}
366
367
}
368
369
void SSATransform::run(OptimiserStepContext& _context, Block& _ast)
370
374k
{
371
374k
  std::set<YulName> assignedVariables = assignedVariableNames(_ast);
372
374k
  IntroduceSSA{_context.dispenser, assignedVariables}(_ast);
373
374k
  IntroduceControlFlowSSA{_context.dispenser, assignedVariables}(_ast);
374
374k
  PropagateValues{assignedVariables}(_ast);
375
374k
}
376
377