Coverage Report

Created: 2026-09-14 06:38

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