Coverage Report

Created: 2026-10-26 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/backends/evm/ssa/SSACFGBuilder.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/ssa/SSACFGBuilder.h>
23
24
#include <libyul/backends/evm/ssa/ControlFlowGraphs.h>
25
26
#include <libyul/AST.h>
27
#include <libyul/ControlFlowSideEffectsCollector.h>
28
#include <libyul/optimiser/Metrics.h>
29
#include <libyul/Exceptions.h>
30
#include <libyul/Utilities.h>
31
32
#include <libsolutil/Algorithms.h>
33
#include <libsolutil/StringUtils.h>
34
#include <libsolutil/Visitor.h>
35
36
#include <range/v3/algorithm/reverse.hpp>
37
#include <range/v3/range/conversion.hpp>
38
#include <range/v3/view/drop_last.hpp>
39
#include <range/v3/view/enumerate.hpp>
40
#include <range/v3/view/filter.hpp>
41
#include <range/v3/view/reverse.hpp>
42
#include <range/v3/view/transform.hpp>
43
#include <range/v3/view/zip.hpp>
44
45
using namespace solidity;
46
using namespace solidity::yul;
47
using namespace solidity::yul::ssa;
48
49
SSACFGBuilder::SSACFGBuilder(
50
  ControlFlowGraphs& _controlFlow,
51
  SSACFG& _graph,
52
  AsmAnalysisInfo const& _analysisInfo,
53
  ControlFlowSideEffectsCollector const& _sideEffects,
54
  EVMDialect const& _dialect,
55
  bool _generateDebugInfo,
56
  FunctionRegistry& _functionRegistry
57
):
58
0
  m_controlFlow(_controlFlow),
59
0
  m_graph(_graph),
60
0
  m_info(_analysisInfo),
61
0
  m_sideEffects(_sideEffects),
62
0
  m_dialect(_dialect),
63
0
  m_generateDebugInfo(_generateDebugInfo),
64
0
  m_functionRegistry(_functionRegistry),
65
0
  m_memoryGuardHandle([&] {
66
0
    auto const memoryGuardHandle = m_dialect.findBuiltin("memoryguard");
67
0
    yulAssert(memoryGuardHandle.has_value(), "We only support EVM dialects that contain memoryguard");
68
0
    return *memoryGuardHandle;
69
0
  }())
70
0
{
71
0
}
72
73
std::unique_ptr<ControlFlowGraphs> SSACFGBuilder::build(
74
  AsmAnalysisInfo const& _analysisInfo,
75
  EVMDialect const& _dialect,
76
  Block const& _block,
77
  bool _generateDebugInfo
78
)
79
0
{
80
0
  ControlFlowSideEffectsCollector sideEffects(_dialect, _block);
81
82
0
  auto controlFlowGraphs = std::make_unique<ControlFlowGraphs>();
83
0
  controlFlowGraphs->functionGraphs.emplace_back(std::make_unique<SSACFG>(
84
0
    _dialect,
85
0
    _generateDebugInfo ? std::make_unique<SSACFGDebugInfo>() : nullptr,
86
0
    2 * CodeSize::codeSize(_block)  // empirically there are roughly 2x the instructions
87
0
  ));
88
0
  SSACFG& mainGraph = *controlFlowGraphs->functionGraphs.back();
89
0
  FunctionRegistry functionRegistry;
90
0
  SSACFGBuilder builder(*controlFlowGraphs, mainGraph, _analysisInfo, sideEffects, _dialect, _generateDebugInfo, functionRegistry);
91
0
  builder.m_currentBlock = mainGraph.makeBlock(debugDataOf(_block));
92
0
  builder.sealBlock(builder.m_currentBlock);
93
0
  builder(_block);
94
0
  if (!builder.blockInfo(builder.m_currentBlock).sealed)
95
0
    builder.sealBlock(builder.m_currentBlock);
96
0
  mainGraph.block(builder.m_currentBlock).exit = SSACFG::BasicBlock::MainExit{};
97
0
  return controlFlowGraphs;
98
0
}
99
100
101
void SSACFGBuilder::buildFunctionGraph(
102
  Scope::Function const* _function,
103
  FunctionDefinition const* _functionDefinition
104
)
105
0
{
106
  // The graph slot and the FunctionGraphID have already been allocated in
107
  // `registerFunctionDefinition`; this call builds the body into that slot.
108
0
  auto const regIt = m_functionRegistry.find(_function);
109
0
  yulAssert(regIt != m_functionRegistry.end(), "Function graph must be registered before building.");
110
0
  auto& cfg = *m_controlFlow.functionGraphs[regIt->second.id];
111
112
0
  yulAssert(m_info.scopes.at(&_functionDefinition->body), "");
113
0
  Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(_functionDefinition).get()).get();
114
0
  yulAssert(virtualFunctionScope, "");
115
116
0
  cfg.entry = cfg.makeBlock(debugDataOf(_functionDefinition->body));
117
0
  auto argumentBindings = _functionDefinition->parameters | ranges::views::transform([&](auto const& _param) {
118
0
    auto const& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name));
119
    // Note: cannot use std::make_tuple since it unwraps reference wrappers.
120
0
    return std::tuple{std::cref(var), cfg.newFunctionArgument()};
121
0
  }) | ranges::to<std::vector>;
122
0
  auto returnVars = _functionDefinition->returnVariables | ranges::views::transform([&](auto const& _param) {
123
0
    return std::cref(std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)));
124
0
  }) | ranges::to<std::vector<std::reference_wrapper<Scope::Variable const>>>();
125
126
0
  if (cfg.debugInfo)
127
0
    cfg.debugInfo->graphDebugData = _functionDefinition->debugData;
128
0
  cfg.canContinue = m_sideEffects.functionSideEffects().at(_functionDefinition).canContinue;
129
0
  cfg.arguments = argumentBindings
130
0
    | ranges::views::transform([](auto const& _binding) { return std::get<1>(_binding); })
131
0
    | ranges::to<std::vector>;
132
133
0
  SSACFGBuilder builder(m_controlFlow, cfg, m_info, m_sideEffects, m_dialect, m_generateDebugInfo, m_functionRegistry);
134
0
  builder.m_currentBlock = cfg.entry;
135
0
  builder.m_currentReturnVars = returnVars;
136
0
  for (auto&& [var, varId]: argumentBindings)
137
0
    builder.currentDef(var, cfg.entry) = varId;
138
0
  for (auto const& var: returnVars)
139
0
    builder.currentDef(var.get(), cfg.entry) = builder.zero();
140
0
  builder.sealBlock(cfg.entry);
141
0
  builder(_functionDefinition->body);
142
0
  cfg.exits.insert(builder.m_currentBlock);
143
  // Artificial explicit function exit (`leave`) at the end of the body.
144
0
  builder(Leave{debugDataOf(*_functionDefinition)});
145
0
}
146
147
void SSACFGBuilder::operator()(ExpressionStatement const& _expressionStatement)
148
0
{
149
0
  auto const* functionCall = std::get_if<FunctionCall>(&_expressionStatement.expression);
150
0
  yulAssert(functionCall);
151
0
  visitFunctionCall(*functionCall);
152
0
}
153
154
void SSACFGBuilder::operator()(Assignment const& _assignment)
155
0
{
156
0
  assign(
157
0
    _assignment.variableNames | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>,
158
0
    _assignment.value.get()
159
0
  );
160
0
}
161
162
void SSACFGBuilder::operator()(VariableDeclaration const& _variableDeclaration)
163
0
{
164
0
  assign(
165
0
    _variableDeclaration.variables | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>,
166
0
    _variableDeclaration.value.get()
167
0
  );
168
0
}
169
170
void SSACFGBuilder::operator()(FunctionDefinition const& _functionDefinition)
171
0
{
172
0
  Scope::Function const& function = lookupFunction(_functionDefinition.name);
173
0
  buildFunctionGraph(&function, &_functionDefinition);
174
0
}
175
176
void SSACFGBuilder::operator()(If const& _if)
177
0
{
178
0
  auto const condition = std::visit(*this, *_if.condition);
179
0
  auto const ifBranch = m_graph.makeBlock(debugDataOf(_if.body));
180
0
  auto const afterIf = m_graph.makeBlock(currentBlockDebugData());
181
0
  conditionalJump(
182
0
    debugDataOf(_if),
183
0
    condition,
184
0
    ifBranch,
185
0
    afterIf
186
0
  );
187
0
  sealBlock(ifBranch);
188
0
  m_currentBlock = ifBranch;
189
0
  (*this)(_if.body);
190
0
  jump(debugDataOf(_if.body), afterIf);
191
0
  sealBlock(afterIf);
192
0
}
193
194
void SSACFGBuilder::operator()(Switch const& _switch)
195
0
{
196
0
  auto expression = std::visit(*this, *_switch.expression);
197
198
0
  std::optional<BuiltinHandle> equalityBuiltinHandle = m_dialect.equalityFunctionHandle();
199
0
  yulAssert(equalityBuiltinHandle);
200
201
0
  auto const& equalityBuiltin = m_dialect.builtin(*equalityBuiltinHandle);
202
0
  auto makeValueCompare = [&](Case const& _case) {
203
0
    return m_graph.makeBuiltinCallWithProjections(
204
0
      m_currentBlock,
205
0
      SSACFG::BuiltinCall{*equalityBuiltinHandle, {}},
206
0
      {expression, m_graph.newLiteral(debugDataOf(_case), _case.value->value.value())},
207
0
      static_cast<InstructionStore::NumReturnsSizeType>(equalityBuiltin.numReturns),
208
0
      debugDataOf(_case)
209
0
    );
210
0
  };
211
212
0
  auto afterSwitch = m_graph.makeBlock(currentBlockDebugData());
213
0
  yulAssert(!_switch.cases.empty(), "");
214
0
  for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1))
215
0
  {
216
0
    yulAssert(switchCase.value, "");
217
0
    auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
218
0
    auto elseBranch = m_graph.makeBlock(debugDataOf(_switch));
219
220
0
    conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch);
221
0
    sealBlock(caseBranch);
222
0
    sealBlock(elseBranch);
223
0
    m_currentBlock = caseBranch;
224
0
    (*this)(switchCase.body);
225
0
    jump(debugDataOf(switchCase.body), afterSwitch);
226
0
    m_currentBlock = elseBranch;
227
0
  }
228
0
  Case const& switchCase = _switch.cases.back();
229
0
  if (switchCase.value)
230
0
  {
231
0
    auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
232
0
    conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch);
233
0
    sealBlock(caseBranch);
234
0
    m_currentBlock = caseBranch;
235
0
  }
236
0
  (*this)(switchCase.body);
237
0
  jump(debugDataOf(switchCase.body), afterSwitch);
238
0
  sealBlock(afterSwitch);
239
0
}
240
void SSACFGBuilder::operator()(ForLoop const& _loop)
241
0
{
242
0
  ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get());
243
0
  (*this)(_loop.pre);
244
0
  auto preLoopDebugData = currentBlockDebugData();
245
246
0
  SSACFG::BlockId const loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition));
247
0
  SSACFG::BlockId const loopBody = m_graph.makeBlock(debugDataOf(_loop.body));
248
0
  SSACFG::BlockId const post = m_graph.makeBlock(debugDataOf(_loop.post));
249
0
  SSACFG::BlockId const afterLoop = m_graph.makeBlock(preLoopDebugData);
250
251
0
  class ForLoopInfoScope {
252
0
  public:
253
0
    ForLoopInfoScope(std::stack<ForLoopInfo>& _info, SSACFG::BlockId _breakBlock, SSACFG::BlockId _continueBlock): m_info(_info)
254
0
    {
255
0
      m_info.push(ForLoopInfo{_breakBlock, _continueBlock});
256
0
    }
257
0
    ~ForLoopInfoScope() {
258
0
      m_info.pop();
259
0
    }
260
0
  private:
261
0
    std::stack<ForLoopInfo>& m_info;
262
0
  } forLoopInfoScope(m_forLoopInfo, afterLoop, post);
263
264
0
  jump(debugDataOf(_loop.pre), loopCondition);
265
0
  auto condition = std::visit(*this, *_loop.condition);
266
0
  conditionalJump(debugDataOf(*_loop.condition), condition, loopBody, afterLoop);
267
0
  sealBlock(loopBody);
268
0
  m_currentBlock = loopBody;
269
0
  (*this)(_loop.body);
270
0
  jump(debugDataOf(_loop.body), post);
271
0
  sealBlock(post);
272
0
  (*this)(_loop.post);
273
0
  jump(debugDataOf(_loop.post), loopCondition);
274
0
  sealBlock(loopCondition);
275
276
0
  sealBlock(afterLoop);
277
0
  m_currentBlock = afterLoop;
278
0
}
279
280
void SSACFGBuilder::operator()(Break const& _break)
281
0
{
282
0
  yulAssert(!m_forLoopInfo.empty());
283
0
  auto savedBlockDebugData = currentBlockDebugData();
284
0
  jump(debugDataOf(_break), m_forLoopInfo.top().breakBlock);
285
0
  m_currentBlock = m_graph.makeBlock(savedBlockDebugData);
286
0
  sealBlock(m_currentBlock);
287
0
}
288
289
void SSACFGBuilder::operator()(Continue const& _continue)
290
0
{
291
0
  yulAssert(!m_forLoopInfo.empty());
292
0
  auto const savedBlockDebugData = currentBlockDebugData();
293
0
  jump(debugDataOf(_continue), m_forLoopInfo.top().continueBlock);
294
0
  m_currentBlock = m_graph.makeBlock(savedBlockDebugData);
295
0
  sealBlock(m_currentBlock);
296
0
}
297
298
void SSACFGBuilder::operator()(Leave const& _leaveStatement)
299
0
{
300
0
  auto const savedBlockDebugData = currentBlockDebugData();
301
0
  if (m_graph.debugInfo)
302
0
    m_graph.debugInfo->setExitDebugData(m_currentBlock, debugDataOf(_leaveStatement));
303
0
  currentBlock().exit = SSACFG::BasicBlock::FunctionReturn{
304
0
    m_currentReturnVars | ranges::views::transform([&](auto _var) {
305
0
      return readVariable(_var, m_currentBlock);
306
0
    }) | ranges::to<std::vector>
307
0
  };
308
0
  m_currentBlock = m_graph.makeBlock(savedBlockDebugData);
309
0
  sealBlock(m_currentBlock);
310
0
}
311
312
void SSACFGBuilder::registerFunctionDefinition(FunctionDefinition const& _functionDefinition)
313
0
{
314
0
  yulAssert(m_scope, "");
315
0
  yulAssert(m_scope->identifiers.count(_functionDefinition.name), "");
316
0
  auto& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name));
317
318
  // Allocate the graph slot up front so sibling functions (and mutually-recursive pairs) can
319
  // reference this function by its FunctionGraphID when their bodies are built later.
320
0
  m_controlFlow.functionGraphs.emplace_back(std::make_unique<SSACFG>(
321
0
    m_dialect,
322
0
    m_generateDebugInfo ? std::make_unique<SSACFGDebugInfo>() : nullptr,
323
0
    2 * CodeSize::codeSize(_functionDefinition.body)  // empirically there are roughly 2x the instructions
324
0
  ));
325
0
  auto const graphID = static_cast<FunctionGraphID>(m_controlFlow.functionGraphs.size() - 1);
326
0
  auto& cfg = *m_controlFlow.functionGraphs.back();
327
0
  cfg.name = function.name.str();
328
0
  cfg.numReturns = _functionDefinition.returnVariables.size();
329
0
  m_functionRegistry[&function] = {graphID, &_functionDefinition};
330
0
}
331
332
void SSACFGBuilder::operator()(Block const& _block)
333
0
{
334
0
  ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get());
335
  // gather all function definitions so that they are visible to each other's subgraphs
336
0
  static constexpr auto functionDefinitionFilter = ranges::views::filter(
337
0
    [](auto const& _statement) { return std::holds_alternative<FunctionDefinition>(_statement); }
338
0
  );
339
0
  for (auto const& statement: _block.statements | functionDefinitionFilter)
340
0
    registerFunctionDefinition(std::get<FunctionDefinition>(statement));
341
  // now visit the rest
342
0
  for (auto const& statement: _block.statements)
343
0
    std::visit(*this, statement);
344
0
}
345
346
InstId SSACFGBuilder::operator()(FunctionCall const& _call)
347
0
{
348
  // Single-output expression context: the call's InstId is itself the value handle.
349
0
  return visitFunctionCall(_call);
350
0
}
351
352
InstId SSACFGBuilder::operator()(Identifier const& _identifier)
353
0
{
354
0
  auto const& var = lookupVariable(_identifier.name);
355
0
  return readVariable(var, m_currentBlock);
356
0
}
357
358
InstId SSACFGBuilder::operator()(Literal const& _literal)
359
0
{
360
0
  return m_graph.newLiteral(currentBlockDebugData(), _literal.value.value());
361
0
}
362
363
void SSACFGBuilder::assign(std::vector<std::reference_wrapper<Scope::Variable const>> _variables, Expression const* _expression)
364
0
{
365
0
  if (auto const* functionCall = std::get_if<FunctionCall>(_expression))
366
0
  {
367
0
    InstId const callId = visitFunctionCall(*functionCall);
368
0
    if (m_graph.isUnreachable(callId))
369
0
    {
370
      // The callee did not continue: post-call block is unreachable. Bind variables to unreachable.
371
0
      for (auto const& var: _variables)
372
0
        writeVariable(var, m_currentBlock, callId);
373
0
    }
374
0
    else
375
0
    {
376
0
      yulAssert(m_graph.numReturnsOf(callId) == _variables.size());
377
0
      auto const outputs = m_graph.outputsOf(callId);
378
0
      yulAssert(outputs.size() == _variables.size());
379
0
      for (auto const& [var, output]: ranges::views::zip(_variables, outputs))
380
0
        writeVariable(var, m_currentBlock, output);
381
0
    }
382
0
    return;
383
0
  }
384
0
  auto const rhs = _expression ?
385
0
    std::vector{std::visit(*this, *_expression)} :
386
0
    std::vector(_variables.size(), zero());
387
0
  yulAssert(rhs.size() == _variables.size());
388
0
  for (auto const& [var, value]: ranges::zip_view(_variables, rhs))
389
0
    writeVariable(var, m_currentBlock, value);
390
0
}
391
392
InstId SSACFGBuilder::visitFunctionCall(FunctionCall const& _call)
393
0
{
394
0
  bool canContinue = true;
395
0
  InstId const id = std::visit(solidity::util::GenericVisitor{
396
0
    [&](BuiltinName const& _builtinName) -> InstId
397
0
    {
398
      // memoryguard(N) is represented as a dedicated MemoryGuard Inst; the boundary value lives in the
399
      // corresponding ControlFlowGraphs instance
400
0
      if (_builtinName.handle == m_memoryGuardHandle)
401
0
      {
402
0
        yulAssert(_call.arguments.size() == 1);
403
0
        Literal const* literal = std::get_if<Literal>(&_call.arguments.front());
404
0
        yulAssert(literal && literal->kind == LiteralKind::Number);
405
0
        u256 const value = literal->value.value();
406
0
        if (m_controlFlow.memoryGuard)
407
0
          yulAssert(
408
0
            *m_controlFlow.memoryGuard == value,
409
0
            "memoryguard: inconsistent literal across subobject"
410
0
          );
411
0
        else
412
0
          m_controlFlow.memoryGuard = value;
413
0
        canContinue = m_dialect.builtin(m_memoryGuardHandle).controlFlowSideEffects.canContinue;
414
0
        return m_graph.makeMemoryGuard(m_currentBlock, debugDataOf(_call));
415
0
      }
416
417
0
      auto const& builtin = m_dialect.builtin(_builtinName.handle);
418
0
      yulAssert(_call.arguments.size() == builtin.numParameters);
419
0
      std::vector<Literal> literalArguments;
420
0
      for (auto&& [kind, arg]: ranges::views::zip(builtin.literalArguments, _call.arguments))
421
0
        if (kind.has_value())
422
0
        {
423
0
          yulAssert(std::holds_alternative<Literal>(arg));
424
0
          literalArguments.emplace_back(std::get<Literal>(arg));
425
0
        }
426
      // Arguments must be evaluated from right to left, according to Yul specification
427
0
      std::vector<InstId> inputs;
428
0
      for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
429
0
        if (!builtin.literalArgument(idx).has_value())
430
0
          inputs.emplace_back(std::visit(*this, arg));
431
      // But we want to store them in the original order
432
0
      ranges::reverse(inputs);
433
0
      canContinue = builtin.controlFlowSideEffects.canContinue;
434
0
      return m_graph.makeBuiltinCallWithProjections(
435
0
        m_currentBlock,
436
0
        SSACFG::BuiltinCall{_builtinName.handle, std::move(literalArguments)},
437
0
        std::move(inputs),
438
0
        static_cast<InstructionStore::NumReturnsSizeType>(builtin.numReturns),
439
0
        debugDataOf(_call)
440
0
      );
441
0
    },
442
0
    [&](Identifier const& _identifier) -> InstId
443
0
    {
444
0
      YulName const& functionName = _identifier.name;
445
0
      Scope::Function const& function = lookupFunction(functionName);
446
0
      auto const calleeIt = m_functionRegistry.find(&function);
447
0
      yulAssert(calleeIt != m_functionRegistry.end(), "Called function has no registered graph id.");
448
0
      canContinue = m_sideEffects.functionSideEffects().at(calleeIt->second.definition).canContinue;
449
      // Arguments must be evaluated from right to left, according to Yul specification
450
0
      std::vector<InstId> inputs;
451
0
      for (auto const& arg: _call.arguments | ranges::views::reverse)
452
0
        inputs.emplace_back(std::visit(*this, arg));
453
      // But we want to store them in the original order
454
0
      ranges::reverse(inputs);
455
0
      return m_graph.makeCallWithProjections(
456
0
        m_currentBlock,
457
0
        SSACFG::Call{calleeIt->second.id, canContinue, function.numReturns},
458
0
        std::move(inputs),
459
0
        static_cast<InstructionStore::NumReturnsSizeType>(function.numReturns),
460
0
        debugDataOf(_call)
461
0
      );
462
0
    }
463
0
  }, _call.functionName);
464
0
  if (canContinue)
465
0
    return id;
466
  // `id` lives in the now-Terminated call block and won't dominate the successor,
467
  // so terminate, open a fresh successor and signal non-continuation via unreachableValue.
468
0
  currentBlock().exit = SSACFG::BasicBlock::Terminated{};
469
0
  m_currentBlock = m_graph.makeBlock(currentBlockDebugData());
470
0
  sealBlock(m_currentBlock);
471
0
  return m_graph.unreachableValue();
472
0
}
473
474
InstId SSACFGBuilder::zero()
475
0
{
476
0
  return m_graph.newLiteral(currentBlockDebugData(), 0u);
477
0
}
478
479
InstId SSACFGBuilder::readVariable(Scope::Variable const& _variable, SSACFG::BlockId _block)
480
0
{
481
0
  auto const& def = currentDef(_variable, _block);
482
0
  if (def.hasValue())
483
0
    return def;
484
0
  return readVariableRecursive(_variable, _block);
485
0
}
486
487
InstId SSACFGBuilder::readVariableRecursive(Scope::Variable const& _variable, SSACFG::BlockId _block)
488
0
{
489
0
  auto& block = m_graph.block(_block);
490
0
  auto& info = blockInfo(_block);
491
492
0
  InstId val;
493
0
  if (!info.sealed)
494
0
  {
495
    // incomplete block: create a phi and defer upsilon emission until the block is sealed
496
0
    val = m_graph.newPhi(_block);
497
0
    info.incompletePhis.emplace_back(val, _variable);
498
0
  }
499
0
  else if (block.entries.size() == 1)
500
    // one predecessor: no phi needed
501
0
    val = readVariable(_variable, *block.entries.begin());
502
0
  else
503
0
  {
504
    // Break potential cycles with an argument-less phi; emit upsilons for all predecessors.
505
0
    val = m_graph.newPhi(_block);
506
0
    writeVariable(_variable, _block, val);
507
0
    addPhiOperands(_variable, val);
508
0
  }
509
0
  writeVariable(_variable, _block, val);
510
0
  return val;
511
0
}
512
513
void SSACFGBuilder::addPhiOperands(Scope::Variable const& _variable, InstId _phi)
514
0
{
515
0
  SSACFG::BlockId const phiBlock = m_graph.inst(_phi).block;
516
0
  for (auto const& pred: m_graph.block(phiBlock).entries)
517
0
  {
518
0
    auto const val = readVariable(_variable, pred);
519
0
    emitUpsilon(pred, val, _phi);
520
0
  }
521
0
}
522
523
void SSACFGBuilder::emitUpsilon(SSACFG::BlockId _block, InstId _value, InstId _phi)
524
0
{
525
0
  yulAssert(m_graph.isPhi(_phi));
526
0
  m_graph.emitUpsilon(_block, _value, _phi);
527
0
}
528
529
void SSACFGBuilder::writeVariable(Scope::Variable const& _variable, SSACFG::BlockId _block, InstId _value)
530
0
{
531
0
  currentDef(_variable, _block) = _value;
532
0
}
533
534
Scope::Function const& SSACFGBuilder::lookupFunction(YulName _name) const
535
0
{
536
0
  Scope::Function const* function = nullptr;
537
0
  yulAssert(m_scope->lookup(_name, solidity::util::GenericVisitor{
538
0
    [](Scope::Variable&) { yulAssert(false, "Expected function name."); },
539
0
    [&](Scope::Function& _function) { function = &_function; }
540
0
  }), "Function name not found.");
541
0
  yulAssert(function, "");
542
0
  return *function;
543
0
}
544
545
Scope::Variable const& SSACFGBuilder::lookupVariable(YulName _name) const
546
0
{
547
0
  yulAssert(m_scope, "");
548
0
  Scope::Variable const* var = nullptr;
549
0
  if (m_scope->lookup(_name, solidity::util::GenericVisitor{
550
0
    [&](Scope::Variable const& _var) { var = &_var; },
551
0
    [](Scope::Function const&)
552
0
    {
553
0
      yulAssert(false, "Function not removed during desugaring.");
554
0
    }
555
0
  }))
556
0
  {
557
0
    yulAssert(var);
558
0
    return *var;
559
0
  };
560
0
  yulAssert(false, "External identifier access unimplemented.");
561
0
}
562
563
void SSACFGBuilder::sealBlock(SSACFG::BlockId _block)
564
0
{
565
0
  auto& info = blockInfo(_block);
566
0
  yulAssert(!info.sealed, "Trying to seal already sealed block.");
567
  // emit upsilons for all incomplete phis before marking the block as sealed
568
0
  for (auto&& [phi, variable] : info.incompletePhis)
569
0
    addPhiOperands(variable, phi);
570
0
  info.sealed = true;
571
0
}
572
573
574
void SSACFGBuilder::conditionalJump(
575
  langutil::DebugData::ConstPtr _debugData,
576
  InstId _condition,
577
  SSACFG::BlockId _nonZero,
578
  SSACFG::BlockId _zero
579
)
580
0
{
581
0
  if (m_graph.debugInfo)
582
0
    m_graph.debugInfo->setExitDebugData(m_currentBlock, std::move(_debugData));
583
0
  currentBlock().exit = SSACFG::BasicBlock::ConditionalJump{
584
0
    _condition,
585
0
    _nonZero,
586
0
    _zero
587
0
  };
588
0
  m_graph.block(_nonZero).entries.push_back(m_currentBlock);
589
0
  m_graph.block(_zero).entries.push_back(m_currentBlock);
590
0
  m_currentBlock = {};
591
0
}
592
593
void SSACFGBuilder::jump(
594
  langutil::DebugData::ConstPtr _debugData,
595
  SSACFG::BlockId _target
596
)
597
0
{
598
0
  if (m_graph.debugInfo)
599
0
    m_graph.debugInfo->setExitDebugData(m_currentBlock, std::move(_debugData));
600
0
  currentBlock().exit = SSACFG::BasicBlock::Jump{_target};
601
  yulAssert(!blockInfo(_target).sealed);
602
0
  m_graph.block(_target).entries.push_back(m_currentBlock);
603
0
  m_currentBlock = _target;
604
0
}
605