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/ControlFlowGraphBuilder.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
 * Transformation of a Yul AST into a control flow graph.
20
 */
21
22
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
23
#include <libyul/AST.h>
24
#include <libyul/Exceptions.h>
25
#include <libyul/Utilities.h>
26
#include <libyul/ControlFlowSideEffectsCollector.h>
27
#include <libyul/backends/evm/EVMDialect.h>
28
29
#include <libsolutil/Visitor.h>
30
#include <libsolutil/Algorithms.h>
31
32
#include <range/v3/action/push_back.hpp>
33
#include <range/v3/action/erase.hpp>
34
#include <range/v3/range/conversion.hpp>
35
#include <range/v3/view/concat.hpp>
36
#include <range/v3/view/drop_last.hpp>
37
#include <range/v3/view/enumerate.hpp>
38
#include <range/v3/view/filter.hpp>
39
#include <range/v3/view/iota.hpp>
40
#include <range/v3/view/map.hpp>
41
#include <range/v3/view/reverse.hpp>
42
#include <range/v3/view/single.hpp>
43
#include <range/v3/view/take_last.hpp>
44
#include <range/v3/view/transform.hpp>
45
46
using namespace solidity;
47
using namespace solidity::yul;
48
49
namespace
50
{
51
/// Removes edges to blocks that are not reachable.
52
void cleanUnreachable(CFG& _cfg)
53
214k
{
54
  // If operation is a function call it adds the callee entry as child
55
214k
  auto const addFunctionsEntries = [&_cfg](CFG::BasicBlock* _node, auto&& _addChild)
56
3.34M
  {
57
3.34M
    for (auto const& operation: _node->operations)
58
10.2M
    {
59
10.2M
      if (auto const* functionCall = std::get_if<CFG::FunctionCall>(&operation.operation))
60
1.00M
      {
61
1.00M
        auto const functionInfo = _cfg.functionInfo.at(&(functionCall->function.get()));
62
1.00M
        _addChild(functionInfo.entry);
63
1.00M
      }
64
10.2M
    }
65
3.34M
  };
66
67
  // Determine which blocks are reachable from the entry.
68
214k
  util::BreadthFirstSearch<CFG::BasicBlock*> reachabilityCheck{{_cfg.entry}};
69
3.34M
  reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) {
70
3.34M
    addFunctionsEntries(_node, _addChild);
71
3.34M
    visit(util::GenericVisitor{
72
3.34M
      [&](CFG::BasicBlock::Jump const& _jump) {
73
1.58M
        _addChild(_jump.target);
74
1.58M
      },
75
3.34M
      [&](CFG::BasicBlock::ConditionalJump const& _jump) {
76
864k
        _addChild(_jump.zero);
77
864k
        _addChild(_jump.nonZero);
78
864k
      },
79
3.34M
      [](CFG::BasicBlock::FunctionReturn const&) {},
80
3.34M
      [](CFG::BasicBlock::Terminated const&) {},
81
3.34M
      [](CFG::BasicBlock::MainExit const&) {}
82
3.34M
    }, _node->exit);
83
3.34M
  });
84
85
  // Remove all entries from unreachable nodes from the graph.
86
214k
  for (CFG::BasicBlock* node: reachabilityCheck.visited)
87
3.59M
    std::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool {
88
3.59M
      return !reachabilityCheck.visited.count(entry);
89
3.59M
    });
90
91
  // Remove functions which are never referenced.
92
542k
  _cfg.functions.erase(std::remove_if(_cfg.functions.begin(), _cfg.functions.end(), [&](auto const& item) {
93
542k
    return !reachabilityCheck.visited.count(_cfg.functionInfo.at(item).entry);
94
542k
  }), _cfg.functions.end());
95
96
  // Remove functionInfos which are never referenced.
97
542k
  std::erase_if(_cfg.functionInfo, [&](auto const& entry) -> bool {
98
542k
    return !reachabilityCheck.visited.count(entry.second.entry);
99
542k
  });
100
214k
}
101
102
/// Sets the ``recursive`` member to ``true`` for all recursive function calls.
103
void markRecursiveCalls(CFG& _cfg)
104
214k
{
105
214k
  std::map<CFG::BasicBlock*, std::vector<CFG::FunctionCall*>> callsPerBlock;
106
214k
  auto const& findCalls = [&](CFG::BasicBlock* _block)
107
2.44M
  {
108
2.44M
    if (auto* calls = util::valueOrNullptr(callsPerBlock, _block))
109
1.93M
      return *calls;
110
504k
    std::vector<CFG::FunctionCall*>& calls = callsPerBlock[_block];
111
1.85M
    util::BreadthFirstSearch<CFG::BasicBlock*>{{_block}}.run([&](CFG::BasicBlock* _block, auto _addChild) {
112
1.85M
      for (auto& operation: _block->operations)
113
4.80M
        if (auto* functionCall = std::get_if<CFG::FunctionCall>(&operation.operation))
114
580k
          calls.emplace_back(functionCall);
115
1.85M
      std::visit(util::GenericVisitor{
116
1.85M
        [&](CFG::BasicBlock::MainExit const&) {},
117
1.85M
        [&](CFG::BasicBlock::Jump const& _jump)
118
1.85M
        {
119
823k
          _addChild(_jump.target);
120
823k
        },
121
1.85M
        [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
122
1.85M
        {
123
419k
          _addChild(_conditionalJump.zero);
124
419k
          _addChild(_conditionalJump.nonZero);
125
419k
        },
126
1.85M
        [&](CFG::BasicBlock::FunctionReturn const&) {},
127
1.85M
        [&](CFG::BasicBlock::Terminated const&) {},
128
1.85M
      }, _block->exit);
129
1.85M
    });
130
504k
    return calls;
131
2.44M
  };
132
214k
  for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
133
504k
    for (CFG::FunctionCall* call: findCalls(functionInfo.entry))
134
580k
    {
135
580k
      util::BreadthFirstSearch<CFG::FunctionCall*> breadthFirstSearch{{call}};
136
1.97M
      breadthFirstSearch.run([&](CFG::FunctionCall* _call, auto _addChild) {
137
1.97M
        auto& calledFunctionInfo = _cfg.functionInfo.at(&_call->function.get());
138
1.97M
        if (&calledFunctionInfo == &functionInfo)
139
33.7k
        {
140
33.7k
          call->recursive = true;
141
33.7k
          breadthFirstSearch.abort();
142
33.7k
          return;
143
33.7k
        }
144
1.93M
        for (CFG::FunctionCall* nestedCall: findCalls(_cfg.functionInfo.at(&_call->function.get()).entry))
145
3.67M
          _addChild(nestedCall);
146
1.93M
      });
147
580k
    }
148
214k
}
149
150
/// Marks each cut-vertex in the CFG, i.e. each block that begins a disconnected sub-graph of the CFG.
151
/// Entering such a block means that control flow will never return to a previously visited block.
152
void markStartsOfSubGraphs(CFG& _cfg)
153
214k
{
154
214k
  std::vector<CFG::BasicBlock*> entries;
155
214k
  entries.emplace_back(_cfg.entry);
156
214k
  for (auto&& functionInfo: _cfg.functionInfo | ranges::views::values)
157
504k
    entries.emplace_back(functionInfo.entry);
158
214k
  for (auto& entry: entries)
159
718k
  {
160
    /**
161
     * Detect bridges following Algorithm 1 in https://arxiv.org/pdf/2108.07346.pdf
162
     * and mark the bridge targets as starts of sub-graphs.
163
     */
164
718k
    std::set<CFG::BasicBlock*> visited;
165
718k
    std::map<CFG::BasicBlock*, size_t> disc;
166
718k
    std::map<CFG::BasicBlock*, size_t> low;
167
718k
    std::map<CFG::BasicBlock*, CFG::BasicBlock*> parent;
168
718k
    size_t time = 0;
169
3.34M
    auto dfs = [&](CFG::BasicBlock* _u, auto _recurse) -> void {
170
3.34M
      visited.insert(_u);
171
3.34M
      disc[_u] = low[_u] = time;
172
3.34M
      time++;
173
174
3.34M
      std::vector<CFG::BasicBlock*> children = _u->entries;
175
3.34M
      visit(util::GenericVisitor{
176
3.34M
        [&](CFG::BasicBlock::Jump const& _jump) {
177
1.58M
          children.emplace_back(_jump.target);
178
1.58M
        },
179
3.34M
        [&](CFG::BasicBlock::ConditionalJump const& _jump) {
180
864k
          children.emplace_back(_jump.zero);
181
864k
          children.emplace_back(_jump.nonZero);
182
864k
        },
183
3.34M
        [&](CFG::BasicBlock::FunctionReturn const&) {},
184
3.34M
        [&](CFG::BasicBlock::Terminated const&) { _u->isStartOfSubGraph = true; },
185
3.34M
        [&](CFG::BasicBlock::MainExit const&) { _u->isStartOfSubGraph = true; }
186
3.34M
      }, _u->exit);
187
3.34M
      yulAssert(!util::contains(children, _u));
188
189
3.34M
      for (CFG::BasicBlock* v: children)
190
6.63M
        if (!visited.count(v))
191
2.62M
        {
192
2.62M
          parent[v] = _u;
193
2.62M
          _recurse(v, _recurse);
194
2.62M
          low[_u] = std::min(low[_u], low[v]);
195
2.62M
          if (low[v] > disc[_u])
196
1.04M
          {
197
            // _u <-> v is a cut edge in the undirected graph
198
1.04M
            bool edgeVtoU = util::contains(_u->entries, v);
199
1.04M
            bool edgeUtoV = util::contains(v->entries, _u);
200
1.04M
            if (edgeVtoU && !edgeUtoV)
201
              // Cut edge v -> _u
202
0
              _u->isStartOfSubGraph = true;
203
1.04M
            else if (edgeUtoV && !edgeVtoU)
204
              // Cut edge _u -> v
205
1.01M
              v->isStartOfSubGraph = true;
206
1.04M
          }
207
2.62M
        }
208
4.01M
        else if (v != parent[_u])
209
1.35M
          low[_u] = std::min(low[_u], disc[v]);
210
3.34M
    };
211
718k
    dfs(entry, dfs);
212
718k
  }
213
214k
}
214
215
/// Marks each block that needs to maintain a clean stack. That is each block that has an outgoing
216
/// path to a function return.
217
void markNeedsCleanStack(CFG& _cfg)
218
214k
{
219
214k
  for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
220
504k
    for (CFG::BasicBlock* exit: functionInfo.exits)
221
1.82M
      util::BreadthFirstSearch<CFG::BasicBlock*>{{exit}}.run([&](CFG::BasicBlock* _block, auto _addChild) {
222
1.82M
        _block->needsCleanStack = true;
223
1.82M
        for (CFG::BasicBlock* entry: _block->entries)
224
1.54M
          _addChild(entry);
225
1.82M
      });
226
214k
}
227
}
228
229
std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
230
  AsmAnalysisInfo const& _analysisInfo,
231
  Dialect const& _dialect,
232
  Block const& _block
233
)
234
214k
{
235
214k
  auto result = std::make_unique<CFG>();
236
214k
  result->entry = &result->makeBlock(debugDataOf(_block));
237
238
214k
  ControlFlowSideEffectsCollector sideEffects(_dialect, _block);
239
214k
  ControlFlowGraphBuilder builder(*result, _analysisInfo, sideEffects.functionSideEffects(), _dialect);
240
214k
  builder.m_currentBlock = result->entry;
241
214k
  builder(_block);
242
243
214k
  cleanUnreachable(*result);
244
214k
  markRecursiveCalls(*result);
245
214k
  markStartsOfSubGraphs(*result);
246
214k
  markNeedsCleanStack(*result);
247
248
  // TODO: It might be worthwhile to run some further simplifications on the graph itself here.
249
  // E.g. if there is a jump to a node that has the jumping node as its only entry, the nodes can be fused, etc.
250
251
214k
  return result;
252
214k
}
253
254
ControlFlowGraphBuilder::ControlFlowGraphBuilder(
255
  CFG& _graph,
256
  AsmAnalysisInfo const& _analysisInfo,
257
  util::unordered_flat_map<FunctionDefinition const*, ControlFlowSideEffects> const& _functionSideEffects,
258
  Dialect const& _dialect
259
):
260
757k
  m_graph(_graph),
261
757k
  m_info(_analysisInfo),
262
757k
  m_functionSideEffects(_functionSideEffects),
263
757k
  m_dialect(_dialect)
264
757k
{}
265
266
StackSlot ControlFlowGraphBuilder::operator()(Literal const& _literal)
267
8.91M
{
268
8.91M
  return LiteralSlot{_literal.value.value(), _literal.debugData};
269
8.91M
}
270
271
StackSlot ControlFlowGraphBuilder::operator()(Identifier const& _identifier)
272
2.85M
{
273
2.85M
  return VariableSlot{lookupVariable(_identifier.name), _identifier.debugData};
274
2.85M
}
275
276
StackSlot ControlFlowGraphBuilder::operator()(Expression const& _expression)
277
0
{
278
0
  return std::visit(*this, _expression);
279
0
}
280
281
StackSlot ControlFlowGraphBuilder::operator()(FunctionCall const& _call)
282
3.94M
{
283
3.94M
  Stack const& output = visitFunctionCall(_call);
284
3.94M
  yulAssert(output.size() == 1, "");
285
3.94M
  return output.front();
286
3.94M
}
287
288
void ControlFlowGraphBuilder::operator()(VariableDeclaration const& _varDecl)
289
1.42M
{
290
1.42M
  yulAssert(m_currentBlock, "");
291
1.60M
  auto declaredVariables = _varDecl.variables | ranges::views::transform([&](NameWithDebugData const& _var) {
292
1.60M
    return VariableSlot{lookupVariable(_var.name), _var.debugData};
293
1.60M
  }) | ranges::to<std::vector<VariableSlot>>;
294
1.42M
  Stack input;
295
1.42M
  if (_varDecl.value)
296
1.41M
    input = visitAssignmentRightHandSide(*_varDecl.value, declaredVariables.size());
297
15.2k
  else
298
15.2k
    input = Stack(_varDecl.variables.size(), LiteralSlot{0, _varDecl.debugData});
299
1.42M
  m_currentBlock->operations.emplace_back(CFG::Operation{
300
1.42M
    std::move(input),
301
1.42M
    declaredVariables | ranges::to<Stack>,
302
1.42M
    CFG::Assignment{_varDecl.debugData, declaredVariables}
303
1.42M
  });
304
1.42M
}
305
void ControlFlowGraphBuilder::operator()(Assignment const& _assignment)
306
916k
{
307
920k
  auto assignedVariables = _assignment.variableNames | ranges::views::transform([&](Identifier const& _var) {
308
920k
    return VariableSlot{lookupVariable(_var.name), _var.debugData};
309
920k
  }) | ranges::to<std::vector<VariableSlot>>;
310
311
916k
  Stack input = visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size());
312
916k
  yulAssert(m_currentBlock);
313
916k
  m_currentBlock->operations.emplace_back(CFG::Operation{
314
916k
    std::move(input),
315
    // output
316
916k
    assignedVariables | ranges::to<Stack>,
317
    // operation
318
916k
    CFG::Assignment{_assignment.debugData, assignedVariables}
319
916k
  });
320
916k
}
321
void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
322
2.82M
{
323
2.82M
  std::visit(util::GenericVisitor{
324
2.82M
    [&](FunctionCall const& _call) {
325
2.82M
      Stack const& output = visitFunctionCall(_call);
326
2.82M
      yulAssert(output.empty(), "");
327
2.82M
    },
328
2.82M
    [&](auto const&) { yulAssert(false, ""); }
Unexecuted instantiation: ControlFlowGraphBuilder.cpp:auto solidity::yul::ControlFlowGraphBuilder::operator()(solidity::yul::ExpressionStatement const&)::$_1::operator()<solidity::yul::Identifier>(solidity::yul::Identifier const&) const
Unexecuted instantiation: ControlFlowGraphBuilder.cpp:auto solidity::yul::ControlFlowGraphBuilder::operator()(solidity::yul::ExpressionStatement const&)::$_1::operator()<solidity::yul::Literal>(solidity::yul::Literal const&) const
329
2.82M
  }, _exprStmt.expression);
330
2.82M
}
331
332
void ControlFlowGraphBuilder::operator()(Block const& _block)
333
2.97M
{
334
2.97M
  ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get());
335
2.97M
  for (auto const& statement: _block.statements)
336
6.86M
    if (auto const* function = std::get_if<FunctionDefinition>(&statement))
337
542k
      registerFunction(*function);
338
2.97M
  for (auto const& statement: _block.statements)
339
6.86M
    std::visit(*this, statement);
340
2.97M
}
341
342
void ControlFlowGraphBuilder::operator()(If const& _if)
343
249k
{
344
249k
  auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body));
345
249k
  auto& afterIf = m_graph.makeBlock(debugDataOf(*m_currentBlock));
346
249k
  StackSlot condition = std::visit(*this, *_if.condition);
347
249k
  makeConditionalJump(debugDataOf(_if), std::move(condition), ifBranch, afterIf);
348
249k
  m_currentBlock = &ifBranch;
349
249k
  (*this)(_if.body);
350
249k
  jump(debugDataOf(_if.body), afterIf);
351
249k
}
352
353
void ControlFlowGraphBuilder::operator()(Switch const& _switch)
354
103k
{
355
103k
  yulAssert(m_currentBlock, "");
356
103k
  langutil::DebugData::ConstPtr preSwitchDebugData = debugDataOf(_switch);
357
358
103k
  auto ghostVariableId = m_graph.ghostVariables.size();
359
103k
  YulName ghostVariableName("GHOST[" + std::to_string(ghostVariableId) + "]");
360
103k
  auto& ghostVar = m_graph.ghostVariables.emplace_back(Scope::Variable{ghostVariableName});
361
362
  // Artificially generate:
363
  // let <ghostVariable> := <switchExpression>
364
103k
  VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)};
365
103k
  StackSlot expression = std::visit(*this, *_switch.expression);
366
103k
  m_currentBlock->operations.emplace_back(CFG::Operation{
367
103k
    Stack{std::move(expression)},
368
103k
    Stack{ghostVarSlot},
369
103k
    CFG::Assignment{_switch.debugData, {ghostVarSlot}}
370
103k
  });
371
372
103k
  std::optional<BuiltinHandle> const& equalityBuiltinHandle = m_dialect.equalityFunctionHandle();
373
103k
  yulAssert(equalityBuiltinHandle);
374
375
  // Artificially generate:
376
  // eq(<literal>, <ghostVariable>)
377
279k
  auto makeValueCompare = [&](Case const& _case) {
378
279k
    yul::FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(yul::FunctionCall{
379
279k
      debugDataOf(_case),
380
279k
      BuiltinName{{}, *equalityBuiltinHandle},
381
279k
      {*_case.value, Identifier{{}, ghostVariableName}}
382
279k
    });
383
279k
    BuiltinFunction const& equalityBuiltin = m_dialect.builtin(*equalityBuiltinHandle);
384
279k
    CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{
385
279k
      Stack{ghostVarSlot, LiteralSlot{_case.value->value.value(), debugDataOf(*_case.value)}},
386
279k
      Stack{TemporarySlot{ghostCall, 0}},
387
279k
      CFG::BuiltinCall{debugDataOf(_case), equalityBuiltin, ghostCall, 2},
388
279k
    });
389
279k
    return operation.output.front();
390
279k
  };
391
103k
  CFG::BasicBlock& afterSwitch = m_graph.makeBlock(preSwitchDebugData);
392
103k
  yulAssert(!_switch.cases.empty(), "");
393
103k
  for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1))
394
255k
  {
395
255k
    yulAssert(switchCase.value, "");
396
255k
    auto& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
397
255k
    auto& elseBranch = m_graph.makeBlock(debugDataOf(_switch));
398
255k
    makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch);
399
255k
    m_currentBlock = &caseBranch;
400
255k
    (*this)(switchCase.body);
401
255k
    jump(debugDataOf(switchCase.body), afterSwitch);
402
255k
    m_currentBlock = &elseBranch;
403
255k
  }
404
103k
  Case const& switchCase = _switch.cases.back();
405
103k
  if (switchCase.value)
406
23.2k
  {
407
23.2k
    CFG::BasicBlock& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
408
23.2k
    makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch);
409
23.2k
    m_currentBlock = &caseBranch;
410
23.2k
  }
411
103k
  (*this)(switchCase.body);
412
103k
  jump(debugDataOf(switchCase.body), afterSwitch);
413
103k
}
414
415
void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
416
460k
{
417
460k
  langutil::DebugData::ConstPtr preLoopDebugData = debugDataOf(_loop);
418
460k
  ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get());
419
460k
  (*this)(_loop.pre);
420
421
460k
  std::optional<bool> constantCondition;
422
460k
  if (auto const* literalCondition = std::get_if<yul::Literal>(_loop.condition.get()))
423
103k
    constantCondition = literalCondition->value.value() != 0;
424
425
460k
  CFG::BasicBlock& loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition));
426
460k
  CFG::BasicBlock& loopBody = m_graph.makeBlock(debugDataOf(_loop.body));
427
460k
  CFG::BasicBlock& post = m_graph.makeBlock(debugDataOf(_loop.post));
428
460k
  CFG::BasicBlock& afterLoop = m_graph.makeBlock(preLoopDebugData);
429
430
460k
  ScopedSaveAndRestore scopedSaveAndRestore(m_forLoopInfo, ForLoopInfo{afterLoop, post});
431
432
460k
  if (constantCondition.has_value())
433
103k
  {
434
103k
    if (*constantCondition)
435
102k
    {
436
102k
      jump(debugDataOf(_loop.pre), loopBody);
437
102k
      (*this)(_loop.body);
438
102k
      jump(debugDataOf(_loop.body), post);
439
102k
      (*this)(_loop.post);
440
102k
      jump(debugDataOf(_loop.post), loopBody, true);
441
102k
    }
442
624
    else
443
624
      jump(debugDataOf(_loop.pre), afterLoop);
444
103k
  }
445
357k
  else
446
357k
  {
447
357k
    jump(debugDataOf(_loop.pre), loopCondition);
448
357k
    StackSlot condition = std::visit(*this, *_loop.condition);
449
357k
    makeConditionalJump(debugDataOf(*_loop.condition), std::move(condition), loopBody, afterLoop);
450
357k
    m_currentBlock = &loopBody;
451
357k
    (*this)(_loop.body);
452
357k
    jump(debugDataOf(_loop.body), post);
453
357k
    (*this)(_loop.post);
454
357k
    jump(debugDataOf(_loop.post), loopCondition, true);
455
357k
  }
456
457
460k
  m_currentBlock = &afterLoop;
458
460k
}
459
460
void ControlFlowGraphBuilder::operator()(Break const& _break)
461
26.5k
{
462
26.5k
  yulAssert(m_forLoopInfo.has_value(), "");
463
26.5k
  jump(debugDataOf(_break), m_forLoopInfo->afterLoop);
464
26.5k
  m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
465
26.5k
}
466
467
void ControlFlowGraphBuilder::operator()(Continue const& _continue)
468
29.8k
{
469
29.8k
  yulAssert(m_forLoopInfo.has_value(), "");
470
29.8k
  jump(debugDataOf(_continue), m_forLoopInfo->post);
471
29.8k
  m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
472
29.8k
}
473
474
// '_leave' and '__leave' are reserved in VisualStudio
475
void ControlFlowGraphBuilder::operator()(Leave const& leave_)
476
58.3k
{
477
58.3k
  yulAssert(m_currentFunction.has_value(), "");
478
58.3k
  m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(leave_), *m_currentFunction};
479
58.3k
  (*m_currentFunction)->exits.emplace_back(m_currentBlock);
480
58.3k
  m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
481
58.3k
}
482
483
void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
484
542k
{
485
542k
  yulAssert(m_scope, "");
486
542k
  yulAssert(m_scope->identifiers.count(_function.name), "");
487
542k
  Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
488
542k
  m_graph.functions.emplace_back(&function);
489
490
542k
  CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function);
491
492
542k
  ControlFlowGraphBuilder builder{m_graph, m_info, m_functionSideEffects, m_dialect};
493
542k
  builder.m_currentFunction = &functionInfo;
494
542k
  builder.m_currentBlock = functionInfo.entry;
495
542k
  builder(_function.body);
496
542k
  functionInfo.exits.emplace_back(builder.m_currentBlock);
497
542k
  builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
498
542k
}
499
500
void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _functionDefinition)
501
542k
{
502
542k
  yulAssert(m_scope, "");
503
542k
  yulAssert(m_scope->identifiers.count(_functionDefinition.name), "");
504
542k
  Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name));
505
506
542k
  yulAssert(m_info.scopes.at(&_functionDefinition.body), "");
507
542k
  Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_functionDefinition).get()).get();
508
542k
  yulAssert(virtualFunctionScope, "");
509
510
542k
  bool inserted = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{
511
542k
    _functionDefinition.debugData,
512
542k
    function,
513
542k
    _functionDefinition,
514
542k
    &m_graph.makeBlock(debugDataOf(_functionDefinition.body)),
515
542k
    _functionDefinition.parameters | ranges::views::transform([&](auto const& _param) {
516
481k
      return VariableSlot{
517
481k
        std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)),
518
481k
        _param.debugData
519
481k
      };
520
481k
    }) | ranges::to<std::vector>,
521
542k
    _functionDefinition.returnVariables | ranges::views::transform([&](auto const& _retVar) {
522
425k
      return VariableSlot{
523
425k
        std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)),
524
425k
        _retVar.debugData
525
425k
      };
526
425k
    }) | ranges::to<std::vector>,
527
542k
    {},
528
542k
    m_functionSideEffects.at(&_functionDefinition).canContinue
529
542k
  })).second;
530
542k
  yulAssert(inserted);
531
542k
}
532
533
Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call)
534
8.12M
{
535
8.12M
  yulAssert(m_scope, "");
536
8.12M
  yulAssert(m_currentBlock, "");
537
538
8.12M
  Stack const* output = nullptr;
539
8.12M
  bool canContinue = true;
540
8.12M
  if (BuiltinFunction const* builtin = resolveBuiltinFunction(_call.functionName, m_dialect))
541
7.04M
  {
542
7.04M
    Stack inputs;
543
7.04M
    for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
544
13.2M
      if (!builtin->literalArgument(idx).has_value())
545
13.0M
        inputs.emplace_back(std::visit(*this, arg));
546
7.04M
    CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()};
547
7.04M
    output = &m_currentBlock->operations.emplace_back(CFG::Operation{
548
      // input
549
7.04M
      std::move(inputs),
550
      // output
551
7.04M
      ranges::views::iota(0u, builtin->numReturns) | ranges::views::transform([&](size_t _i) {
552
4.77M
        return TemporarySlot{_call, _i};
553
4.77M
      }) | ranges::to<Stack>,
554
      // operation
555
7.04M
      std::move(builtinCall)
556
7.04M
    }).output;
557
7.04M
    canContinue = builtin->controlFlowSideEffects.canContinue;
558
7.04M
  }
559
1.08M
  else
560
1.08M
  {
561
1.08M
    yulAssert(std::holds_alternative<Identifier>(_call.functionName));
562
1.08M
    Scope::Function const& function = lookupFunction(std::get<Identifier>(_call.functionName).name);
563
1.08M
    canContinue = m_graph.functionInfo.at(&function).canContinue;
564
1.08M
    Stack inputs;
565
1.08M
    if (canContinue)
566
992k
      inputs.emplace_back(FunctionCallReturnLabelSlot{_call});
567
1.08M
    for (auto const& arg: _call.arguments | ranges::views::reverse)
568
939k
      inputs.emplace_back(std::visit(*this, arg));
569
1.08M
    output = &m_currentBlock->operations.emplace_back(CFG::Operation{
570
      // input
571
1.08M
      std::move(inputs),
572
      // output
573
1.08M
      ranges::views::iota(0u, function.numReturns) | ranges::views::transform([&](size_t _i) {
574
683k
        return TemporarySlot{_call, _i};
575
683k
      }) | ranges::to<Stack>,
576
      // operation
577
1.08M
      CFG::FunctionCall{_call.debugData, function, _call, /* recursive */ false, canContinue}
578
1.08M
    }).output;
579
1.08M
  }
580
8.12M
  if (!canContinue)
581
274k
  {
582
274k
    m_currentBlock->exit = CFG::BasicBlock::Terminated{};
583
274k
    m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
584
274k
  }
585
8.12M
  return *output;
586
8.12M
}
587
588
Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount)
589
2.32M
{
590
2.32M
  return std::visit(util::GenericVisitor{
591
2.32M
    [&](FunctionCall const& _call) -> Stack {
592
1.36M
      Stack const& output = visitFunctionCall(_call);
593
1.36M
      yulAssert(_expectedSlotCount == output.size(), "");
594
1.36M
      return output;
595
1.36M
    },
596
2.32M
    [&](auto const& _identifierOrLiteral) -> Stack {
597
965k
      yulAssert(_expectedSlotCount == 1, "");
598
965k
      return {(*this)(_identifierOrLiteral)};
599
965k
    }
ControlFlowGraphBuilder.cpp:std::__1::vector<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot>, std::__1::allocator<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> > > solidity::yul::ControlFlowGraphBuilder::visitAssignmentRightHandSide(std::__1::variant<solidity::yul::FunctionCall, solidity::yul::Identifier, solidity::yul::Literal> const&, unsigned long)::$_1::operator()<solidity::yul::Identifier>(solidity::yul::Identifier const&) const
Line
Count
Source
596
216k
    [&](auto const& _identifierOrLiteral) -> Stack {
597
      yulAssert(_expectedSlotCount == 1, "");
598
216k
      return {(*this)(_identifierOrLiteral)};
599
216k
    }
ControlFlowGraphBuilder.cpp:std::__1::vector<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot>, std::__1::allocator<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> > > solidity::yul::ControlFlowGraphBuilder::visitAssignmentRightHandSide(std::__1::variant<solidity::yul::FunctionCall, solidity::yul::Identifier, solidity::yul::Literal> const&, unsigned long)::$_1::operator()<solidity::yul::Literal>(solidity::yul::Literal const&) const
Line
Count
Source
596
749k
    [&](auto const& _identifierOrLiteral) -> Stack {
597
      yulAssert(_expectedSlotCount == 1, "");
598
749k
      return {(*this)(_identifierOrLiteral)};
599
749k
    }
600
2.32M
  }, _expression);
601
2.32M
}
602
603
Scope::Function const& ControlFlowGraphBuilder::lookupFunction(YulName _name) const
604
1.08M
{
605
1.08M
  Scope::Function const* function = nullptr;
606
1.08M
  yulAssert(m_scope->lookup(_name, util::GenericVisitor{
607
1.08M
    [](Scope::Variable&) { yulAssert(false, "Expected function name."); },
608
1.08M
    [&](Scope::Function& _function) { function = &_function; }
609
1.08M
  }), "Function name not found.");
610
1.08M
  yulAssert(function, "");
611
1.08M
  return *function;
612
1.08M
}
613
614
Scope::Variable const& ControlFlowGraphBuilder::lookupVariable(YulName _name) const
615
5.37M
{
616
5.37M
  yulAssert(m_scope, "");
617
5.37M
  Scope::Variable const* var = nullptr;
618
5.37M
  if (m_scope->lookup(_name, util::GenericVisitor{
619
5.37M
    [&](Scope::Variable& _var) { var = &_var; },
620
5.37M
    [](Scope::Function&)
621
5.37M
    {
622
0
      yulAssert(false, "Function not removed during desugaring.");
623
0
    }
624
5.37M
  }))
625
5.37M
  {
626
5.37M
    yulAssert(var, "");
627
5.37M
    return *var;
628
5.37M
  };
629
0
  yulAssert(false, "External identifier access unimplemented.");
630
0
}
631
632
void ControlFlowGraphBuilder::makeConditionalJump(
633
  langutil::DebugData::ConstPtr _debugData,
634
  StackSlot _condition,
635
  CFG::BasicBlock& _nonZero,
636
  CFG::BasicBlock& _zero
637
)
638
886k
{
639
886k
  yulAssert(m_currentBlock, "");
640
886k
  m_currentBlock->exit = CFG::BasicBlock::ConditionalJump{
641
886k
    std::move(_debugData),
642
886k
    std::move(_condition),
643
886k
    &_nonZero,
644
886k
    &_zero
645
886k
  };
646
886k
  _nonZero.entries.emplace_back(m_currentBlock);
647
886k
  _zero.entries.emplace_back(m_currentBlock);
648
886k
  m_currentBlock = nullptr;
649
886k
}
650
651
void ControlFlowGraphBuilder::jump(
652
  langutil::DebugData::ConstPtr _debugData,
653
  CFG::BasicBlock& _target,
654
  bool backwards
655
)
656
2.04M
{
657
  yulAssert(m_currentBlock, "");
658
2.04M
  m_currentBlock->exit = CFG::BasicBlock::Jump{std::move(_debugData), &_target, backwards};
659
2.04M
  _target.entries.emplace_back(m_currentBlock);
660
2.04M
  m_currentBlock = &_target;
661
2.04M
}