Coverage Report

Created: 2025-09-08 08:10

/src/solidity/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp
Line
Count
Source (jump to first uncovered line)
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/SSAControlFlowGraphBuilder.h>
23
24
#include <libyul/backends/evm/ControlFlow.h>
25
#include <libyul/AST.h>
26
#include <libyul/ControlFlowSideEffectsCollector.h>
27
#include <libyul/Exceptions.h>
28
#include <libyul/Utilities.h>
29
30
#include <libsolutil/Algorithms.h>
31
#include <libsolutil/StringUtils.h>
32
#include <libsolutil/Visitor.h>
33
34
#include <range/v3/algorithm/replace.hpp>
35
#include <range/v3/range/conversion.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/reverse.hpp>
40
#include <range/v3/view/transform.hpp>
41
#include <range/v3/view/zip.hpp>
42
43
using namespace solidity;
44
using namespace solidity::yul;
45
46
namespace solidity::yul
47
{
48
49
SSAControlFlowGraphBuilder::SSAControlFlowGraphBuilder(
50
  ControlFlow& _controlFlow,
51
  SSACFG& _graph,
52
  AsmAnalysisInfo const& _analysisInfo,
53
  ControlFlowSideEffectsCollector const& _sideEffects,
54
  Dialect const& _dialect,
55
  bool _keepLiteralAssignments
56
):
57
  m_controlFlow(_controlFlow),
58
  m_graph(_graph),
59
  m_info(_analysisInfo),
60
  m_sideEffects(_sideEffects),
61
  m_dialect(_dialect),
62
  m_keepLiteralAssignments(_keepLiteralAssignments)
63
0
{
64
0
}
65
66
std::unique_ptr<ControlFlow> SSAControlFlowGraphBuilder::build(
67
  AsmAnalysisInfo const& _analysisInfo,
68
  Dialect const& _dialect,
69
  Block const& _block,
70
  bool _keepLiteralAssignments
71
)
72
0
{
73
0
  ControlFlowSideEffectsCollector sideEffects(_dialect, _block);
74
75
0
  auto controlFlow = std::make_unique<ControlFlow>();
76
0
  SSAControlFlowGraphBuilder builder(*controlFlow, *controlFlow->mainGraph, _analysisInfo, sideEffects, _dialect, _keepLiteralAssignments);
77
0
  builder.m_currentBlock = controlFlow->mainGraph->makeBlock(debugDataOf(_block));
78
0
  builder.sealBlock(builder.m_currentBlock);
79
0
  builder(_block);
80
0
  if (!builder.blockInfo(builder.m_currentBlock).sealed)
81
0
    builder.sealBlock(builder.m_currentBlock);
82
0
  controlFlow->mainGraph->block(builder.m_currentBlock).exit = SSACFG::BasicBlock::MainExit{};
83
0
  builder.cleanUnreachable();
84
0
  return controlFlow;
85
0
}
86
87
SSACFG::ValueId SSAControlFlowGraphBuilder::tryRemoveTrivialPhi(SSACFG::ValueId _phi)
88
0
{
89
  // TODO: double-check if this is sane
90
0
  auto const* phiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(_phi));
91
0
  yulAssert(phiInfo);
92
0
  yulAssert(blockInfo(phiInfo->block).sealed);
93
94
0
  SSACFG::ValueId same;
95
0
  for (SSACFG::ValueId arg: phiInfo->arguments)
96
0
  {
97
0
    if (arg == same || arg == _phi)
98
0
      continue;  // unique value or self-reference
99
0
    if (same.hasValue())
100
0
      return _phi;  // phi merges at least two distinct values -> not trivial
101
0
    same = arg;
102
0
  }
103
0
  if (!same.hasValue())
104
0
  {
105
    // This will happen for unreachable paths.
106
    // TODO: check how best to deal with this
107
0
    same = m_graph.unreachableValue();
108
0
  }
109
110
0
  m_graph.block(phiInfo->block).phis.erase(_phi);
111
112
0
  std::vector<SSACFG::ValueId> phiUses;
113
0
  for (size_t blockIdValue = 0; blockIdValue < m_graph.numBlocks(); ++blockIdValue)
114
0
  {
115
0
    auto& block = m_graph.block(SSACFG::BlockId{blockIdValue});
116
0
    for (auto blockPhi: block.phis)
117
0
    {
118
0
      yulAssert(blockPhi != _phi, "Phis should be defined in exactly one block, _phi was erased.");
119
0
      auto* blockPhiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(blockPhi));
120
0
      yulAssert(blockPhiInfo);
121
0
      bool usedInPhi = false;
122
0
      for (auto& arg: blockPhiInfo->arguments)
123
0
        if (arg == _phi)
124
0
        {
125
0
          arg = same;
126
0
          usedInPhi = true;
127
0
        }
128
0
      if (usedInPhi)
129
0
        phiUses.push_back(blockPhi);
130
0
    }
131
0
    for (auto& op: block.operations)
132
0
      ranges::replace(op.inputs, _phi, same);
133
0
    std::visit(util::GenericVisitor{
134
0
      [_phi, same](SSACFG::BasicBlock::FunctionReturn& _functionReturn) {
135
0
        ranges::replace(_functionReturn.returnValues,_phi, same);
136
0
      },
137
0
      [_phi, same](SSACFG::BasicBlock::ConditionalJump& _condJump) {
138
0
        if (_condJump.condition == _phi)
139
0
          _condJump.condition = same;
140
0
      },
141
0
      [_phi, same](SSACFG::BasicBlock::JumpTable& _jumpTable) {
142
0
        if (_jumpTable.value == _phi)
143
0
          _jumpTable.value = same;
144
0
      },
145
0
      [](SSACFG::BasicBlock::Jump&) {},
146
0
      [](SSACFG::BasicBlock::MainExit&) {},
147
0
      [](SSACFG::BasicBlock::Terminated&) {}
148
0
    }, block.exit);
149
0
  }
150
0
  for (auto& currentVariableDefs: m_currentDef | ranges::views::values)
151
0
    ranges::replace(currentVariableDefs, _phi, same);
152
153
0
  for (auto phiUse: phiUses)
154
0
    tryRemoveTrivialPhi(phiUse);
155
156
0
  return same;
157
0
}
158
159
/// Removes edges to blocks that are not reachable.
160
void SSAControlFlowGraphBuilder::cleanUnreachable()
161
0
{
162
  // Determine which blocks are reachable from the entry.
163
0
  util::BreadthFirstSearch<SSACFG::BlockId> reachabilityCheck{{m_graph.entry}};
164
0
  reachabilityCheck.run([&](SSACFG::BlockId _blockId, auto&& _addChild) {
165
0
    auto const& block = m_graph.block(_blockId);
166
0
    visit(util::GenericVisitor{
167
0
        [&](SSACFG::BasicBlock::Jump const& _jump) {
168
0
          _addChild(_jump.target);
169
0
        },
170
0
        [&](SSACFG::BasicBlock::ConditionalJump const& _jump) {
171
0
          _addChild(_jump.zero);
172
0
          _addChild(_jump.nonZero);
173
0
        },
174
0
        [](SSACFG::BasicBlock::JumpTable const&) { yulAssert(false); },
175
0
        [](SSACFG::BasicBlock::FunctionReturn const&) {},
176
0
        [](SSACFG::BasicBlock::Terminated const&) {},
177
0
        [](SSACFG::BasicBlock::MainExit const&) {}
178
0
      }, block.exit);
179
0
  });
180
181
  // Remove all entries from unreachable nodes from the graph.
182
0
  for (SSACFG::BlockId blockId: reachabilityCheck.visited)
183
0
  {
184
0
    auto& block = m_graph.block(blockId);
185
186
0
    std::vector<SSACFG::ValueId> maybeTrivialPhi;
187
0
    std::erase_if(block.entries, [&](auto const& entry) { return !reachabilityCheck.visited.contains(entry); });
188
0
    for (auto phi: block.phis)
189
0
      if (auto* phiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(phi)))
190
0
      {
191
0
        auto erasedCount = std::erase_if(phiInfo->arguments, [&](SSACFG::ValueId _arg) {
192
0
          return std::holds_alternative<SSACFG::UnreachableValue>(m_graph.valueInfo(_arg));
193
0
        });
194
0
        if (erasedCount > 0)
195
0
          maybeTrivialPhi.push_back(phi);
196
0
      }
197
198
    // After removing a phi argument, we might end up with a trivial phi that can be removed.
199
0
    for (auto phi: maybeTrivialPhi)
200
0
      tryRemoveTrivialPhi(phi);
201
0
  }
202
0
}
203
204
void SSAControlFlowGraphBuilder::buildFunctionGraph(
205
  Scope::Function const* _function,
206
  FunctionDefinition const* _functionDefinition
207
)
208
0
{
209
0
  m_controlFlow.functionGraphs.emplace_back(std::make_unique<SSACFG>());
210
0
  auto& cfg = *m_controlFlow.functionGraphs.back();
211
0
  m_controlFlow.functionGraphMapping.emplace_back(_function, &cfg);
212
213
0
  yulAssert(m_info.scopes.at(&_functionDefinition->body), "");
214
0
  Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(_functionDefinition).get()).get();
215
0
  yulAssert(virtualFunctionScope, "");
216
217
0
  cfg.entry = cfg.makeBlock(debugDataOf(_functionDefinition->body));
218
0
  auto arguments = _functionDefinition->parameters | ranges::views::transform([&](auto const& _param) {
219
0
    auto const& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name));
220
    // Note: cannot use std::make_tuple since it unwraps reference wrappers.
221
0
    return std::tuple{std::cref(var), cfg.newVariable(cfg.entry)};
222
0
  }) | ranges::to<std::vector>;
223
0
  auto returns = _functionDefinition->returnVariables | ranges::views::transform([&](auto const& _param) {
224
0
    return std::cref(std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)));
225
0
  }) | ranges::to<std::vector>;
226
227
0
  cfg.debugData = _functionDefinition->debugData;
228
0
  cfg.function = _function;
229
0
  cfg.canContinue = m_sideEffects.functionSideEffects().at(_functionDefinition).canContinue;
230
0
  cfg.arguments = arguments;
231
0
  cfg.returns = returns;
232
233
0
  SSAControlFlowGraphBuilder builder(m_controlFlow, cfg, m_info, m_sideEffects, m_dialect, m_keepLiteralAssignments);
234
0
  builder.m_currentBlock = cfg.entry;
235
0
  builder.m_functionDefinitions = m_functionDefinitions;
236
0
  for (auto&& [var, varId]: cfg.arguments)
237
0
    builder.currentDef(var, cfg.entry) = varId;
238
0
  for (auto const& var: cfg.returns)
239
0
    builder.currentDef(var.get(), cfg.entry) = builder.zero();
240
0
  builder.sealBlock(cfg.entry);
241
0
  builder(_functionDefinition->body);
242
0
  cfg.exits.insert(builder.m_currentBlock);
243
  // Artificial explicit function exit (`leave`) at the end of the body.
244
0
  builder(Leave{debugDataOf(*_functionDefinition)});
245
0
  builder.cleanUnreachable();
246
0
}
247
248
void SSAControlFlowGraphBuilder::operator()(ExpressionStatement const& _expressionStatement)
249
0
{
250
0
  auto const* functionCall = std::get_if<FunctionCall>(&_expressionStatement.expression);
251
0
  yulAssert(functionCall);
252
0
  auto results = visitFunctionCall(*functionCall);
253
0
  yulAssert(results.empty());
254
0
}
255
256
void SSAControlFlowGraphBuilder::operator()(Assignment const& _assignment)
257
0
{
258
0
  assign(
259
0
    _assignment.variableNames | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>,
260
0
    _assignment.value.get()
261
0
  );
262
0
}
263
264
void SSAControlFlowGraphBuilder::operator()(VariableDeclaration const& _variableDeclaration)
265
0
{
266
0
  assign(
267
0
    _variableDeclaration.variables | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>,
268
0
    _variableDeclaration.value.get()
269
0
  );
270
0
}
271
272
void SSAControlFlowGraphBuilder::operator()(FunctionDefinition const& _functionDefinition)
273
0
{
274
0
  Scope::Function const& function = lookupFunction(_functionDefinition.name);
275
0
  buildFunctionGraph(&function, &_functionDefinition);
276
0
}
277
278
void SSAControlFlowGraphBuilder::operator()(If const& _if)
279
0
{
280
0
  std::optional<bool> constantCondition;
281
0
  if (auto const* literalCondition = std::get_if<Literal>(_if.condition.get()))
282
0
    constantCondition = literalCondition->value.value() != 0;
283
  // deal with literal (constant) conditions explicitly
284
0
  if (constantCondition)
285
0
  {
286
0
    if (*constantCondition)
287
      // Always true - skip conditional, just execute if branch
288
0
      (*this)(_if.body);
289
0
  }
290
0
  else
291
0
  {
292
0
    auto condition = std::visit(*this, *_if.condition);
293
0
    auto ifBranch = m_graph.makeBlock(debugDataOf(_if.body));
294
0
    auto afterIf = m_graph.makeBlock(debugDataOf(currentBlock()));
295
0
    conditionalJump(
296
0
      debugDataOf(_if),
297
0
      condition,
298
0
      ifBranch,
299
0
      afterIf
300
0
    );
301
0
    sealBlock(ifBranch);
302
0
    m_currentBlock = ifBranch;
303
0
    (*this)(_if.body);
304
0
    jump(debugDataOf(_if.body), afterIf);
305
0
    sealBlock(afterIf);
306
0
  }
307
0
}
308
309
void SSAControlFlowGraphBuilder::operator()(Switch const& _switch)
310
0
{
311
0
  auto expression = std::visit(*this, *_switch.expression);
312
313
0
  auto useJumpTableForSwitch = [](Switch const&) {
314
    // TODO: check for EOF support & tight switch values.
315
0
    return false;
316
0
  };
317
0
  if (useJumpTableForSwitch(_switch))
318
0
  {
319
    // TODO: also generate a subtraction to shift tight, but non-zero switch cases - or, alternative,
320
    // transform to zero-based tight switches on Yul if possible.
321
0
    std::map<u256, SSACFG::BlockId> cases;
322
0
    std::optional<SSACFG::BlockId> defaultCase;
323
0
    std::vector<std::tuple<SSACFG::BlockId, std::reference_wrapper<Block const>>> children;
324
0
    for (auto const& _case: _switch.cases)
325
0
    {
326
0
      auto blockId = m_graph.makeBlock(debugDataOf(_case.body));
327
0
      if (_case.value)
328
0
        cases[_case.value->value.value()] = blockId;
329
0
      else
330
0
        defaultCase = blockId;
331
0
      children.emplace_back(blockId, std::ref(_case.body));
332
0
    }
333
0
    auto afterSwitch = m_graph.makeBlock(debugDataOf(currentBlock()));
334
335
0
    tableJump(debugDataOf(_switch), expression, cases, defaultCase ? *defaultCase : afterSwitch);
336
0
    for (auto [blockId, block]: children)
337
0
    {
338
0
      sealBlock(blockId);
339
0
      m_currentBlock = blockId;
340
0
      (*this)(block);
341
0
      jump(debugDataOf(currentBlock()), afterSwitch);
342
0
    }
343
0
    sealBlock(afterSwitch);
344
0
    m_currentBlock = afterSwitch;
345
0
  }
346
0
  else
347
0
  {
348
0
    if (auto const* constantExpression = std::get_if<Literal>(_switch.expression.get()))
349
0
    {
350
0
      Case const* matchedCase = nullptr;
351
      // select case that matches (or default if available)
352
0
      for (auto const& switchCase: _switch.cases)
353
0
      {
354
0
        if (!switchCase.value)
355
0
          matchedCase = &switchCase;
356
0
        if (switchCase.value && switchCase.value->value.value() == constantExpression->value.value())
357
0
        {
358
0
          matchedCase = &switchCase;
359
0
          break;
360
0
        }
361
0
      }
362
0
      if (matchedCase)
363
0
      {
364
        // inject directly into the current block
365
0
        (*this)(matchedCase->body);
366
0
      }
367
0
      return;
368
0
    }
369
370
0
    std::optional<BuiltinHandle> equalityBuiltinHandle = m_dialect.equalityFunctionHandle();
371
0
    yulAssert(equalityBuiltinHandle);
372
373
0
    auto makeValueCompare = [&](Case const& _case) {
374
0
      FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(FunctionCall{
375
0
        debugDataOf(_case),
376
0
        BuiltinName{{}, *equalityBuiltinHandle},
377
0
        {*_case.value /* skip second argument */ }
378
0
      });
379
0
      auto outputValue = m_graph.newVariable(m_currentBlock);
380
0
      currentBlock().operations.emplace_back(SSACFG::Operation{
381
0
        {outputValue},
382
0
        SSACFG::BuiltinCall{
383
0
          debugDataOf(_case),
384
0
          m_dialect.builtin(*equalityBuiltinHandle),
385
0
          ghostCall
386
0
        },
387
0
        {m_graph.newLiteral(debugDataOf(_case), _case.value->value.value()), expression}
388
0
      });
389
0
      return outputValue;
390
0
    };
391
392
0
    auto afterSwitch = m_graph.makeBlock(debugDataOf(currentBlock()));
393
0
    yulAssert(!_switch.cases.empty(), "");
394
0
    for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1))
395
0
    {
396
0
      yulAssert(switchCase.value, "");
397
0
      auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
398
0
      auto elseBranch = m_graph.makeBlock(debugDataOf(_switch));
399
400
0
      conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch);
401
0
      sealBlock(caseBranch);
402
0
      sealBlock(elseBranch);
403
0
      m_currentBlock = caseBranch;
404
0
      (*this)(switchCase.body);
405
0
      jump(debugDataOf(switchCase.body), afterSwitch);
406
0
      m_currentBlock = elseBranch;
407
0
    }
408
0
    Case const& switchCase = _switch.cases.back();
409
0
    if (switchCase.value)
410
0
    {
411
0
      auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
412
0
      conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch);
413
0
      sealBlock(caseBranch);
414
0
      m_currentBlock = caseBranch;
415
0
    }
416
0
    (*this)(switchCase.body);
417
0
    jump(debugDataOf(switchCase.body), afterSwitch);
418
0
    sealBlock(afterSwitch);
419
0
  }
420
0
}
421
void SSAControlFlowGraphBuilder::operator()(ForLoop const& _loop)
422
0
{
423
0
  ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get());
424
0
  (*this)(_loop.pre);
425
0
  auto preLoopDebugData = debugDataOf(currentBlock());
426
427
0
  std::optional<bool> constantCondition;
428
0
  if (auto const* literalCondition = std::get_if<Literal>(_loop.condition.get()))
429
0
    constantCondition = literalCondition->value.value() != 0;
430
431
0
  SSACFG::BlockId loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition));
432
0
  SSACFG::BlockId loopBody = m_graph.makeBlock(debugDataOf(_loop.body));
433
0
  SSACFG::BlockId post = m_graph.makeBlock(debugDataOf(_loop.post));
434
0
  SSACFG::BlockId afterLoop = m_graph.makeBlock(preLoopDebugData);
435
436
0
  class ForLoopInfoScope {
437
0
  public:
438
0
    ForLoopInfoScope(std::stack<ForLoopInfo>& _info, SSACFG::BlockId _breakBlock, SSACFG::BlockId _continueBlock): m_info(_info)
439
0
    {
440
0
      m_info.push(ForLoopInfo{_breakBlock, _continueBlock});
441
0
    }
442
0
    ~ForLoopInfoScope() {
443
0
      m_info.pop();
444
0
    }
445
0
  private:
446
0
    std::stack<ForLoopInfo>& m_info;
447
0
  } forLoopInfoScope(m_forLoopInfo, afterLoop, post);
448
449
0
  if (constantCondition.has_value())
450
0
  {
451
0
    std::visit(*this, *_loop.condition);
452
0
    if (*constantCondition)
453
0
    {
454
0
      jump(debugDataOf(*_loop.condition), loopBody);
455
0
      (*this)(_loop.body);
456
0
      jump(debugDataOf(_loop.body), post);
457
0
      sealBlock(post);
458
0
      (*this)(_loop.post);
459
0
      jump(debugDataOf(_loop.post), loopBody);
460
0
      sealBlock(loopBody);
461
0
    }
462
0
    else
463
0
      jump(debugDataOf(*_loop.condition), afterLoop);
464
0
  }
465
0
  else
466
0
  {
467
0
    jump(debugDataOf(_loop.pre), loopCondition);
468
0
    auto condition = std::visit(*this, *_loop.condition);
469
0
    conditionalJump(debugDataOf(*_loop.condition), condition, loopBody, afterLoop);
470
0
    sealBlock(loopBody);
471
0
    m_currentBlock = loopBody;
472
0
    (*this)(_loop.body);
473
0
    jump(debugDataOf(_loop.body), post);
474
0
    sealBlock(post);
475
0
    (*this)(_loop.post);
476
0
    jump(debugDataOf(_loop.post), loopCondition);
477
0
    sealBlock(loopCondition);
478
0
  }
479
480
0
  sealBlock(afterLoop);
481
0
  m_currentBlock = afterLoop;
482
0
}
483
484
void SSAControlFlowGraphBuilder::operator()(Break const& _break)
485
0
{
486
0
  yulAssert(!m_forLoopInfo.empty());
487
0
  auto currentBlockDebugData = debugDataOf(currentBlock());
488
0
  jump(debugDataOf(_break), m_forLoopInfo.top().breakBlock);
489
0
  m_currentBlock = m_graph.makeBlock(currentBlockDebugData);
490
0
  sealBlock(m_currentBlock);
491
0
}
492
493
void SSAControlFlowGraphBuilder::operator()(Continue const& _continue)
494
0
{
495
0
  yulAssert(!m_forLoopInfo.empty());
496
0
  auto currentBlockDebugData = debugDataOf(currentBlock());
497
0
  jump(debugDataOf(_continue), m_forLoopInfo.top().continueBlock);
498
0
  m_currentBlock = m_graph.makeBlock(currentBlockDebugData);
499
0
  sealBlock(m_currentBlock);
500
0
}
501
502
void SSAControlFlowGraphBuilder::operator()(Leave const& _leaveStatement)
503
0
{
504
0
  auto currentBlockDebugData = debugDataOf(currentBlock());
505
0
  currentBlock().exit = SSACFG::BasicBlock::FunctionReturn{
506
0
    debugDataOf(_leaveStatement),
507
0
    m_graph.returns | ranges::views::transform([&](auto _var) {
508
0
      return readVariable(_var, m_currentBlock);
509
0
    }) | ranges::to<std::vector>
510
0
  };
511
0
  m_currentBlock = m_graph.makeBlock(currentBlockDebugData);
512
0
  sealBlock(m_currentBlock);
513
0
}
514
515
void SSAControlFlowGraphBuilder::registerFunctionDefinition(FunctionDefinition const& _functionDefinition)
516
0
{
517
0
  yulAssert(m_scope, "");
518
0
  yulAssert(m_scope->identifiers.count(_functionDefinition.name), "");
519
0
  auto& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name));
520
0
  m_graph.functions.emplace_back(function);
521
0
  m_functionDefinitions.emplace_back(&function, &_functionDefinition);
522
0
}
523
524
void SSAControlFlowGraphBuilder::operator()(Block const& _block)
525
0
{
526
0
  ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get());
527
  // gather all function definitions so that they are visible to each other's subgraphs
528
0
  static constexpr auto functionDefinitionFilter = ranges::views::filter(
529
0
    [](auto const& _statement) { return std::holds_alternative<FunctionDefinition>(_statement); }
530
0
  );
531
0
  for (auto const& statement: _block.statements | functionDefinitionFilter)
532
0
    registerFunctionDefinition(std::get<FunctionDefinition>(statement));
533
  // now visit the rest
534
0
  for (auto const& statement: _block.statements)
535
0
    std::visit(*this, statement);
536
0
}
537
538
SSACFG::ValueId SSAControlFlowGraphBuilder::operator()(FunctionCall const& _call)
539
0
{
540
0
  auto results = visitFunctionCall(_call);
541
0
  yulAssert(results.size() == 1);
542
0
  return results.front();
543
0
}
544
545
SSACFG::ValueId SSAControlFlowGraphBuilder::operator()(Identifier const& _identifier)
546
0
{
547
0
  auto const& var = lookupVariable(_identifier.name);
548
0
  return readVariable(var, m_currentBlock);
549
0
}
550
551
SSACFG::ValueId SSAControlFlowGraphBuilder::operator()(Literal const& _literal)
552
0
{
553
0
  return m_graph.newLiteral(debugDataOf(currentBlock()), _literal.value.value());
554
0
}
555
556
void SSAControlFlowGraphBuilder::assign(std::vector<std::reference_wrapper<Scope::Variable const>> _variables, Expression const* _expression)
557
0
{
558
0
  auto rhs = [&]() -> std::vector<SSACFG::ValueId> {
559
0
    if (auto const* functionCall = std::get_if<FunctionCall>(_expression))
560
0
      return visitFunctionCall(*functionCall);
561
0
    if (_expression)
562
0
      return {std::visit(*this, *_expression)};
563
0
    return {_variables.size(), zero()};
564
0
  }();
565
0
  yulAssert(rhs.size() == _variables.size());
566
567
0
  for (auto const& [var, value]: ranges::zip_view(_variables, rhs))
568
0
  {
569
0
    if (m_keepLiteralAssignments && m_graph.isLiteralValue(value))
570
0
    {
571
0
      SSACFG::Operation assignment{
572
0
        .outputs = {m_graph.newVariable(m_currentBlock)},
573
0
        .kind = SSACFG::LiteralAssignment{},
574
0
        .inputs = {value}
575
0
      };
576
0
      currentBlock().operations.emplace_back(assignment);
577
0
      writeVariable(var, m_currentBlock, assignment.outputs.back());
578
0
    }
579
0
    else
580
0
      writeVariable(var, m_currentBlock, value);
581
0
  }
582
583
0
}
584
585
std::vector<SSACFG::ValueId> SSAControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call)
586
0
{
587
0
  bool canContinue = true;
588
0
  SSACFG::Operation operation = std::visit(util::GenericVisitor{
589
0
    [&](BuiltinName const& _builtinName)
590
0
    {
591
0
      auto const& builtin = m_dialect.builtin(_builtinName.handle);
592
0
      SSACFG::Operation result{{}, SSACFG::BuiltinCall{_call.debugData, builtin, _call}, {}};
593
0
      for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
594
0
        if (!builtin.literalArgument(idx).has_value())
595
0
          result.inputs.emplace_back(std::visit(*this, arg));
596
0
      for (size_t i = 0; i < builtin.numReturns; ++i)
597
0
        result.outputs.emplace_back(m_graph.newVariable(m_currentBlock));
598
0
      canContinue = builtin.controlFlowSideEffects.canContinue;
599
0
      return result;
600
0
    },
601
0
    [&](Identifier const& _identifier)
602
0
    {
603
0
      YulName const& functionName = _identifier.name;
604
0
      Scope::Function const& function = lookupFunction(functionName);
605
0
      auto const* definition = findFunctionDefinition(&function);
606
0
      yulAssert(definition);
607
0
      canContinue = m_sideEffects.functionSideEffects().at(definition).canContinue;
608
0
      SSACFG::Operation result{{}, SSACFG::Call{debugDataOf(_call), function, _call, canContinue}, {}};
609
0
      for (auto const& arg: _call.arguments | ranges::views::reverse)
610
0
        result.inputs.emplace_back(std::visit(*this, arg));
611
0
      for (size_t i = 0; i < function.numReturns; ++i)
612
0
        result.outputs.emplace_back(m_graph.newVariable(m_currentBlock));
613
0
      return result;
614
0
    }
615
0
  }, _call.functionName);
616
0
  auto results = operation.outputs;
617
0
  currentBlock().operations.emplace_back(std::move(operation));
618
0
  if (!canContinue)
619
0
  {
620
0
    currentBlock().exit = SSACFG::BasicBlock::Terminated{};
621
0
    m_currentBlock = m_graph.makeBlock(debugDataOf(currentBlock()));
622
0
    sealBlock(m_currentBlock);
623
0
  }
624
0
  return results;
625
0
}
626
627
SSACFG::ValueId SSAControlFlowGraphBuilder::zero()
628
0
{
629
0
  return m_graph.newLiteral(debugDataOf(currentBlock()), 0u);
630
0
}
631
632
SSACFG::ValueId SSAControlFlowGraphBuilder::readVariable(Scope::Variable const& _variable, SSACFG::BlockId _block)
633
0
{
634
0
  if (auto const& def = currentDef(_variable, _block))
635
0
    return *def;
636
0
  return readVariableRecursive(_variable, _block);
637
0
}
638
639
SSACFG::ValueId SSAControlFlowGraphBuilder::readVariableRecursive(Scope::Variable const& _variable, SSACFG::BlockId _block)
640
0
{
641
0
  auto& block = m_graph.block(_block);
642
0
  auto& info = blockInfo(_block);
643
644
0
  SSACFG::ValueId val;
645
0
  if (!info.sealed)
646
0
  {
647
    // incomplete block
648
0
    val = m_graph.newPhi(_block);
649
0
    block.phis.insert(val);
650
0
    info.incompletePhis.emplace_back(val, _variable);
651
0
  }
652
0
  else if (block.entries.size() == 1)
653
    // one predecessor: no phi needed
654
0
    val = readVariable(_variable, *block.entries.begin());
655
0
  else
656
0
  {
657
    // Break potential cycles with operandless phi
658
0
    val = m_graph.newPhi(_block);
659
0
    block.phis.insert(val);
660
0
    writeVariable(_variable, _block, val);
661
    // we call tryRemoveTrivialPhi explicitly opposed to what is presented in Algorithm 2, as our implementation
662
    // does not call it in addPhiOperands to avoid removing phis in unsealed blocks
663
0
    val = tryRemoveTrivialPhi(addPhiOperands(_variable, val));
664
0
  }
665
0
  writeVariable(_variable, _block, val);
666
0
  return val;
667
0
}
668
669
SSACFG::ValueId SSAControlFlowGraphBuilder::addPhiOperands(Scope::Variable const& _variable, SSACFG::ValueId _phi)
670
0
{
671
0
  yulAssert(std::holds_alternative<SSACFG::PhiValue>(m_graph.valueInfo(_phi)));
672
0
  auto& phi = std::get<SSACFG::PhiValue>(m_graph.valueInfo(_phi));
673
0
  for (auto const& pred: m_graph.block(phi.block).entries)
674
0
    phi.arguments.emplace_back(readVariable(_variable, pred));
675
  // we call tryRemoveTrivialPhi explicitly to avoid removing trivial phis in unsealed blocks
676
0
  return _phi;
677
0
}
678
679
void SSAControlFlowGraphBuilder::writeVariable(Scope::Variable const& _variable, SSACFG::BlockId _block, SSACFG::ValueId _value)
680
0
{
681
0
  currentDef(_variable, _block) = _value;
682
0
}
683
684
Scope::Function const& SSAControlFlowGraphBuilder::lookupFunction(YulName _name) const
685
0
{
686
0
  Scope::Function const* function = nullptr;
687
0
  yulAssert(m_scope->lookup(_name, util::GenericVisitor{
688
0
    [](Scope::Variable&) { yulAssert(false, "Expected function name."); },
689
0
    [&](Scope::Function& _function) { function = &_function; }
690
0
  }), "Function name not found.");
691
0
  yulAssert(function, "");
692
0
  return *function;
693
0
}
694
695
Scope::Variable const& SSAControlFlowGraphBuilder::lookupVariable(YulName _name) const
696
0
{
697
0
  yulAssert(m_scope, "");
698
0
  Scope::Variable const* var = nullptr;
699
0
  if (m_scope->lookup(_name, util::GenericVisitor{
700
0
    [&](Scope::Variable const& _var) { var = &_var; },
701
0
    [](Scope::Function const&)
702
0
    {
703
0
      yulAssert(false, "Function not removed during desugaring.");
704
0
    }
705
0
  }))
706
0
  {
707
0
    yulAssert(var);
708
0
    return *var;
709
0
  };
710
0
  yulAssert(false, "External identifier access unimplemented.");
711
0
}
712
713
void SSAControlFlowGraphBuilder::sealBlock(SSACFG::BlockId _block)
714
0
{
715
  // this method deviates from Algorithm 4 in the reference paper,
716
  // as it would lead to tryRemoveTrivialPhi being called on unsealed blocks
717
0
  auto& info = blockInfo(_block);
718
0
  yulAssert(!info.sealed, "Trying to seal already sealed block.");
719
0
  for (auto&& [phi, variable] : info.incompletePhis)
720
0
    addPhiOperands(variable, phi);
721
0
  info.sealed = true;
722
0
  for (auto& [phi, _]: info.incompletePhis)
723
0
    phi = tryRemoveTrivialPhi(phi);
724
0
}
725
726
727
void SSAControlFlowGraphBuilder::conditionalJump(
728
  langutil::DebugData::ConstPtr _debugData,
729
  SSACFG::ValueId _condition,
730
  SSACFG::BlockId _nonZero,
731
  SSACFG::BlockId _zero
732
)
733
0
{
734
0
  currentBlock().exit = SSACFG::BasicBlock::ConditionalJump{
735
0
    std::move(_debugData),
736
0
    _condition,
737
0
    _nonZero,
738
0
    _zero
739
0
  };
740
0
  m_graph.block(_nonZero).entries.insert(m_currentBlock);
741
0
  m_graph.block(_zero).entries.insert(m_currentBlock);
742
0
  m_currentBlock = {};
743
0
}
744
745
void SSAControlFlowGraphBuilder::jump(
746
  langutil::DebugData::ConstPtr _debugData,
747
  SSACFG::BlockId _target
748
)
749
0
{
750
0
  currentBlock().exit = SSACFG::BasicBlock::Jump{std::move(_debugData), _target};
751
0
  yulAssert(!blockInfo(_target).sealed);
752
0
  m_graph.block(_target).entries.insert(m_currentBlock);
753
0
  m_currentBlock = _target;
754
0
}
755
756
void SSAControlFlowGraphBuilder::tableJump(
757
  langutil::DebugData::ConstPtr _debugData,
758
  SSACFG::ValueId _value,
759
  std::map<u256, SSACFG::BlockId> _cases,
760
  SSACFG::BlockId _defaultCase)
761
0
{
762
0
  for (auto caseBlock: _cases | ranges::views::values)
763
0
  {
764
0
    yulAssert(!blockInfo(caseBlock).sealed);
765
0
    m_graph.block(caseBlock).entries.insert(m_currentBlock);
766
0
  }
767
0
  yulAssert(!blockInfo(_defaultCase).sealed);
768
0
  m_graph.block(_defaultCase).entries.insert(m_currentBlock);
769
0
  currentBlock().exit = SSACFG::BasicBlock::JumpTable{std::move(_debugData), _value, std::move(_cases), _defaultCase};
770
0
  m_currentBlock = {};
771
0
}
772
773
FunctionDefinition const* SSAControlFlowGraphBuilder::findFunctionDefinition(Scope::Function const* _function) const
774
0
{
775
0
  auto it = std::find_if(
776
0
      m_functionDefinitions.begin(),
777
0
      m_functionDefinitions.end(),
778
0
      [&_function](auto const& _entry) { return std::get<0>(_entry) == _function; }
779
0
    );
780
0
  if (it != m_functionDefinitions.end())
781
0
    return std::get<1>(*it);
782
0
  return nullptr;
783
0
}
784
785
}