Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/backends/evm/EVMCodeTransform.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
 * Code generator for translating Yul / inline assembly to EVM.
20
 */
21
22
#include <libyul/backends/evm/EVMCodeTransform.h>
23
24
#include <libyul/optimiser/NameCollector.h>
25
#include <libyul/AsmAnalysisInfo.h>
26
#include <libyul/Utilities.h>
27
28
#include <libsolutil/Visitor.h>
29
#include <libsolutil/StackTooDeepString.h>
30
31
#include <liblangutil/Exceptions.h>
32
33
#include <libevmasm/Instruction.h>
34
35
#include <range/v3/view/reverse.hpp>
36
37
#include <range/v3/algorithm/max.hpp>
38
#include <range/v3/algorithm/none_of.hpp>
39
#include <range/v3/view/enumerate.hpp>
40
#include <range/v3/view/transform.hpp>
41
42
#include <utility>
43
#include <variant>
44
45
using namespace solidity;
46
using namespace solidity::yul;
47
using namespace solidity::util;
48
49
CodeTransform::CodeTransform(
50
  AbstractAssembly& _assembly,
51
  AsmAnalysisInfo& _analysisInfo,
52
  Block const& _block,
53
  bool _allowStackOpt,
54
  EVMDialect const& _dialect,
55
  BuiltinContext& _builtinContext,
56
  ExternalIdentifierAccess::CodeGenerator _identifierAccessCodeGen,
57
  UseNamedLabels _useNamedLabelsForFunctions,
58
  std::shared_ptr<Context> _context,
59
  std::vector<NameWithDebugData> _delayedReturnVariables,
60
  std::optional<AbstractAssembly::LabelID> _functionExitLabel
61
):
62
524k
  m_assembly(_assembly),
63
524k
  m_info(_analysisInfo),
64
524k
  m_dialect(_dialect),
65
524k
  m_builtinContext(_builtinContext),
66
524k
  m_allowStackOpt(_allowStackOpt),
67
524k
  m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions),
68
524k
  m_identifierAccessCodeGen(std::move(_identifierAccessCodeGen)),
69
524k
  m_context(std::move(_context)),
70
524k
  m_delayedReturnVariables(std::move(_delayedReturnVariables)),
71
524k
  m_functionExitLabel(_functionExitLabel)
72
524k
{
73
524k
  if (!m_context)
74
150k
  {
75
    // initialize
76
150k
    m_context = std::make_shared<Context>();
77
150k
    if (m_allowStackOpt)
78
89.4k
      m_context->variableReferences = VariableReferenceCounter::run(m_info, _block);
79
150k
  }
80
524k
}
81
82
void CodeTransform::decreaseReference(YulName, Scope::Variable const& _var)
83
2.18M
{
84
2.18M
  if (!m_allowStackOpt)
85
880k
    return;
86
87
1.30M
  unsigned& ref = m_context->variableReferences.at(&_var);
88
1.30M
  yulAssert(ref >= 1, "");
89
1.30M
  --ref;
90
1.30M
  if (ref == 0)
91
623k
    m_variablesScheduledForDeletion.insert(&_var);
92
1.30M
}
93
94
bool CodeTransform::unreferenced(Scope::Variable const& _var) const
95
930k
{
96
930k
  return !m_context->variableReferences.count(&_var) || m_context->variableReferences[&_var] == 0;
97
930k
}
98
99
void CodeTransform::freeUnusedVariables(bool _popUnusedSlotsAtStackTop)
100
5.66M
{
101
5.66M
  if (!m_allowStackOpt)
102
1.90M
    return;
103
104
3.76M
  for (auto const& identifier: m_scope->identifiers)
105
17.3M
    if (Scope::Variable const* var = std::get_if<Scope::Variable>(&identifier.second))
106
13.1M
      if (m_variablesScheduledForDeletion.count(var))
107
483k
        deleteVariable(*var);
108
  // Directly in a function body block, we can also delete the function arguments,
109
  // which live in the virtual function scope.
110
  // However, doing so after the return variables are already allocated, seems to have an adverse
111
  // effect, so we only do it before that.
112
3.76M
  if (!returnVariablesAndFunctionExitAreSetup() && !m_scope->functionScope && m_scope->superScope && m_scope->superScope->functionScope)
113
341k
    for (auto const& identifier: m_scope->superScope->identifiers)
114
848k
      if (Scope::Variable const* var = std::get_if<Scope::Variable>(&identifier.second))
115
848k
        if (m_variablesScheduledForDeletion.count(var))
116
15.6k
          deleteVariable(*var);
117
118
3.76M
  if (_popUnusedSlotsAtStackTop)
119
3.97M
    while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1))
120
603k
    {
121
603k
      yulAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), "");
122
603k
      m_assembly.appendInstruction(evmasm::Instruction::POP);
123
603k
    }
124
3.76M
}
125
126
void CodeTransform::deleteVariable(Scope::Variable const& _var)
127
662k
{
128
662k
  yulAssert(m_allowStackOpt, "");
129
662k
  yulAssert(m_context->variableStackHeights.count(&_var) > 0, "");
130
662k
  m_unusedStackSlots.insert(static_cast<int>(m_context->variableStackHeights[&_var]));
131
662k
  m_context->variableStackHeights.erase(&_var);
132
662k
  m_context->variableReferences.erase(&_var);
133
662k
  m_variablesScheduledForDeletion.erase(&_var);
134
662k
}
135
136
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
137
985k
{
138
985k
  yulAssert(m_scope, "");
139
140
985k
  size_t const numVariables = _varDecl.variables.size();
141
985k
  auto heightAtStart = static_cast<size_t>(m_assembly.stackHeight());
142
985k
  if (_varDecl.value)
143
548k
  {
144
548k
    std::visit(*this, *_varDecl.value);
145
548k
    expectDeposit(static_cast<int>(numVariables), static_cast<int>(heightAtStart));
146
548k
    freeUnusedVariables(false);
147
548k
  }
148
436k
  else
149
436k
  {
150
436k
    m_assembly.setSourceLocation(originLocationOf(_varDecl));
151
436k
    size_t variablesLeft = numVariables;
152
894k
    while (variablesLeft--)
153
457k
      m_assembly.appendConstant(u256(0));
154
436k
  }
155
156
985k
  m_assembly.setSourceLocation(originLocationOf(_varDecl));
157
985k
  bool atTopOfStack = true;
158
2.21M
  for (size_t varIndex = 0; varIndex < numVariables; ++varIndex)
159
1.22M
  {
160
1.22M
    size_t varIndexReverse = numVariables - 1 - varIndex;
161
1.22M
    YulName varName = _varDecl.variables[varIndexReverse].name;
162
1.22M
    auto& var = std::get<Scope::Variable>(m_scope->identifiers.at(varName));
163
1.22M
    m_context->variableStackHeights[&var] = heightAtStart + varIndexReverse;
164
1.22M
    if (!m_allowStackOpt)
165
295k
      continue;
166
167
930k
    if (unreferenced(var))
168
146k
    {
169
146k
      if (atTopOfStack)
170
139k
      {
171
139k
        m_context->variableStackHeights.erase(&var);
172
139k
        m_assembly.appendInstruction(evmasm::Instruction::POP);
173
139k
      }
174
6.52k
      else
175
6.52k
        m_variablesScheduledForDeletion.insert(&var);
176
146k
    }
177
783k
    else
178
783k
    {
179
783k
      bool foundUnusedSlot = false;
180
784k
      for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it)
181
47.6k
      {
182
47.6k
        if (m_assembly.stackHeight() - *it > static_cast<int>(m_dialect.reachableStackDepth() + 1))
183
468
          continue;
184
47.1k
        foundUnusedSlot = true;
185
47.1k
        auto slot = static_cast<size_t>(*it);
186
47.1k
        m_unusedStackSlots.erase(it);
187
47.1k
        m_context->variableStackHeights[&var] = slot;
188
47.1k
        if (size_t heightDiff = variableHeightDiff(var, varName, true))
189
47.1k
          m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1)));
190
47.1k
        m_assembly.appendInstruction(evmasm::Instruction::POP);
191
47.1k
        break;
192
47.6k
      }
193
783k
      if (!foundUnusedSlot)
194
736k
        atTopOfStack = false;
195
783k
    }
196
930k
  }
197
985k
}
198
199
void CodeTransform::stackError(StackTooDeepError _error, int _targetStackHeight)
200
216
{
201
216
  m_assembly.appendInstruction(evmasm::Instruction::INVALID);
202
  // Correct the stack.
203
1.98k
  while (m_assembly.stackHeight() > _targetStackHeight)
204
1.76k
    m_assembly.appendInstruction(evmasm::Instruction::POP);
205
216
  while (m_assembly.stackHeight() < _targetStackHeight)
206
0
    m_assembly.appendConstant(u256(0));
207
  // Store error.
208
216
  m_stackErrors.emplace_back(std::move(_error));
209
216
  m_assembly.markAsInvalid();
210
216
}
211
212
void CodeTransform::operator()(Assignment const& _assignment)
213
371k
{
214
371k
  int height = m_assembly.stackHeight();
215
371k
  std::visit(*this, *_assignment.value);
216
371k
  expectDeposit(static_cast<int>(_assignment.variableNames.size()), height);
217
218
371k
  m_assembly.setSourceLocation(originLocationOf(_assignment));
219
371k
  generateMultiAssignment(_assignment.variableNames);
220
371k
}
221
222
void CodeTransform::operator()(ExpressionStatement const& _statement)
223
919k
{
224
919k
  m_assembly.setSourceLocation(originLocationOf(_statement));
225
919k
  std::visit(*this, _statement.expression);
226
919k
}
227
228
void CodeTransform::operator()(FunctionCall const& _call)
229
4.76M
{
230
4.76M
  yulAssert(m_scope, "");
231
232
4.76M
  m_assembly.setSourceLocation(originLocationOf(_call));
233
4.76M
  if (BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_call.functionName, m_dialect))
234
4.19M
  {
235
4.19M
    for (auto&& [i, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
236
9.63M
      if (!builtin->literalArgument(i))
237
9.41M
        visitExpression(arg);
238
4.19M
    m_assembly.setSourceLocation(originLocationOf(_call));
239
4.19M
    builtin->generateCode(_call, m_assembly, m_builtinContext);
240
4.19M
  }
241
565k
  else
242
565k
  {
243
565k
    yulAssert(std::holds_alternative<Identifier>(_call.functionName));
244
565k
    AbstractAssembly::LabelID returnLabel = m_assembly.newLabelId();
245
565k
    m_assembly.appendLabelReference(returnLabel);
246
247
565k
    Scope::Function* function = nullptr;
248
565k
    yulAssert(m_scope->lookup(std::get<Identifier>(_call.functionName).name, GenericVisitor{
249
565k
      [](Scope::Variable&) { yulAssert(false, "Expected function name."); },
250
565k
      [&](Scope::Function& _function) { function = &_function; }
251
565k
    }), "Function name not found.");
252
565k
    yulAssert(function, "");
253
565k
    yulAssert(function->numArguments == _call.arguments.size(), "");
254
565k
    for (auto const& arg: _call.arguments | ranges::views::reverse)
255
921k
      visitExpression(arg);
256
565k
    m_assembly.setSourceLocation(originLocationOf(_call));
257
565k
    m_assembly.appendJumpTo(
258
565k
      functionEntryID(*function),
259
565k
      static_cast<int>(function->numReturns) - static_cast<int>(function->numArguments) - 1,
260
565k
      AbstractAssembly::JumpType::IntoFunction
261
565k
    );
262
565k
    m_assembly.appendLabel(returnLabel);
263
565k
  }
264
4.76M
}
265
266
void CodeTransform::operator()(Identifier const& _identifier)
267
1.87M
{
268
1.87M
  m_assembly.setSourceLocation(originLocationOf(_identifier));
269
  // First search internals, then externals.
270
1.87M
  yulAssert(m_scope, "");
271
1.87M
  if (m_scope->lookup(_identifier.name, GenericVisitor{
272
1.87M
    [&](Scope::Variable& _var)
273
1.87M
    {
274
      // TODO: opportunity for optimization: Do not DUP if this is the last reference
275
      // to the top most element of the stack
276
1.82M
      if (size_t heightDiff = variableHeightDiff(_var, _identifier.name, false))
277
1.82M
        m_assembly.appendInstruction(evmasm::dupInstruction(static_cast<unsigned>(heightDiff)));
278
0
      else
279
        // Store something to balance the stack
280
0
        m_assembly.appendConstant(u256(0));
281
1.82M
      decreaseReference(_identifier.name, _var);
282
1.82M
    },
283
1.87M
    [](Scope::Function&)
284
1.87M
    {
285
0
      yulAssert(false, "Function not removed during desugaring.");
286
0
    }
287
1.87M
  }))
288
1.82M
  {
289
1.82M
    return;
290
1.82M
  }
291
56.8k
  yulAssert(
292
56.8k
    m_identifierAccessCodeGen,
293
56.8k
    "Identifier not found and no external access available."
294
56.8k
  );
295
56.8k
  m_identifierAccessCodeGen(_identifier, IdentifierContext::RValue, m_assembly);
296
56.8k
}
297
298
void CodeTransform::operator()(Literal const& _literal)
299
5.85M
{
300
5.85M
  m_assembly.setSourceLocation(originLocationOf(_literal));
301
5.85M
  m_assembly.appendConstant(_literal.value.value());
302
5.85M
}
303
304
void CodeTransform::operator()(If const& _if)
305
167k
{
306
167k
  visitExpression(*_if.condition);
307
167k
  m_assembly.setSourceLocation(originLocationOf(_if));
308
167k
  m_assembly.appendInstruction(evmasm::Instruction::ISZERO);
309
167k
  AbstractAssembly::LabelID end = m_assembly.newLabelId();
310
167k
  m_assembly.appendJumpToIf(end);
311
167k
  (*this)(_if.body);
312
167k
  m_assembly.setSourceLocation(originLocationOf(_if));
313
167k
  m_assembly.appendLabel(end);
314
167k
}
315
316
void CodeTransform::operator()(Switch const& _switch)
317
42.6k
{
318
42.6k
  visitExpression(*_switch.expression);
319
42.6k
  int expressionHeight = m_assembly.stackHeight();
320
42.6k
  std::map<Case const*, AbstractAssembly::LabelID> caseBodies;
321
42.6k
  AbstractAssembly::LabelID end = m_assembly.newLabelId();
322
42.6k
  for (Case const& c: _switch.cases)
323
78.1k
  {
324
78.1k
    if (c.value)
325
38.9k
    {
326
38.9k
      (*this)(*c.value);
327
38.9k
      m_assembly.setSourceLocation(originLocationOf(c));
328
38.9k
      AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId();
329
38.9k
      caseBodies[&c] = bodyLabel;
330
38.9k
      yulAssert(m_assembly.stackHeight() == expressionHeight + 1, "");
331
38.9k
      m_assembly.appendInstruction(evmasm::dupInstruction(2));
332
38.9k
      m_assembly.appendInstruction(evmasm::Instruction::EQ);
333
38.9k
      m_assembly.appendJumpToIf(bodyLabel);
334
38.9k
    }
335
39.1k
    else
336
      // default case
337
39.1k
      (*this)(c.body);
338
78.1k
  }
339
42.6k
  m_assembly.setSourceLocation(originLocationOf(_switch));
340
42.6k
  m_assembly.appendJumpTo(end);
341
342
42.6k
  size_t numCases = caseBodies.size();
343
42.6k
  for (auto const& c: caseBodies)
344
38.9k
  {
345
38.9k
    m_assembly.setSourceLocation(originLocationOf(*c.first));
346
38.9k
    m_assembly.appendLabel(c.second);
347
38.9k
    (*this)(c.first->body);
348
    // Avoid useless "jump to next" for the last case.
349
38.9k
    if (--numCases > 0)
350
22.5k
    {
351
22.5k
      m_assembly.setSourceLocation(originLocationOf(*c.first));
352
22.5k
      m_assembly.appendJumpTo(end);
353
22.5k
    }
354
38.9k
  }
355
356
42.6k
  m_assembly.setSourceLocation(originLocationOf(_switch));
357
42.6k
  m_assembly.appendLabel(end);
358
42.6k
  m_assembly.appendInstruction(evmasm::Instruction::POP);
359
42.6k
}
360
361
void CodeTransform::operator()(FunctionDefinition const& _function)
362
374k
{
363
374k
  yulAssert(m_scope, "");
364
374k
  yulAssert(m_scope->identifiers.count(_function.name), "");
365
374k
  Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
366
367
374k
  size_t height = 1;
368
374k
  yulAssert(m_info.scopes.at(&_function.body), "");
369
374k
  Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
370
374k
  yulAssert(virtualFunctionScope, "");
371
374k
  for (auto const& v: _function.parameters | ranges::views::reverse)
372
550k
  {
373
550k
    auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name));
374
550k
    m_context->variableStackHeights[&var] = height++;
375
550k
  }
376
377
374k
  m_assembly.setSourceLocation(originLocationOf(_function));
378
374k
  int const stackHeightBefore = m_assembly.stackHeight();
379
380
374k
  m_assembly.appendLabel(functionEntryID(function));
381
382
374k
  m_assembly.setStackHeight(static_cast<int>(height));
383
384
374k
  CodeTransform subTransform(
385
374k
    m_assembly,
386
374k
    m_info,
387
374k
    _function.body,
388
374k
    m_allowStackOpt,
389
374k
    m_dialect,
390
374k
    m_builtinContext,
391
374k
    m_identifierAccessCodeGen,
392
374k
    m_useNamedLabelsForFunctions,
393
374k
    m_context,
394
374k
    _function.returnVariables,
395
374k
    m_assembly.newLabelId()
396
374k
  );
397
374k
  subTransform.m_scope = virtualFunctionScope;
398
399
374k
  if (m_allowStackOpt)
400
    // Immediately delete entirely unused parameters.
401
217k
    for (auto const& v: _function.parameters | ranges::views::reverse)
402
309k
    {
403
309k
      auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name));
404
309k
      if (util::valueOrDefault(m_context->variableReferences, &var, 0u) == 0)
405
163k
        subTransform.deleteVariable(var);
406
309k
    }
407
408
374k
  if (!m_allowStackOpt)
409
157k
    subTransform.setupReturnVariablesAndFunctionExit();
410
411
374k
  subTransform.m_assignedNamedLabels = std::move(m_assignedNamedLabels);
412
413
374k
  subTransform(_function.body);
414
415
374k
  m_assignedNamedLabels = std::move(subTransform.m_assignedNamedLabels);
416
417
374k
  m_assembly.setSourceLocation(originLocationOf(_function));
418
374k
  if (!subTransform.m_stackErrors.empty())
419
44.2k
  {
420
44.2k
    m_assembly.markAsInvalid();
421
44.2k
    for (StackTooDeepError& stackError: subTransform.m_stackErrors)
422
86.8k
    {
423
86.8k
      if (stackError.functionName.empty())
424
86.1k
        stackError.functionName = _function.name;
425
86.8k
      m_stackErrors.emplace_back(std::move(stackError));
426
86.8k
    }
427
44.2k
  }
428
429
374k
  if (!subTransform.returnVariablesAndFunctionExitAreSetup())
430
50.2k
    subTransform.setupReturnVariablesAndFunctionExit();
431
374k
  appendPopUntil(*subTransform.m_functionExitStackHeight);
432
433
374k
  yulAssert(
434
374k
    subTransform.m_functionExitStackHeight &&
435
374k
    *subTransform.m_functionExitStackHeight == m_assembly.stackHeight(),
436
374k
    ""
437
374k
  );
438
439
374k
  m_assembly.appendLabel(*subTransform.m_functionExitLabel);
440
441
374k
  {
442
    // The stack layout here is:
443
    // <return label>? <arguments...> <return values...>
444
    // But we would like it to be:
445
    // <return values...> <return label>?
446
    // So we have to append some SWAP and POP instructions.
447
448
    // This vector holds the desired target positions of all stack slots and is
449
    // modified parallel to the actual stack.
450
374k
    std::vector<int> stackLayout(static_cast<size_t>(m_assembly.stackHeight()), -1);
451
374k
    stackLayout[0] = static_cast<int>(_function.returnVariables.size()); // Move return label to the top
452
374k
    for (auto&& [n, returnVariable]: ranges::views::enumerate(_function.returnVariables))
453
425k
      stackLayout.at(m_context->variableStackHeights.at(
454
425k
        &std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name))
455
425k
      )) = static_cast<int>(n);
456
457
374k
    size_t const reachableStackDepth = m_dialect.reachableStackDepth();
458
374k
    if (stackLayout.size() > reachableStackDepth + 1)
459
216
    {
460
216
      size_t const unreachableSlots = stackLayout.size() - (reachableStackDepth + 1);
461
216
      StackTooDeepError error(
462
216
        _function.name,
463
216
        YulName{},
464
216
        static_cast<int>(unreachableSlots),
465
216
        "The function " +
466
216
        _function.name.str() +
467
216
        " has " +
468
216
        std::to_string(unreachableSlots) +
469
216
        " parameters or return variables too many to fit the stack size."
470
216
      );
471
216
      stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size()));
472
216
    }
473
374k
    else
474
374k
    {
475
1.23M
      while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
476
861k
        if (stackLayout.back() < 0)
477
281k
        {
478
281k
          m_assembly.appendInstruction(evmasm::Instruction::POP);
479
281k
          stackLayout.pop_back();
480
281k
        }
481
579k
        else
482
579k
        {
483
579k
          m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(stackLayout.size()) - static_cast<unsigned>(stackLayout.back()) - 1u));
484
579k
          std::swap(stackLayout[static_cast<size_t>(stackLayout.back())], stackLayout.back());
485
579k
        }
486
1.17M
      for (size_t i = 0; i < stackLayout.size(); ++i)
487
797k
        yulAssert(i == static_cast<size_t>(stackLayout[i]), "Error reshuffling stack.");
488
374k
    }
489
374k
  }
490
374k
  m_assembly.appendJump(
491
374k
    stackHeightBefore - static_cast<int>(_function.returnVariables.size()),
492
374k
    AbstractAssembly::JumpType::OutOfFunction
493
374k
  );
494
374k
  m_assembly.setStackHeight(stackHeightBefore);
495
374k
}
496
497
void CodeTransform::operator()(ForLoop const& _forLoop)
498
72.7k
{
499
72.7k
  Scope* originalScope = m_scope;
500
  // We start with visiting the block, but not finalizing it.
501
72.7k
  m_scope = m_info.scopes.at(&_forLoop.pre).get();
502
72.7k
  int stackStartHeight = m_assembly.stackHeight();
503
504
72.7k
  visitStatements(_forLoop.pre.statements);
505
506
72.7k
  AbstractAssembly::LabelID loopStart = m_assembly.newLabelId();
507
72.7k
  AbstractAssembly::LabelID postPart = m_assembly.newLabelId();
508
72.7k
  AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
509
510
72.7k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
511
72.7k
  m_assembly.appendLabel(loopStart);
512
513
72.7k
  visitExpression(*_forLoop.condition);
514
72.7k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
515
72.7k
  m_assembly.appendInstruction(evmasm::Instruction::ISZERO);
516
72.7k
  m_assembly.appendJumpToIf(loopEnd);
517
518
72.7k
  int const stackHeightBody = m_assembly.stackHeight();
519
72.7k
  m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} });
520
72.7k
  (*this)(_forLoop.body);
521
522
72.7k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
523
72.7k
  m_assembly.appendLabel(postPart);
524
525
72.7k
  (*this)(_forLoop.post);
526
527
72.7k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
528
72.7k
  m_assembly.appendJumpTo(loopStart);
529
72.7k
  m_assembly.appendLabel(loopEnd);
530
531
72.7k
  finalizeBlock(_forLoop.pre, stackStartHeight);
532
72.7k
  m_context->forLoopStack.pop();
533
72.7k
  m_scope = originalScope;
534
72.7k
}
535
536
int CodeTransform::appendPopUntil(int _targetDepth)
537
453k
{
538
453k
  int const stackDiffAfter = m_assembly.stackHeight() - _targetDepth;
539
617k
  for (int i = 0; i < stackDiffAfter; ++i)
540
164k
    m_assembly.appendInstruction(evmasm::Instruction::POP);
541
453k
  return stackDiffAfter;
542
453k
}
543
544
void CodeTransform::operator()(Break const& _break)
545
36.3k
{
546
36.3k
  yulAssert(!m_context->forLoopStack.empty(), "Invalid break-statement. Requires surrounding for-loop in code generation.");
547
36.3k
  m_assembly.setSourceLocation(originLocationOf(_break));
548
549
36.3k
  Context::JumpInfo const& jump = m_context->forLoopStack.top().done;
550
36.3k
  m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight));
551
36.3k
}
552
553
void CodeTransform::operator()(Continue const& _continue)
554
15.4k
{
555
15.4k
  yulAssert(!m_context->forLoopStack.empty(), "Invalid continue-statement. Requires surrounding for-loop in code generation.");
556
15.4k
  m_assembly.setSourceLocation(originLocationOf(_continue));
557
558
15.4k
  Context::JumpInfo const& jump = m_context->forLoopStack.top().post;
559
15.4k
  m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight));
560
15.4k
}
561
562
void CodeTransform::operator()(Leave const& _leaveStatement)
563
26.7k
{
564
26.7k
  yulAssert(m_functionExitLabel, "Invalid leave-statement. Requires surrounding function in code generation.");
565
26.7k
  yulAssert(m_functionExitStackHeight, "");
566
26.7k
  m_assembly.setSourceLocation(originLocationOf(_leaveStatement));
567
26.7k
  m_assembly.appendJumpTo(*m_functionExitLabel, appendPopUntil(*m_functionExitStackHeight));
568
26.7k
}
569
570
void CodeTransform::operator()(Block const& _block)
571
1.09M
{
572
1.09M
  Scope* originalScope = m_scope;
573
1.09M
  m_scope = m_info.scopes.at(&_block).get();
574
575
1.09M
  for (auto const& statement: _block.statements)
576
2.75M
    if (auto function = std::get_if<FunctionDefinition>(&statement))
577
374k
      createFunctionEntryID(*function);
578
579
1.09M
  int blockStartStackHeight = m_assembly.stackHeight();
580
1.09M
  visitStatements(_block.statements);
581
582
1.09M
  bool isOutermostFunctionBodyBlock = m_scope && m_scope->superScope && m_scope->superScope->functionScope;
583
1.09M
  bool performValidation = !m_allowStackOpt || !isOutermostFunctionBodyBlock;
584
1.09M
  finalizeBlock(_block, performValidation ? std::make_optional(blockStartStackHeight) : std::nullopt);
585
1.09M
  m_scope = originalScope;
586
1.09M
}
587
588
void CodeTransform::createFunctionEntryID(FunctionDefinition const& _function)
589
374k
{
590
374k
  Scope::Function& scopeFunction = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
591
374k
  yulAssert(!m_context->functionEntryIDs.count(&scopeFunction), "");
592
593
374k
  std::optional<size_t> astID;
594
374k
  if (_function.debugData)
595
374k
    astID = _function.debugData->astID;
596
597
374k
  bool nameAlreadySeen = !m_assignedNamedLabels.insert(_function.name).second;
598
599
374k
  if (m_useNamedLabelsForFunctions == UseNamedLabels::YesAndForceUnique)
600
374k
    yulAssert(!nameAlreadySeen);
601
602
374k
  m_context->functionEntryIDs[&scopeFunction] =
603
374k
    (
604
374k
      m_useNamedLabelsForFunctions != UseNamedLabels::Never &&
605
185k
      !nameAlreadySeen
606
374k
    ) ?
607
184k
    m_assembly.namedLabel(
608
184k
      _function.name.str(),
609
184k
      _function.parameters.size(),
610
184k
      _function.returnVariables.size(),
611
184k
      astID
612
184k
    ) :
613
374k
    m_assembly.newLabelId();
614
374k
}
615
616
AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _scopeFunction) const
617
940k
{
618
940k
  yulAssert(m_context->functionEntryIDs.count(&_scopeFunction), "");
619
940k
  return m_context->functionEntryIDs.at(&_scopeFunction);
620
940k
}
621
622
void CodeTransform::visitExpression(Expression const& _expression)
623
10.6M
{
624
10.6M
  int height = m_assembly.stackHeight();
625
10.6M
  std::visit(*this, _expression);
626
10.6M
  expectDeposit(1, height);
627
10.6M
}
628
629
void CodeTransform::setupReturnVariablesAndFunctionExit()
630
374k
{
631
374k
  yulAssert(isInsideFunction(), "");
632
374k
  yulAssert(!returnVariablesAndFunctionExitAreSetup(), "");
633
374k
  yulAssert(m_scope, "");
634
635
374k
  ScopeGuard scopeGuard([oldScope = m_scope, this] { m_scope = oldScope; });
636
374k
  if (!m_scope->functionScope)
637
166k
  {
638
166k
    yulAssert(m_scope->superScope && m_scope->superScope->functionScope, "");
639
166k
    m_scope = m_scope->superScope;
640
166k
  }
641
642
  // We could reuse unused slots for return variables, but it turns out this is detrimental in practice.
643
374k
  m_unusedStackSlots.clear();
644
645
374k
  if (m_delayedReturnVariables.empty())
646
125k
  {
647
125k
    m_functionExitStackHeight = 1;
648
125k
    return;
649
125k
  }
650
651
  // Allocate slots for return variables as if they were declared as variables in the virtual function scope.
652
249k
  for (NameWithDebugData const& var: m_delayedReturnVariables)
653
425k
    (*this)(VariableDeclaration{var.debugData, {var}, {}});
654
655
425k
  m_functionExitStackHeight = ranges::max(m_delayedReturnVariables | ranges::views::transform([&](NameWithDebugData const& _name) {
656
425k
    return variableStackHeight(_name.name);
657
425k
  })) + 1;
658
249k
  m_delayedReturnVariables.clear();
659
249k
}
660
661
namespace
662
{
663
664
bool statementNeedsReturnVariableSetup(Statement const& _statement, std::vector<NameWithDebugData> const& _returnVariables)
665
241k
{
666
241k
  if (std::holds_alternative<FunctionDefinition>(_statement))
667
29.4k
    return true;
668
211k
  if (
669
211k
    std::holds_alternative<ExpressionStatement>(_statement) ||
670
141k
    std::holds_alternative<Assignment>(_statement)
671
211k
  )
672
136k
  {
673
136k
    std::map<YulName, size_t> references = VariableReferencesCounter::countReferences(_statement);
674
136k
    auto isReferenced = [&references](NameWithDebugData const& _returnVariable) {
675
116k
      return references.count(_returnVariable.name);
676
116k
    };
677
136k
    if (ranges::none_of(_returnVariables, isReferenced))
678
74.4k
      return false;
679
136k
  }
680
137k
  return true;
681
211k
}
682
683
}
684
685
void CodeTransform::visitStatements(std::vector<Statement> const& _statements)
686
1.17M
{
687
1.17M
  std::optional<AbstractAssembly::LabelID> jumpTarget = std::nullopt;
688
689
1.17M
  for (auto const& statement: _statements)
690
2.76M
  {
691
2.76M
    freeUnusedVariables();
692
2.76M
    if (
693
2.76M
      isInsideFunction() &&
694
1.67M
      !returnVariablesAndFunctionExitAreSetup() &&
695
241k
      statementNeedsReturnVariableSetup(statement, m_delayedReturnVariables)
696
2.76M
    )
697
166k
      setupReturnVariablesAndFunctionExit();
698
699
2.76M
    auto const* functionDefinition = std::get_if<FunctionDefinition>(&statement);
700
2.76M
    if (functionDefinition && !jumpTarget)
701
104k
    {
702
104k
      m_assembly.setSourceLocation(originLocationOf(*functionDefinition));
703
104k
      jumpTarget = m_assembly.newLabelId();
704
104k
      m_assembly.appendJumpTo(*jumpTarget, 0);
705
104k
    }
706
2.66M
    else if (!functionDefinition && jumpTarget)
707
67.7k
    {
708
67.7k
      m_assembly.appendLabel(*jumpTarget);
709
67.7k
      jumpTarget = std::nullopt;
710
67.7k
    }
711
712
2.76M
    std::visit(*this, statement);
713
2.76M
  }
714
  // we may have a leftover jumpTarget
715
1.17M
  if (jumpTarget)
716
37.0k
    m_assembly.appendLabel(*jumpTarget);
717
718
1.17M
  freeUnusedVariables();
719
1.17M
}
720
721
void CodeTransform::finalizeBlock(Block const& _block, std::optional<int> blockStartStackHeight)
722
1.17M
{
723
1.17M
  m_assembly.setSourceLocation(originLocationOf(_block));
724
725
1.17M
  freeUnusedVariables();
726
727
  // pop variables
728
1.17M
  yulAssert(m_info.scopes.at(&_block).get() == m_scope, "");
729
1.17M
  for (auto const& id: m_scope->identifiers)
730
1.17M
    if (std::holds_alternative<Scope::Variable>(id.second))
731
800k
    {
732
800k
      Scope::Variable const& var = std::get<Scope::Variable>(id.second);
733
800k
      if (m_allowStackOpt)
734
623k
      {
735
623k
        yulAssert(!m_context->variableStackHeights.count(&var), "");
736
623k
        yulAssert(!m_context->variableReferences.count(&var), "");
737
623k
      }
738
176k
      else
739
176k
        m_assembly.appendInstruction(evmasm::Instruction::POP);
740
800k
    }
741
742
1.17M
  if (blockStartStackHeight)
743
954k
  {
744
954k
    int deposit = m_assembly.stackHeight() - *blockStartStackHeight;
745
954k
    yulAssert(deposit == 0, "Invalid stack height at end of block: " + std::to_string(deposit));
746
954k
  }
747
1.17M
}
748
749
void CodeTransform::generateMultiAssignment(std::vector<Identifier> const& _variableNames)
750
371k
{
751
371k
  yulAssert(m_scope, "");
752
371k
  for (auto const& variableName: _variableNames | ranges::views::reverse)
753
372k
    generateAssignment(variableName);
754
371k
}
755
756
void CodeTransform::generateAssignment(Identifier const& _variableName)
757
372k
{
758
372k
  yulAssert(m_scope, "");
759
372k
  if (auto var = m_scope->lookup(_variableName.name))
760
364k
  {
761
364k
    Scope::Variable const& _var = std::get<Scope::Variable>(*var);
762
364k
    if (size_t heightDiff = variableHeightDiff(_var, _variableName.name, true))
763
364k
      m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1)));
764
364k
    m_assembly.appendInstruction(evmasm::Instruction::POP);
765
364k
    decreaseReference(_variableName.name, _var);
766
364k
  }
767
8.52k
  else
768
8.52k
  {
769
8.52k
    yulAssert(
770
8.52k
      m_identifierAccessCodeGen,
771
8.52k
      "Identifier not found and no external access available."
772
8.52k
    );
773
8.52k
    m_identifierAccessCodeGen(_variableName, IdentifierContext::LValue, m_assembly);
774
8.52k
  }
775
372k
}
776
777
size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulName _varName, bool _forSwap)
778
2.23M
{
779
2.23M
  yulAssert(m_context->variableStackHeights.count(&_var), "");
780
2.23M
  size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var];
781
2.23M
  yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable.");
782
2.23M
  size_t limit = m_dialect.reachableStackDepth() + (_forSwap ? 1 : 0);
783
2.23M
  if (heightDiff > limit)
784
116k
  {
785
116k
    m_stackErrors.emplace_back(
786
116k
      _varName,
787
116k
      heightDiff - limit,
788
116k
      "Variable " +
789
116k
      _varName.str() +
790
116k
      " is " +
791
116k
      std::to_string(heightDiff - limit) +
792
116k
      " slot(s) too deep inside the stack. " +
793
116k
      stackTooDeepString
794
116k
    );
795
116k
    m_assembly.markAsInvalid();
796
116k
    return _forSwap ? 2 : 1;
797
116k
  }
798
2.11M
  return heightDiff;
799
2.23M
}
800
801
int CodeTransform::variableStackHeight(YulName _name) const
802
425k
{
803
425k
  Scope::Variable const* var = std::get_if<Scope::Variable>(m_scope->lookup(_name));
804
425k
  yulAssert(var, "");
805
425k
  return static_cast<int>(m_context->variableStackHeights.at(var));
806
425k
}
807
808
void CodeTransform::expectDeposit(int _deposit, int _oldHeight) const
809
11.5M
{
810
  yulAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
811
11.5M
}