Coverage Report

Created: 2026-07-13 07:08

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
523k
  m_assembly(_assembly),
63
523k
  m_info(_analysisInfo),
64
523k
  m_dialect(_dialect),
65
523k
  m_builtinContext(_builtinContext),
66
523k
  m_allowStackOpt(_allowStackOpt),
67
523k
  m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions),
68
523k
  m_identifierAccessCodeGen(std::move(_identifierAccessCodeGen)),
69
523k
  m_context(std::move(_context)),
70
523k
  m_delayedReturnVariables(std::move(_delayedReturnVariables)),
71
523k
  m_functionExitLabel(_functionExitLabel)
72
523k
{
73
523k
  if (!m_context)
74
152k
  {
75
    // initialize
76
152k
    m_context = std::make_shared<Context>();
77
152k
    if (m_allowStackOpt)
78
96.9k
      m_context->variableReferences = VariableReferenceCounter::run(m_info, _block);
79
152k
  }
80
523k
}
81
82
void CodeTransform::decreaseReference(YulName, Scope::Variable const& _var)
83
2.14M
{
84
2.14M
  if (!m_allowStackOpt)
85
799k
    return;
86
87
1.34M
  unsigned& ref = m_context->variableReferences.at(&_var);
88
1.34M
  yulAssert(ref >= 1, "");
89
1.34M
  --ref;
90
1.34M
  if (ref == 0)
91
626k
    m_variablesScheduledForDeletion.insert(&_var);
92
1.34M
}
93
94
bool CodeTransform::unreferenced(Scope::Variable const& _var) const
95
953k
{
96
953k
  return !m_context->variableReferences.count(&_var) || m_context->variableReferences[&_var] == 0;
97
953k
}
98
99
void CodeTransform::freeUnusedVariables(bool _popUnusedSlotsAtStackTop)
100
5.66M
{
101
5.66M
  if (!m_allowStackOpt)
102
1.74M
    return;
103
104
3.91M
  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
486k
        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.91M
  if (!returnVariablesAndFunctionExitAreSetup() && !m_scope->functionScope && m_scope->superScope && m_scope->superScope->functionScope)
113
343k
    for (auto const& identifier: m_scope->superScope->identifiers)
114
845k
      if (Scope::Variable const* var = std::get_if<Scope::Variable>(&identifier.second))
115
845k
        if (m_variablesScheduledForDeletion.count(var))
116
15.2k
          deleteVariable(*var);
117
118
3.91M
  if (_popUnusedSlotsAtStackTop)
119
4.12M
    while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1))
120
600k
    {
121
600k
      yulAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), "");
122
600k
      m_assembly.appendInstruction(evmasm::Instruction::POP);
123
600k
    }
124
3.91M
}
125
126
void CodeTransform::deleteVariable(Scope::Variable const& _var)
127
654k
{
128
654k
  yulAssert(m_allowStackOpt, "");
129
654k
  yulAssert(m_context->variableStackHeights.count(&_var) > 0, "");
130
654k
  m_unusedStackSlots.insert(static_cast<int>(m_context->variableStackHeights[&_var]));
131
654k
  m_context->variableStackHeights.erase(&_var);
132
654k
  m_context->variableReferences.erase(&_var);
133
654k
  m_variablesScheduledForDeletion.erase(&_var);
134
654k
}
135
136
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
137
975k
{
138
975k
  yulAssert(m_scope, "");
139
140
975k
  size_t const numVariables = _varDecl.variables.size();
141
975k
  auto heightAtStart = static_cast<size_t>(m_assembly.stackHeight());
142
975k
  if (_varDecl.value)
143
539k
  {
144
539k
    std::visit(*this, *_varDecl.value);
145
539k
    expectDeposit(static_cast<int>(numVariables), static_cast<int>(heightAtStart));
146
539k
    freeUnusedVariables(false);
147
539k
  }
148
435k
  else
149
435k
  {
150
435k
    m_assembly.setSourceLocation(originLocationOf(_varDecl));
151
435k
    size_t variablesLeft = numVariables;
152
896k
    while (variablesLeft--)
153
460k
      m_assembly.appendConstant(u256(0));
154
435k
  }
155
156
975k
  m_assembly.setSourceLocation(originLocationOf(_varDecl));
157
975k
  bool atTopOfStack = true;
158
2.19M
  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
267k
      continue;
166
167
953k
    if (unreferenced(var))
168
161k
    {
169
161k
      if (atTopOfStack)
170
152k
      {
171
152k
        m_context->variableStackHeights.erase(&var);
172
152k
        m_assembly.appendInstruction(evmasm::Instruction::POP);
173
152k
      }
174
8.97k
      else
175
8.97k
        m_variablesScheduledForDeletion.insert(&var);
176
161k
    }
177
792k
    else
178
792k
    {
179
792k
      bool foundUnusedSlot = false;
180
792k
      for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it)
181
43.7k
      {
182
43.7k
        if (m_assembly.stackHeight() - *it > static_cast<int>(m_dialect.reachableStackDepth() + 1))
183
476
          continue;
184
43.3k
        foundUnusedSlot = true;
185
43.3k
        auto slot = static_cast<size_t>(*it);
186
43.3k
        m_unusedStackSlots.erase(it);
187
43.3k
        m_context->variableStackHeights[&var] = slot;
188
43.3k
        if (size_t heightDiff = variableHeightDiff(var, varName, true))
189
43.3k
          m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1)));
190
43.3k
        m_assembly.appendInstruction(evmasm::Instruction::POP);
191
43.3k
        break;
192
43.7k
      }
193
792k
      if (!foundUnusedSlot)
194
748k
        atTopOfStack = false;
195
792k
    }
196
953k
  }
197
975k
}
198
199
void CodeTransform::stackError(StackTooDeepError _error, int _targetStackHeight)
200
195
{
201
195
  m_assembly.appendInstruction(evmasm::Instruction::INVALID);
202
  // Correct the stack.
203
1.48k
  while (m_assembly.stackHeight() > _targetStackHeight)
204
1.28k
    m_assembly.appendInstruction(evmasm::Instruction::POP);
205
195
  while (m_assembly.stackHeight() < _targetStackHeight)
206
0
    m_assembly.appendConstant(u256(0));
207
  // Store error.
208
195
  m_stackErrors.emplace_back(std::move(_error));
209
195
  m_assembly.markAsInvalid();
210
195
}
211
212
void CodeTransform::operator()(Assignment const& _assignment)
213
373k
{
214
373k
  int height = m_assembly.stackHeight();
215
373k
  std::visit(*this, *_assignment.value);
216
373k
  expectDeposit(static_cast<int>(_assignment.variableNames.size()), height);
217
218
373k
  m_assembly.setSourceLocation(originLocationOf(_assignment));
219
373k
  generateMultiAssignment(_assignment.variableNames);
220
373k
}
221
222
void CodeTransform::operator()(ExpressionStatement const& _statement)
223
883k
{
224
883k
  m_assembly.setSourceLocation(originLocationOf(_statement));
225
883k
  std::visit(*this, _statement.expression);
226
883k
}
227
228
void CodeTransform::operator()(FunctionCall const& _call)
229
4.65M
{
230
4.65M
  yulAssert(m_scope, "");
231
232
4.65M
  m_assembly.setSourceLocation(originLocationOf(_call));
233
4.65M
  if (BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_call.functionName, m_dialect))
234
4.12M
  {
235
4.12M
    for (auto&& [i, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
236
9.68M
      if (!builtin->literalArgument(i))
237
9.46M
        visitExpression(arg);
238
4.12M
    m_assembly.setSourceLocation(originLocationOf(_call));
239
4.12M
    builtin->generateCode(_call, m_assembly, m_builtinContext);
240
4.12M
  }
241
536k
  else
242
536k
  {
243
536k
    yulAssert(std::holds_alternative<Identifier>(_call.functionName));
244
536k
    AbstractAssembly::LabelID returnLabel = m_assembly.newLabelId();
245
536k
    m_assembly.appendLabelReference(returnLabel);
246
247
536k
    Scope::Function* function = nullptr;
248
536k
    yulAssert(m_scope->lookup(std::get<Identifier>(_call.functionName).name, GenericVisitor{
249
536k
      [](Scope::Variable&) { yulAssert(false, "Expected function name."); },
250
536k
      [&](Scope::Function& _function) { function = &_function; }
251
536k
    }), "Function name not found.");
252
536k
    yulAssert(function, "");
253
536k
    yulAssert(function->numArguments == _call.arguments.size(), "");
254
536k
    for (auto const& arg: _call.arguments | ranges::views::reverse)
255
826k
      visitExpression(arg);
256
536k
    m_assembly.setSourceLocation(originLocationOf(_call));
257
536k
    m_assembly.appendJumpTo(
258
536k
      functionEntryID(*function),
259
536k
      static_cast<int>(function->numReturns) - static_cast<int>(function->numArguments) - 1,
260
536k
      AbstractAssembly::JumpType::IntoFunction
261
536k
    );
262
536k
    m_assembly.appendLabel(returnLabel);
263
536k
  }
264
4.65M
}
265
266
void CodeTransform::operator()(Identifier const& _identifier)
267
1.82M
{
268
1.82M
  m_assembly.setSourceLocation(originLocationOf(_identifier));
269
  // First search internals, then externals.
270
1.82M
  yulAssert(m_scope, "");
271
1.82M
  if (m_scope->lookup(_identifier.name, GenericVisitor{
272
1.82M
    [&](Scope::Variable& _var)
273
1.82M
    {
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.77M
      if (size_t heightDiff = variableHeightDiff(_var, _identifier.name, false))
277
1.77M
        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.77M
      decreaseReference(_identifier.name, _var);
282
1.77M
    },
283
1.82M
    [](Scope::Function&)
284
1.82M
    {
285
0
      yulAssert(false, "Function not removed during desugaring.");
286
0
    }
287
1.82M
  }))
288
1.77M
  {
289
1.77M
    return;
290
1.77M
  }
291
51.1k
  yulAssert(
292
51.1k
    m_identifierAccessCodeGen,
293
51.1k
    "Identifier not found and no external access available."
294
51.1k
  );
295
51.1k
  m_identifierAccessCodeGen(_identifier, IdentifierContext::RValue, m_assembly);
296
51.1k
}
297
298
void CodeTransform::operator()(Literal const& _literal)
299
5.94M
{
300
5.94M
  m_assembly.setSourceLocation(originLocationOf(_literal));
301
5.94M
  m_assembly.appendConstant(_literal.value.value());
302
5.94M
}
303
304
void CodeTransform::operator()(If const& _if)
305
168k
{
306
168k
  visitExpression(*_if.condition);
307
168k
  m_assembly.setSourceLocation(originLocationOf(_if));
308
168k
  m_assembly.appendInstruction(evmasm::Instruction::ISZERO);
309
168k
  AbstractAssembly::LabelID end = m_assembly.newLabelId();
310
168k
  m_assembly.appendJumpToIf(end);
311
168k
  (*this)(_if.body);
312
168k
  m_assembly.setSourceLocation(originLocationOf(_if));
313
168k
  m_assembly.appendLabel(end);
314
168k
}
315
316
void CodeTransform::operator()(Switch const& _switch)
317
43.1k
{
318
43.1k
  visitExpression(*_switch.expression);
319
43.1k
  int expressionHeight = m_assembly.stackHeight();
320
43.1k
  std::map<Case const*, AbstractAssembly::LabelID> caseBodies;
321
43.1k
  AbstractAssembly::LabelID end = m_assembly.newLabelId();
322
43.1k
  for (Case const& c: _switch.cases)
323
76.1k
  {
324
76.1k
    if (c.value)
325
36.7k
    {
326
36.7k
      (*this)(*c.value);
327
36.7k
      m_assembly.setSourceLocation(originLocationOf(c));
328
36.7k
      AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId();
329
36.7k
      caseBodies[&c] = bodyLabel;
330
36.7k
      yulAssert(m_assembly.stackHeight() == expressionHeight + 1, "");
331
36.7k
      m_assembly.appendInstruction(evmasm::dupInstruction(2));
332
36.7k
      m_assembly.appendInstruction(evmasm::Instruction::EQ);
333
36.7k
      m_assembly.appendJumpToIf(bodyLabel);
334
36.7k
    }
335
39.3k
    else
336
      // default case
337
39.3k
      (*this)(c.body);
338
76.1k
  }
339
43.1k
  m_assembly.setSourceLocation(originLocationOf(_switch));
340
43.1k
  m_assembly.appendJumpTo(end);
341
342
43.1k
  size_t numCases = caseBodies.size();
343
43.1k
  for (auto const& c: caseBodies)
344
36.7k
  {
345
36.7k
    m_assembly.setSourceLocation(originLocationOf(*c.first));
346
36.7k
    m_assembly.appendLabel(c.second);
347
36.7k
    (*this)(c.first->body);
348
    // Avoid useless "jump to next" for the last case.
349
36.7k
    if (--numCases > 0)
350
21.6k
    {
351
21.6k
      m_assembly.setSourceLocation(originLocationOf(*c.first));
352
21.6k
      m_assembly.appendJumpTo(end);
353
21.6k
    }
354
36.7k
  }
355
356
43.1k
  m_assembly.setSourceLocation(originLocationOf(_switch));
357
43.1k
  m_assembly.appendLabel(end);
358
43.1k
  m_assembly.appendInstruction(evmasm::Instruction::POP);
359
43.1k
}
360
361
void CodeTransform::operator()(FunctionDefinition const& _function)
362
371k
{
363
371k
  yulAssert(m_scope, "");
364
371k
  yulAssert(m_scope->identifiers.count(_function.name), "");
365
371k
  Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
366
367
371k
  size_t height = 1;
368
371k
  yulAssert(m_info.scopes.at(&_function.body), "");
369
371k
  Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
370
371k
  yulAssert(virtualFunctionScope, "");
371
371k
  for (auto const& v: _function.parameters | ranges::views::reverse)
372
525k
  {
373
525k
    auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name));
374
525k
    m_context->variableStackHeights[&var] = height++;
375
525k
  }
376
377
371k
  m_assembly.setSourceLocation(originLocationOf(_function));
378
371k
  int const stackHeightBefore = m_assembly.stackHeight();
379
380
371k
  m_assembly.appendLabel(functionEntryID(function));
381
382
371k
  m_assembly.setStackHeight(static_cast<int>(height));
383
384
371k
  CodeTransform subTransform(
385
371k
    m_assembly,
386
371k
    m_info,
387
371k
    _function.body,
388
371k
    m_allowStackOpt,
389
371k
    m_dialect,
390
371k
    m_builtinContext,
391
371k
    m_identifierAccessCodeGen,
392
371k
    m_useNamedLabelsForFunctions,
393
371k
    m_context,
394
371k
    _function.returnVariables,
395
371k
    m_assembly.newLabelId()
396
371k
  );
397
371k
  subTransform.m_scope = virtualFunctionScope;
398
399
371k
  if (m_allowStackOpt)
400
    // Immediately delete entirely unused parameters.
401
221k
    for (auto const& v: _function.parameters | ranges::views::reverse)
402
301k
    {
403
301k
      auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name));
404
301k
      if (util::valueOrDefault(m_context->variableReferences, &var, 0u) == 0)
405
152k
        subTransform.deleteVariable(var);
406
301k
    }
407
408
371k
  if (!m_allowStackOpt)
409
149k
    subTransform.setupReturnVariablesAndFunctionExit();
410
411
371k
  subTransform.m_assignedNamedLabels = std::move(m_assignedNamedLabels);
412
413
371k
  subTransform(_function.body);
414
415
371k
  m_assignedNamedLabels = std::move(subTransform.m_assignedNamedLabels);
416
417
371k
  m_assembly.setSourceLocation(originLocationOf(_function));
418
371k
  if (!subTransform.m_stackErrors.empty())
419
52.9k
  {
420
52.9k
    m_assembly.markAsInvalid();
421
52.9k
    for (StackTooDeepError& stackError: subTransform.m_stackErrors)
422
96.2k
    {
423
96.2k
      if (stackError.functionName.empty())
424
95.4k
        stackError.functionName = _function.name;
425
96.2k
      m_stackErrors.emplace_back(std::move(stackError));
426
96.2k
    }
427
52.9k
  }
428
429
371k
  if (!subTransform.returnVariablesAndFunctionExitAreSetup())
430
50.5k
    subTransform.setupReturnVariablesAndFunctionExit();
431
371k
  appendPopUntil(*subTransform.m_functionExitStackHeight);
432
433
371k
  yulAssert(
434
371k
    subTransform.m_functionExitStackHeight &&
435
371k
    *subTransform.m_functionExitStackHeight == m_assembly.stackHeight(),
436
371k
    ""
437
371k
  );
438
439
371k
  m_assembly.appendLabel(*subTransform.m_functionExitLabel);
440
441
371k
  {
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
371k
    std::vector<int> stackLayout(static_cast<size_t>(m_assembly.stackHeight()), -1);
451
371k
    stackLayout[0] = static_cast<int>(_function.returnVariables.size()); // Move return label to the top
452
371k
    for (auto&& [n, returnVariable]: ranges::views::enumerate(_function.returnVariables))
453
424k
      stackLayout.at(m_context->variableStackHeights.at(
454
424k
        &std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name))
455
424k
      )) = static_cast<int>(n);
456
457
371k
    size_t const reachableStackDepth = m_dialect.reachableStackDepth();
458
371k
    if (stackLayout.size() > reachableStackDepth + 1)
459
195
    {
460
195
      size_t const unreachableSlots = stackLayout.size() - (reachableStackDepth + 1);
461
195
      StackTooDeepError error(
462
195
        _function.name,
463
195
        YulName{},
464
195
        static_cast<int>(unreachableSlots),
465
195
        "The function " +
466
195
        _function.name.str() +
467
195
        " has " +
468
195
        std::to_string(unreachableSlots) +
469
195
        " parameters or return variables too many to fit the stack size."
470
195
      );
471
195
      stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size()));
472
195
    }
473
370k
    else
474
370k
    {
475
1.21M
      while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
476
845k
        if (stackLayout.back() < 0)
477
273k
        {
478
273k
          m_assembly.appendInstruction(evmasm::Instruction::POP);
479
273k
          stackLayout.pop_back();
480
273k
        }
481
572k
        else
482
572k
        {
483
572k
          m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(stackLayout.size()) - static_cast<unsigned>(stackLayout.back()) - 1u));
484
572k
          std::swap(stackLayout[static_cast<size_t>(stackLayout.back())], stackLayout.back());
485
572k
        }
486
1.16M
      for (size_t i = 0; i < stackLayout.size(); ++i)
487
792k
        yulAssert(i == static_cast<size_t>(stackLayout[i]), "Error reshuffling stack.");
488
370k
    }
489
371k
  }
490
371k
  m_assembly.appendJump(
491
371k
    stackHeightBefore - static_cast<int>(_function.returnVariables.size()),
492
371k
    AbstractAssembly::JumpType::OutOfFunction
493
371k
  );
494
371k
  m_assembly.setStackHeight(stackHeightBefore);
495
371k
}
496
497
void CodeTransform::operator()(ForLoop const& _forLoop)
498
80.8k
{
499
80.8k
  Scope* originalScope = m_scope;
500
  // We start with visiting the block, but not finalizing it.
501
80.8k
  m_scope = m_info.scopes.at(&_forLoop.pre).get();
502
80.8k
  int stackStartHeight = m_assembly.stackHeight();
503
504
80.8k
  visitStatements(_forLoop.pre.statements);
505
506
80.8k
  AbstractAssembly::LabelID loopStart = m_assembly.newLabelId();
507
80.8k
  AbstractAssembly::LabelID postPart = m_assembly.newLabelId();
508
80.8k
  AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
509
510
80.8k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
511
80.8k
  m_assembly.appendLabel(loopStart);
512
513
80.8k
  visitExpression(*_forLoop.condition);
514
80.8k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
515
80.8k
  m_assembly.appendInstruction(evmasm::Instruction::ISZERO);
516
80.8k
  m_assembly.appendJumpToIf(loopEnd);
517
518
80.8k
  int const stackHeightBody = m_assembly.stackHeight();
519
80.8k
  m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} });
520
80.8k
  (*this)(_forLoop.body);
521
522
80.8k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
523
80.8k
  m_assembly.appendLabel(postPart);
524
525
80.8k
  (*this)(_forLoop.post);
526
527
80.8k
  m_assembly.setSourceLocation(originLocationOf(_forLoop));
528
80.8k
  m_assembly.appendJumpTo(loopStart);
529
80.8k
  m_assembly.appendLabel(loopEnd);
530
531
80.8k
  finalizeBlock(_forLoop.pre, stackStartHeight);
532
80.8k
  m_context->forLoopStack.pop();
533
80.8k
  m_scope = originalScope;
534
80.8k
}
535
536
int CodeTransform::appendPopUntil(int _targetDepth)
537
456k
{
538
456k
  int const stackDiffAfter = m_assembly.stackHeight() - _targetDepth;
539
611k
  for (int i = 0; i < stackDiffAfter; ++i)
540
155k
    m_assembly.appendInstruction(evmasm::Instruction::POP);
541
456k
  return stackDiffAfter;
542
456k
}
543
544
void CodeTransform::operator()(Break const& _break)
545
41.3k
{
546
41.3k
  yulAssert(!m_context->forLoopStack.empty(), "Invalid break-statement. Requires surrounding for-loop in code generation.");
547
41.3k
  m_assembly.setSourceLocation(originLocationOf(_break));
548
549
41.3k
  Context::JumpInfo const& jump = m_context->forLoopStack.top().done;
550
41.3k
  m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight));
551
41.3k
}
552
553
void CodeTransform::operator()(Continue const& _continue)
554
14.9k
{
555
14.9k
  yulAssert(!m_context->forLoopStack.empty(), "Invalid continue-statement. Requires surrounding for-loop in code generation.");
556
14.9k
  m_assembly.setSourceLocation(originLocationOf(_continue));
557
558
14.9k
  Context::JumpInfo const& jump = m_context->forLoopStack.top().post;
559
14.9k
  m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight));
560
14.9k
}
561
562
void CodeTransform::operator()(Leave const& _leaveStatement)
563
28.7k
{
564
28.7k
  yulAssert(m_functionExitLabel, "Invalid leave-statement. Requires surrounding function in code generation.");
565
28.7k
  yulAssert(m_functionExitStackHeight, "");
566
28.7k
  m_assembly.setSourceLocation(originLocationOf(_leaveStatement));
567
28.7k
  m_assembly.appendJumpTo(*m_functionExitLabel, appendPopUntil(*m_functionExitStackHeight));
568
28.7k
}
569
570
void CodeTransform::operator()(Block const& _block)
571
1.11M
{
572
1.11M
  Scope* originalScope = m_scope;
573
1.11M
  m_scope = m_info.scopes.at(&_block).get();
574
575
1.11M
  for (auto const& statement: _block.statements)
576
2.72M
    if (auto function = std::get_if<FunctionDefinition>(&statement))
577
371k
      createFunctionEntryID(*function);
578
579
1.11M
  int blockStartStackHeight = m_assembly.stackHeight();
580
1.11M
  visitStatements(_block.statements);
581
582
1.11M
  bool isOutermostFunctionBodyBlock = m_scope && m_scope->superScope && m_scope->superScope->functionScope;
583
1.11M
  bool performValidation = !m_allowStackOpt || !isOutermostFunctionBodyBlock;
584
1.11M
  finalizeBlock(_block, performValidation ? std::make_optional(blockStartStackHeight) : std::nullopt);
585
1.11M
  m_scope = originalScope;
586
1.11M
}
587
588
void CodeTransform::createFunctionEntryID(FunctionDefinition const& _function)
589
371k
{
590
371k
  Scope::Function& scopeFunction = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
591
371k
  yulAssert(!m_context->functionEntryIDs.count(&scopeFunction), "");
592
593
371k
  std::optional<size_t> astID;
594
371k
  if (_function.debugData)
595
371k
    astID = _function.debugData->astID;
596
597
371k
  bool nameAlreadySeen = !m_assignedNamedLabels.insert(_function.name).second;
598
599
371k
  if (m_useNamedLabelsForFunctions == UseNamedLabels::YesAndForceUnique)
600
371k
    yulAssert(!nameAlreadySeen);
601
602
371k
  m_context->functionEntryIDs[&scopeFunction] =
603
371k
    (
604
371k
      m_useNamedLabelsForFunctions != UseNamedLabels::Never &&
605
174k
      !nameAlreadySeen
606
371k
    ) ?
607
173k
    m_assembly.namedLabel(
608
173k
      _function.name.str(),
609
173k
      _function.parameters.size(),
610
173k
      _function.returnVariables.size(),
611
173k
      astID
612
173k
    ) :
613
371k
    m_assembly.newLabelId();
614
371k
}
615
616
AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _scopeFunction) const
617
907k
{
618
907k
  yulAssert(m_context->functionEntryIDs.count(&_scopeFunction), "");
619
907k
  return m_context->functionEntryIDs.at(&_scopeFunction);
620
907k
}
621
622
void CodeTransform::visitExpression(Expression const& _expression)
623
10.5M
{
624
10.5M
  int height = m_assembly.stackHeight();
625
10.5M
  std::visit(*this, _expression);
626
10.5M
  expectDeposit(1, height);
627
10.5M
}
628
629
void CodeTransform::setupReturnVariablesAndFunctionExit()
630
371k
{
631
371k
  yulAssert(isInsideFunction(), "");
632
371k
  yulAssert(!returnVariablesAndFunctionExitAreSetup(), "");
633
371k
  yulAssert(m_scope, "");
634
635
371k
  ScopeGuard scopeGuard([oldScope = m_scope, this] { m_scope = oldScope; });
636
371k
  if (!m_scope->functionScope)
637
171k
  {
638
171k
    yulAssert(m_scope->superScope && m_scope->superScope->functionScope, "");
639
171k
    m_scope = m_scope->superScope;
640
171k
  }
641
642
  // We could reuse unused slots for return variables, but it turns out this is detrimental in practice.
643
371k
  m_unusedStackSlots.clear();
644
645
371k
  if (m_delayedReturnVariables.empty())
646
123k
  {
647
123k
    m_functionExitStackHeight = 1;
648
123k
    return;
649
123k
  }
650
651
  // Allocate slots for return variables as if they were declared as variables in the virtual function scope.
652
247k
  for (NameWithDebugData const& var: m_delayedReturnVariables)
653
424k
    (*this)(VariableDeclaration{var.debugData, {var}, {}});
654
655
424k
  m_functionExitStackHeight = ranges::max(m_delayedReturnVariables | ranges::views::transform([&](NameWithDebugData const& _name) {
656
424k
    return variableStackHeight(_name.name);
657
424k
  })) + 1;
658
247k
  m_delayedReturnVariables.clear();
659
247k
}
660
661
namespace
662
{
663
664
bool statementNeedsReturnVariableSetup(Statement const& _statement, std::vector<NameWithDebugData> const& _returnVariables)
665
242k
{
666
242k
  if (std::holds_alternative<FunctionDefinition>(_statement))
667
28.5k
    return true;
668
213k
  if (
669
213k
    std::holds_alternative<ExpressionStatement>(_statement) ||
670
146k
    std::holds_alternative<Assignment>(_statement)
671
213k
  )
672
134k
  {
673
134k
    std::map<YulName, size_t> references = VariableReferencesCounter::countReferences(_statement);
674
134k
    auto isReferenced = [&references](NameWithDebugData const& _returnVariable) {
675
124k
      return references.count(_returnVariable.name);
676
124k
    };
677
134k
    if (ranges::none_of(_returnVariables, isReferenced))
678
71.2k
      return false;
679
134k
  }
680
142k
  return true;
681
213k
}
682
683
}
684
685
void CodeTransform::visitStatements(std::vector<Statement> const& _statements)
686
1.19M
{
687
1.19M
  std::optional<AbstractAssembly::LabelID> jumpTarget = std::nullopt;
688
689
1.19M
  for (auto const& statement: _statements)
690
2.73M
  {
691
2.73M
    freeUnusedVariables();
692
2.73M
    if (
693
2.73M
      isInsideFunction() &&
694
1.62M
      !returnVariablesAndFunctionExitAreSetup() &&
695
242k
      statementNeedsReturnVariableSetup(statement, m_delayedReturnVariables)
696
2.73M
    )
697
171k
      setupReturnVariablesAndFunctionExit();
698
699
2.73M
    auto const* functionDefinition = std::get_if<FunctionDefinition>(&statement);
700
2.73M
    if (functionDefinition && !jumpTarget)
701
105k
    {
702
105k
      m_assembly.setSourceLocation(originLocationOf(*functionDefinition));
703
105k
      jumpTarget = m_assembly.newLabelId();
704
105k
      m_assembly.appendJumpTo(*jumpTarget, 0);
705
105k
    }
706
2.63M
    else if (!functionDefinition && jumpTarget)
707
66.3k
    {
708
66.3k
      m_assembly.appendLabel(*jumpTarget);
709
66.3k
      jumpTarget = std::nullopt;
710
66.3k
    }
711
712
2.73M
    std::visit(*this, statement);
713
2.73M
  }
714
  // we may have a leftover jumpTarget
715
1.19M
  if (jumpTarget)
716
38.9k
    m_assembly.appendLabel(*jumpTarget);
717
718
1.19M
  freeUnusedVariables();
719
1.19M
}
720
721
void CodeTransform::finalizeBlock(Block const& _block, std::optional<int> blockStartStackHeight)
722
1.19M
{
723
1.19M
  m_assembly.setSourceLocation(originLocationOf(_block));
724
725
1.19M
  freeUnusedVariables();
726
727
  // pop variables
728
1.19M
  yulAssert(m_info.scopes.at(&_block).get() == m_scope, "");
729
1.19M
  for (auto const& id: m_scope->identifiers)
730
1.16M
    if (std::holds_alternative<Scope::Variable>(id.second))
731
795k
    {
732
795k
      Scope::Variable const& var = std::get<Scope::Variable>(id.second);
733
795k
      if (m_allowStackOpt)
734
638k
      {
735
638k
        yulAssert(!m_context->variableStackHeights.count(&var), "");
736
638k
        yulAssert(!m_context->variableReferences.count(&var), "");
737
638k
      }
738
156k
      else
739
156k
        m_assembly.appendInstruction(evmasm::Instruction::POP);
740
795k
    }
741
742
1.19M
  if (blockStartStackHeight)
743
970k
  {
744
970k
    int deposit = m_assembly.stackHeight() - *blockStartStackHeight;
745
970k
    yulAssert(deposit == 0, "Invalid stack height at end of block: " + std::to_string(deposit));
746
970k
  }
747
1.19M
}
748
749
void CodeTransform::generateMultiAssignment(std::vector<Identifier> const& _variableNames)
750
373k
{
751
373k
  yulAssert(m_scope, "");
752
373k
  for (auto const& variableName: _variableNames | ranges::views::reverse)
753
374k
    generateAssignment(variableName);
754
373k
}
755
756
void CodeTransform::generateAssignment(Identifier const& _variableName)
757
374k
{
758
374k
  yulAssert(m_scope, "");
759
374k
  if (auto var = m_scope->lookup(_variableName.name))
760
367k
  {
761
367k
    Scope::Variable const& _var = std::get<Scope::Variable>(*var);
762
367k
    if (size_t heightDiff = variableHeightDiff(_var, _variableName.name, true))
763
367k
      m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1)));
764
367k
    m_assembly.appendInstruction(evmasm::Instruction::POP);
765
367k
    decreaseReference(_variableName.name, _var);
766
367k
  }
767
7.53k
  else
768
7.53k
  {
769
7.53k
    yulAssert(
770
7.53k
      m_identifierAccessCodeGen,
771
7.53k
      "Identifier not found and no external access available."
772
7.53k
    );
773
7.53k
    m_identifierAccessCodeGen(_variableName, IdentifierContext::LValue, m_assembly);
774
7.53k
  }
775
374k
}
776
777
size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulName _varName, bool _forSwap)
778
2.18M
{
779
2.18M
  yulAssert(m_context->variableStackHeights.count(&_var), "");
780
2.18M
  size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var];
781
2.18M
  yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable.");
782
2.18M
  size_t limit = m_dialect.reachableStackDepth() + (_forSwap ? 1 : 0);
783
2.18M
  if (heightDiff > limit)
784
132k
  {
785
132k
    m_stackErrors.emplace_back(
786
132k
      _varName,
787
132k
      heightDiff - limit,
788
132k
      "Variable " +
789
132k
      _varName.str() +
790
132k
      " is " +
791
132k
      std::to_string(heightDiff - limit) +
792
132k
      " slot(s) too deep inside the stack. " +
793
132k
      stackTooDeepString
794
132k
    );
795
132k
    m_assembly.markAsInvalid();
796
132k
    return _forSwap ? 2 : 1;
797
132k
  }
798
2.05M
  return heightDiff;
799
2.18M
}
800
801
int CodeTransform::variableStackHeight(YulName _name) const
802
424k
{
803
424k
  Scope::Variable const* var = std::get_if<Scope::Variable>(m_scope->lookup(_name));
804
424k
  yulAssert(var, "");
805
424k
  return static_cast<int>(m_context->variableStackHeights.at(var));
806
424k
}
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
}