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/ssa/SSACFG.h
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
 * Control flow graph and stack layout structures used during code generation.
20
 */
21
22
#pragma once
23
24
#include <libyul/backends/evm/ssa/InstructionStore.h>
25
#include <libyul/backends/evm/ssa/SSACFGDebugInfo.h>
26
#include <libyul/backends/evm/ssa/SSACFGTypes.h>
27
28
#include <libyul/backends/evm/EVMDialect.h>
29
30
#include <libyul/Dialect.h>
31
#include <libyul/Exceptions.h>
32
33
#include <libsolutil/Numeric.h>
34
35
#include <range/v3/algorithm/all_of.hpp>
36
#include <range/v3/range/conversion.hpp>
37
#include <range/v3/view/filter.hpp>
38
#include <range/v3/view/iota.hpp>
39
#include <range/v3/view/transform.hpp>
40
41
#include <concepts>
42
#include <functional>
43
#include <optional>
44
#include <string>
45
#include <vector>
46
47
namespace solidity::yul::ssa
48
{
49
class LivenessAnalysis;
50
struct ControlFlowGraphs;
51
52
class SSACFG
53
{
54
public:
55
  using DebugInfo = SSACFGDebugInfo;
56
57
  explicit SSACFG(
58
    EVMDialect const& _evmVersion,
59
    std::unique_ptr<DebugInfo> _debugInfo = nullptr,
60
    std::size_t const _instructionCountHint = 0
61
  ):
62
0
    m_instructions(_instructionCountHint),
63
0
    evmDialect(_evmVersion),
64
0
    debugInfo(std::move(_debugInfo))
65
0
  {}
66
67
  SSACFG(SSACFG const&) = delete;
68
  SSACFG(SSACFG&&) = delete;
69
  SSACFG& operator=(SSACFG const&) = delete;
70
  SSACFG& operator=(SSACFG&&) = delete;
71
0
  ~SSACFG() = default;
72
73
  using BlockId = ssa::BlockId;
74
75
  using BuiltinCall = InstructionStore::BuiltinCall;
76
  using Call = InstructionStore::Call;
77
  using Inst = InstructionStore::Inst;
78
79
  struct BasicBlock
80
  {
81
    struct MainExit {};
82
    struct ConditionalJump
83
    {
84
      InstId condition;
85
      BlockId nonZero;
86
      BlockId zero;
87
    };
88
    struct Jump
89
    {
90
      BlockId target;
91
    };
92
    struct FunctionReturn
93
    {
94
      std::vector<InstId> returnValues;
95
    };
96
    struct Terminated {};
97
    std::vector<BlockId> entries;
98
    std::vector<InstId> instructions;
99
    std::variant<MainExit, Jump, ConditionalJump, FunctionReturn, Terminated> exit = MainExit{};
100
101
    template<std::invocable<BlockId> Callable>
102
    void forEachExit(Callable&& _callable) const
103
0
    {
104
0
      if (auto* jump = std::get_if<Jump>(&exit))
105
0
        _callable(jump->target);
106
0
      else if (auto* conditionalJump = std::get_if<ConditionalJump>(&exit))
107
0
      {
108
0
        _callable(conditionalJump->nonZero);
109
0
        _callable(conditionalJump->zero);
110
0
      }
111
0
    }
Unexecuted instantiation: LivenessAnalysis.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_16LivenessAnalysis9runDagDfsEvE3$_0EEvOT_
Unexecuted instantiation: StackLayoutGenerator.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_20StackLayoutGeneratorC1ERKNS1_16LivenessAnalysisERKNS1_9CallSitesEjbNS1_5spill8SpillSetEE3$_0EEvOT_
Unexecuted instantiation: StackUtils.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_15gatherCallSitesERKS2_E3$_0EEvOT_
Unexecuted instantiation: DotExporterBase.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_2io15DotExporterBase8traverseERNS5_13basic_ostreamIcNS5_11char_traitsIcEEEES7_E3$_0EEvOT_
Unexecuted instantiation: UnreachableBlockCleaner.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_9transform22cleanUnreachableBlocksERS2_E3$_0EEvOT_
Unexecuted instantiation: ForwardTopologicalSort.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_9traversal22ForwardTopologicalSort3dfsEjE3$_0EEvOT_
Unexecuted instantiation: ForwardTopologicalSort.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNKS1_9traversal22ForwardTopologicalSort8backEdgeERKS7_SB_E3$_0EEvOT_
Unexecuted instantiation: _ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZNS1_12BridgeFinder3dfsERmRKS7_RKNS5_8optionalIS7_EEEUlSB_E_EEvOT_
Unexecuted instantiation: JumpThreader.cpp:_ZNK8solidity3yul3ssa6SSACFG10BasicBlock11forEachExitITkNSt3__19invocableINS1_7BlockIdEEEZN12_GLOBAL__N_129mergeSuccessorIntoPredecessorERS2_S7_S7_E3$_1EEvOT_
112
113
0
    bool isMainExitBlock() const { return std::holds_alternative<MainExit>(exit); }
114
0
    bool isTerminationBlock() const { return std::holds_alternative<Terminated>(exit); }
115
0
    bool isFunctionReturnBlock() const { return std::holds_alternative<FunctionReturn>(exit); }
116
0
    bool isJumpBlock() const { return std::holds_alternative<Jump>(exit); }
117
  };
118
119
  BlockId makeBlock(langutil::DebugData::ConstPtr _debugData)
120
0
  {
121
0
    BlockId blockId;
122
0
    if (!m_freeBlocks.empty())
123
0
    {
124
0
      blockId = m_freeBlocks.back();
125
0
      yulAssert(blockId.value < m_freeBlocks.size());
126
0
      std::optional<BasicBlock>& block = m_blocks[blockId.value];
127
0
      yulAssert(!block.has_value());
128
0
      m_freeBlocks.pop_back();
129
0
      block.emplace(BasicBlock{{}, {}, BasicBlock::Terminated{}});
130
0
    }
131
0
    else
132
0
    {
133
0
      yulAssert(m_blocks.size() < std::numeric_limits<BlockId::ValueType>::max());
134
0
      blockId = BlockId{static_cast<BlockId::ValueType>(m_blocks.size())};
135
0
      m_blocks.emplace_back(BasicBlock{{}, {}, BasicBlock::Terminated{}});
136
0
    }
137
0
    if (debugInfo)
138
0
      debugInfo->setBlockDebugData(blockId, std::move(_debugData));
139
0
    return blockId;
140
0
  }
141
  BasicBlock& block(BlockId _id)
142
0
  {
143
0
    auto& slot = m_blocks.at(_id.value);
144
0
    yulAssert(slot.has_value(), fmt::format("Access of dead block #{}", _id.value));
145
0
    return *slot;
146
0
  }
147
  BasicBlock const& block(BlockId _id) const
148
0
  {
149
0
    auto const& slot = m_blocks.at(_id.value);
150
0
    yulAssert(slot.has_value(), fmt::format("Access of dead block #{}", _id.value));
151
0
    return *slot;
152
0
  }
153
0
  size_t numBlocks() const { return m_blocks.size(); }
154
0
  bool hasBlock(BlockId const _id) const { return _id.value < m_blocks.size() && m_blocks[_id.value].has_value(); }
155
  void resetBlock(BlockId const _id)
156
0
  {
157
0
    yulAssert(_id.value < m_blocks.size());
158
0
    auto& block = m_blocks[_id.value];
159
0
    yulAssert(block.has_value(), "double reset");
160
0
    yulAssert(
161
0
      ranges::all_of(block->instructions, [this](InstId const _instId) { return isTombstone(_instId); }),
162
0
      "can only reset blocks that have no live instructions left"
163
0
    );
164
0
    block.reset();
165
0
    m_freeBlocks.push_back(_id);
166
0
  }
167
168
  InputRangeOf<BlockId> auto liveBlocks() const
169
0
  {
170
0
    return
171
0
      ranges::views::iota(BlockId::ValueType{0}, static_cast<BlockId::ValueType>(m_blocks.size())) |
172
0
      ranges::views::filter([this](BlockId::ValueType const _v) { return m_blocks[_v].has_value(); }) |
173
0
      ranges::views::transform([](BlockId::ValueType const _v) { return BlockId{_v}; });
174
0
  }
175
176
0
  InstructionStore& instructionStore() { return m_instructions; }
177
0
  InstructionStore const& instructionStore() const { return m_instructions; }
178
179
0
  Inst& inst(InstId _id) { return m_instructions.inst(_id); }
180
0
  Inst const& inst(InstId _id) const { return m_instructions.inst(_id); }
181
0
  size_t numInsts() const { return m_instructions.numInsts(); }
182
  InputRangeOf<InstId> auto instructionIds() const
183
0
  {
184
0
    return
185
0
      ranges::views::iota(static_cast<InstId::ValueType>(0), static_cast<InstId::ValueType>(numInsts())) |
186
0
      ranges::views::transform([](auto const _value) { return InstId{_value}; });
187
0
  }
188
0
  std::vector<Inst> const& instructions() const { return m_instructions.instructions(); }
189
190
  /// Returns the opcode category for a given InstId.
191
0
  InstOpcode kindOf(InstId const _id) const { return m_instructions.kindOf(_id); }
192
193
0
  bool isPhi(InstId const _id) const { return inst(_id).isPhi(); }
194
0
  bool isUpsilon(InstId const _id) const { return inst(_id).isUpsilon(); }
195
0
  bool isLiteral(InstId const _id) const { return inst(_id).isLiteral(); }
196
0
  bool isUnreachable(InstId const _id) const { return inst(_id).isUnreachable(); }
197
0
  bool isFunctionArg(InstId const _id) const { return inst(_id).isFunctionArg(); }
198
0
  bool isProjection(InstId const _id) const { return inst(_id).isProjection(); }
199
0
  bool isIdentity(InstId const _id) const { return inst(_id).isIdentity(); }
200
0
  bool isNop(InstId const _id) const { return inst(_id).isNop(); }
201
0
  bool isMemoryGuard(InstId const _id) const { return inst(_id).isMemoryGuard(); }
202
0
  bool isTombstone(InstId const _id) const { return inst(_id).isTombstone(); }
203
0
  bool isOperation(InstId const _id) const { return inst(_id).isOperation(); }
204
205
  /// Walks the Identity chain starting at `_id` and returns the terminal (non-Identity) target.
206
  InstId resolveIdentity(InstId _id) const
207
0
  {
208
0
    while (_id.hasValue() && kindOf(_id) == InstOpcode::Identity)
209
0
    {
210
0
      auto const& i = inst(_id);
211
0
      yulAssert(i.inputs.size() == 1);
212
0
      yulAssert(i.inputs[0] != _id, "Identity self-loop");
213
0
      _id = i.inputs[0];
214
0
    }
215
0
    return _id;
216
0
  }
217
218
  /// Returns the phi targeted by an Upsilon Inst.
219
0
  InstId upsilonPhi(InstId const _id) const { return m_instructions.upsilonPhi(_id); }
220
221
  /// Returns the u256 payload of a Const Inst.
222
0
  u256 const& literalPayload(InstId const _id) const { return m_instructions.literalPayload(_id); }
223
224
0
  BuiltinCall const& builtinPayload(InstId const _id) const { return m_instructions.builtinPayload(_id); }
225
226
0
  Call const& callPayload(InstId const _id) const { return m_instructions.callPayload(_id); }
227
228
  /// Returns the projection index of a Projection Inst.
229
0
  InstructionStore::NumReturnsSizeType projectionIndex(InstId const _id) const { return m_instructions.projectionIndex(_id); }
230
231
  /// Creates a Phi Inst in the given block and returns its InstId.
232
  InstId newPhi(BlockId const _definingBlock)
233
0
  {
234
0
    InstId const id = scheduleInBlock(m_instructions.appendPhi(_definingBlock), _definingBlock);
235
0
    if (debugInfo)
236
0
      debugInfo->setValueDebugData(id, debugInfo->blockDebugData(_definingBlock));
237
0
    return id;
238
0
  }
239
240
  InstId newFunctionArgument()
241
0
  {
242
0
    InstId const id = scheduleInBlock(m_instructions.appendFunctionArg(entry), entry);
243
0
    if (debugInfo)
244
0
      debugInfo->setValueDebugData(id, debugInfo->blockDebugData(entry));
245
0
    return id;
246
0
  }
247
248
  /// Allocates a MemoryGuard Inst at `_block`. The boundary value lives in `ControlFlowGraphs::memoryGuard`.
249
  InstId makeMemoryGuard(BlockId const _block, langutil::DebugData::ConstPtr _debugData = {})
250
0
  {
251
0
    InstId const id = scheduleInBlock(m_instructions.appendMemoryGuard(_block), _block);
252
0
    if (debugInfo && _debugData)
253
0
      debugInfo->setValueDebugData(id, std::move(_debugData));
254
0
    return id;
255
0
  }
256
257
  InstId unreachableValue()
258
0
  {
259
0
    return m_instructions.appendUnreachable();
260
0
  }
261
262
  /// Literal InstIds are deduplicated. Const Insts are pinned to the entry block.
263
  InstId newLiteral(langutil::DebugData::ConstPtr _debugData, u256 _value)
264
0
  {
265
0
    auto const [id, newlyAllocated] = m_instructions.appendLiteral(entry, std::move(_value));
266
    // Newly allocated (not deduplicated): schedule in entry and attach debug data.
267
0
    if (newlyAllocated)
268
0
    {
269
0
      scheduleInBlock(id, entry);
270
0
      if (debugInfo)
271
0
        debugInfo->setValueDebugData(id, std::move(_debugData));
272
0
    }
273
0
    return id;
274
0
  }
275
276
  /// Allocates a BuiltinCall at `_block` and, for `_numReturns >= 2`, the `_numReturns` matching
277
  /// Projections immediately after it in `m_insts` as a single contiguous cluster.
278
  InstId makeBuiltinCallWithProjections(
279
    BlockId const _block,
280
    BuiltinCall _payload,
281
    std::vector<InstId> _inputs,
282
    InstructionStore::NumReturnsSizeType const _numReturns,
283
    langutil::DebugData::ConstPtr _debugData = {}
284
  )
285
0
  {
286
0
    if (_numReturns < 2)
287
0
      return makeBuiltinCall(_block, std::move(_payload), std::move(_inputs), _debugData);
288
0
    InstId const producer = m_instructions.appendBuiltinCallWithProjections(
289
0
      _block, std::move(_payload), std::move(_inputs), _numReturns
290
0
    );
291
0
    scheduleInBlock(producer, _block);
292
0
    if (debugInfo && _debugData)
293
0
      debugInfo->setInstDebugData(producer, _debugData);
294
0
    for (InstructionStore::NumReturnsSizeType k = 0; k < _numReturns; ++k)
295
0
    {
296
0
      InstId const projId{producer.value + 1u + k};
297
0
      scheduleInBlock(projId, _block);
298
0
      if (debugInfo && _debugData)
299
0
        debugInfo->setValueDebugData(projId, _debugData);
300
0
    }
301
0
    return producer;
302
0
  }
303
304
  /// Allocates a user Call at `_block` and, for `_numReturns >= 2`, the `_numReturns` matching
305
  /// Projections immediately after it in `m_insts` as a single contiguous cluster.
306
  InstId makeCallWithProjections(
307
    BlockId const _block,
308
    Call _payload,
309
    std::vector<InstId> _inputs,
310
    InstructionStore::NumReturnsSizeType const _numReturns,
311
    langutil::DebugData::ConstPtr _debugData = {}
312
  )
313
0
  {
314
0
    if (_numReturns < 2)
315
0
      return makeCall(_block, std::move(_payload), std::move(_inputs), _debugData);
316
0
    yulAssert(_payload.numReturns == _numReturns, "Call payload's numReturns disagrees with caller's _numReturns");
317
0
    InstId const producer = m_instructions.appendCallWithProjections(
318
0
      _block, std::move(_payload), std::move(_inputs)
319
0
    );
320
0
    scheduleInBlock(producer, _block);
321
0
    if (debugInfo && _debugData)
322
0
      debugInfo->setInstDebugData(producer, _debugData);
323
0
    for (InstructionStore::NumReturnsSizeType k = 0; k < _numReturns; ++k)
324
0
    {
325
0
      InstId const projId{producer.value + 1u + k};
326
0
      scheduleInBlock(projId, _block);
327
0
      if (debugInfo && _debugData)
328
0
        debugInfo->setValueDebugData(projId, _debugData);
329
0
    }
330
0
    return producer;
331
0
  }
332
333
  InstId emitUpsilon(BlockId const _block, InstId const _value, InstId const _phi)
334
0
  {
335
0
    return scheduleInBlock(m_instructions.appendUpsilon(_block, _value, _phi), _block);
336
0
  }
337
338
  /// Flips _target's opcode to Identity forwarding _forward
339
  void replaceWithIdentity(InstId const _target, InstId const _forward)
340
0
  {
341
0
    m_instructions.replaceWithIdentity(_target, _forward);
342
0
  }
343
344
  /// Flips _target to Const carrying _value
345
  void replaceWithConst(InstId const _target, u256 _value)
346
0
  {
347
0
    m_instructions.replaceWithConst(_target, std::move(_value));
348
0
  }
349
350
  /// Flips _target to Nop. Caller must ensure _target produces no observable value
351
  void replaceWithNop(InstId const _target)
352
0
  {
353
0
    m_instructions.replaceWithNop(_target);
354
0
  }
355
356
  /// Tombstones an Inst, sweeping any trailing Projection cluster too
357
  void tombstone(InstId const _id)
358
0
  {
359
0
    m_instructions.tombstone(_id);
360
0
  }
361
362
  /// Returns the number of Projections trailing `_producer` in `m_insts` (0 for
363
  /// non-multi-return slots). Thin wrapper around `InstructionStore::numTrailingProjections`.
364
  InstructionStore::NumReturnsSizeType numTrailingProjections(InstId const _producer) const
365
0
  {
366
0
    return m_instructions.numTrailingProjections(_producer);
367
0
  }
368
369
  std::string toDot(
370
    bool _includeDiGraphDefinition=true,
371
    std::optional<size_t> _functionIndex=std::nullopt,
372
    LivenessAnalysis const* _liveness=nullptr,
373
    ControlFlowGraphs const* _controlFlow=nullptr
374
  ) const;
375
376
private:
377
  InstId scheduleInBlock(InstId const _id, BlockId const _block)
378
0
  {
379
0
    block(_block).instructions.push_back(_id);
380
0
    return _id;
381
0
  }
382
383
  std::vector<std::optional<BasicBlock>> m_blocks;
384
  // free list of blocks
385
  std::vector<BlockId> m_freeBlocks;
386
  InstructionStore m_instructions;
387
public:
388
  EVMDialect const& evmDialect;
389
  std::unique_ptr<DebugInfo> debugInfo;
390
  BlockId entry = BlockId{0};
391
  std::set<BlockId> exits;
392
  std::string name{};
393
  bool canContinue = true;
394
  std::vector<InstId> arguments;
395
  std::size_t numReturns = 0;
396
397
0
  bool isMainGraph() const { return name.empty(); }
398
399
  /// Iterates `_block.instructions`, invoking `_fn(instId, inst)` for every Phi.
400
  template<typename Callable>
401
  void forEachPhi(BasicBlock const& _block, Callable&& _fn)
402
  {
403
    forEachInstWhere(*this, _block, &Inst::isPhi, std::forward<Callable>(_fn));
404
  }
405
  template<typename Callable>
406
  void forEachPhi(BasicBlock const& _block, Callable&& _fn) const
407
0
  {
408
0
    forEachInstWhere(*this, _block, &Inst::isPhi, std::forward<Callable>(_fn));
409
0
  }
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachPhi<solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_0::operator()(solidity::yul::ssa::BlockId const&) const::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}>(solidity::yul::ssa::SSACFG::BasicBlock const&, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_0::operator()(solidity::yul::ssa::BlockId const&) const::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}&&) const
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachPhi<solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_3>(solidity::yul::ssa::SSACFG::BasicBlock const&, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_3&&) const
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachPhi<solidity::yul::ssa::LivenessAnalysis::runLoopTreeDfs(unsigned int)::$_0>(solidity::yul::ssa::SSACFG::BasicBlock const&, solidity::yul::ssa::LivenessAnalysis::runLoopTreeDfs(unsigned int)::$_0&&) const
Unexecuted instantiation: SSACFG.cpp:void solidity::yul::ssa::SSACFG::forEachPhi<(anonymous namespace)::SSACFGDotExporter::writeBlockLabel(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, solidity::yul::ssa::BlockId)::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}>(solidity::yul::ssa::SSACFG::BasicBlock const&, (anonymous namespace)::SSACFGDotExporter::writeBlockLabel(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, solidity::yul::ssa::BlockId)::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}&&) const
Unexecuted instantiation: JSONExporter.cpp:void solidity::yul::ssa::SSACFG::forEachPhi<(anonymous namespace)::toJson(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId, solidity::yul::ssa::LivenessAnalysis const*, solidity::yul::ssa::ControlFlowGraphs const&)::$_4>(solidity::yul::ssa::SSACFG::BasicBlock const&, (anonymous namespace)::toJson(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId, solidity::yul::ssa::LivenessAnalysis const*, solidity::yul::ssa::ControlFlowGraphs const&)::$_4&&) const
410
411
  /// Iterates `_block.instructions`, invoking `_fn(instId, inst)` for every Upsilon.
412
  template<typename Callable>
413
  void forEachUpsilon(BasicBlock const& _block, Callable&& _fn)
414
  {
415
    forEachInstWhere(*this, _block, &Inst::isUpsilon, std::forward<Callable>(_fn));
416
  }
417
  template<typename Callable>
418
  void forEachUpsilon(BasicBlock const& _block, Callable&& _fn) const
419
0
  {
420
0
    forEachInstWhere(*this, _block, &Inst::isUpsilon, std::forward<Callable>(_fn));
421
0
  }
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachUpsilon<solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_1>(solidity::yul::ssa::SSACFG::BasicBlock const&, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_1&&) const
Unexecuted instantiation: PhiInverse.cpp:void solidity::yul::ssa::SSACFG::forEachUpsilon<solidity::yul::ssa::PhiInverse::PhiInverse(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId const&, solidity::yul::ssa::BlockId const&)::$_0>(solidity::yul::ssa::SSACFG::BasicBlock const&, solidity::yul::ssa::PhiInverse::PhiInverse(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId const&, solidity::yul::ssa::BlockId const&)::$_0&&) const
Unexecuted instantiation: SSACFG.cpp:void solidity::yul::ssa::SSACFG::forEachUpsilon<(anonymous namespace)::formatPhi(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::InstId)::$_0>(solidity::yul::ssa::SSACFG::BasicBlock const&, (anonymous namespace)::formatPhi(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::InstId)::$_0&&) const
422
423
  /// Iterates `_block.instructions`, invoking `_fn(instId, inst)` for every operation. Trailing
424
  /// Projections are skipped here; access them via `projectionsOf(instId)` / `forEachOutput(instId, ...)`.
425
  template<std::invocable<InstId, Inst&> Callable>
426
  void forEachOperation(BasicBlock const& _block, Callable&& _fn)
427
  {
428
    forEachInstWhere(*this, _block, &Inst::isOperation, std::forward<Callable>(_fn));
429
  }
430
  template<std::invocable<InstId, Inst const&> Callable>
431
  void forEachOperation(BasicBlock const& _block, Callable&& _fn) const
432
0
  {
433
0
    forEachInstWhere(*this, _block, &Inst::isOperation, std::forward<Callable>(_fn));
434
0
  }
Unexecuted instantiation: SSACFG.cpp:_ZNK8solidity3yul3ssa6SSACFG16forEachOperationITkNSt3__19invocableINS1_6InstIdERKNS1_16InstructionStore4InstEEEZN12_GLOBAL__N_117SSACFGDotExporter15writeBlockLabelERNS4_13basic_ostreamIcNS4_11char_traitsIcEEEENS1_7BlockIdEEUlS6_SA_E0_EEvRKNS2_10BasicBlockEOT_
Unexecuted instantiation: StackLayoutGenerator.cpp:_ZNK8solidity3yul3ssa6SSACFG16forEachOperationITkNSt3__19invocableINS1_6InstIdERKNS1_16InstructionStore4InstEEEZNS1_20StackLayoutGenerator10visitBlockERKNS1_7BlockIdEE3$_0EEvRKNS2_10BasicBlockEOT_
Unexecuted instantiation: JSONExporter.cpp:_ZNK8solidity3yul3ssa6SSACFG16forEachOperationITkNSt3__19invocableINS1_6InstIdERKNS1_16InstructionStore4InstEEEZN12_GLOBAL__N_16toJsonERKS2_NS1_7BlockIdEPKNS1_16LivenessAnalysisERKNS1_17ControlFlowGraphsEE3$_3EEvRKNS2_10BasicBlockEOT_
435
436
  std::size_t numReturnsOf(InstId _op) const
437
0
  {
438
0
    auto const& i = inst(_op);
439
0
    switch (i.opcode)
440
0
    {
441
0
    case InstOpcode::Call:
442
0
      return callPayload(_op).numReturns;
443
0
    case InstOpcode::BuiltinCall:
444
0
      return evmDialect.builtin(builtinPayload(_op).builtin).numReturns;
445
0
    case InstOpcode::MemoryGuard:
446
0
      return 1;
447
0
    default:
448
0
      yulAssert(false, fmt::format("numReturnsOf called on non-operation inst {}", _op));
449
0
    }
450
0
  }
451
452
  /// View over the contiguous trailing Projections of `_producer` in `m_insts`. Empty for ops with <= 1 returns.
453
  /// Note this relies on the m_insts-contiguity invariant established by `make(Builtin)CallWithProjections`:
454
  /// projections live at `m_insts[_producer.value+1..+numReturns]`.
455
  InputRangeOf<InstId> auto projectionsOf(InstId const _producer) const
456
0
  {
457
0
    std::size_t const n = numReturnsOf(_producer);
458
0
    std::size_t const count = n >= 2 ? n : 0;
459
0
    std::size_t const firstIdx = static_cast<std::size_t>(_producer.value) + 1;
460
0
    std::size_t const lastIdx = firstIdx + count;
461
    // `appendInst` already caps `numInsts()` below `max(InstId::ValueType)`, so this also guarantees
462
    // the casts to `InstId::ValueType` below cannot wrap.
463
0
    yulAssert(
464
0
      lastIdx <= numInsts(),
465
0
      fmt::format(
466
0
        "Producer {} declares {} returns but only {} trailing m_insts slots are available",
467
0
        _producer, n, numInsts() - firstIdx
468
0
      )
469
0
    );
470
0
    for (std::size_t v = firstIdx; v < lastIdx; ++v)
471
0
    {
472
0
      auto const& trailing = inst(InstId{static_cast<InstId::ValueType>(v)});
473
0
      yulAssert(
474
0
        trailing.isProjection() && trailing.inputs.size() == 1 && trailing.inputs.front() == _producer,
475
0
        fmt::format("Trailing slot {} is not a Projection of producer {}", InstId{static_cast<InstId::ValueType>(v)}, _producer)
476
0
      );
477
0
    }
478
0
    return
479
0
      ranges::views::iota(static_cast<InstId::ValueType>(firstIdx), static_cast<InstId::ValueType>(lastIdx)) |
480
0
      ranges::views::transform(&toInstId);
481
0
  }
482
483
  /// View over the logical outputs of `_producer` in stack order: the producer itself if it has a single return,
484
  /// the trailing Projections if it has multiple, and an empty range otherwise.
485
  InputRangeOf<InstId> auto outputsOf(InstId const _producer) const
486
0
  {
487
0
    std::size_t const n = numReturnsOf(_producer);
488
0
    if (n >= 2)
489
0
      return projectionsOf(_producer);
490
0
    auto const firstIdx = static_cast<InstId::ValueType>(_producer.value);
491
0
    return
492
0
      ranges::views::iota(firstIdx, static_cast<InstId::ValueType>(firstIdx + n)) |
493
0
      ranges::views::transform(&toInstId);
494
0
  }
495
496
  /// Invokes `_fn(InstId)` once per logical output of `_producer` in stack order.
497
  template<std::invocable<InstId> Fn>
498
  void forEachOutput(InstId const _producer, Fn&& _fn) const
499
0
  {
500
0
    for (InstId const id: outputsOf(_producer))
501
0
      _fn(id);
502
0
  }
Unexecuted instantiation: StackLayoutGenerator.cpp:_ZNK8solidity3yul3ssa6SSACFG13forEachOutputITkNSt3__19invocableINS1_6InstIdEEEZZNS1_20StackLayoutGenerator10visitBlockERKNS1_7BlockIdEENK3$_0clES6_RKNS1_16InstructionStore4InstEEUlS6_E_EEvS6_OT_
Unexecuted instantiation: StackLayoutGenerator.cpp:_ZNK8solidity3yul3ssa6SSACFG13forEachOutputITkNSt3__19invocableINS1_6InstIdEEEZZNS1_20StackLayoutGenerator10visitBlockERKNS1_7BlockIdEENK3$_0clES6_RKNS1_16InstructionStore4InstEEUlS6_E0_EEvS6_OT_
Unexecuted instantiation: SpillSet.cpp:_ZNK8solidity3yul3ssa6SSACFG13forEachOutputITkNSt3__19invocableINS1_6InstIdEEEZN12_GLOBAL__N_119computeOperationOutERKS2_RKNS1_17SSACFGStackLayoutES6_E3$_0EEvS6_OT_
503
504
private:
505
0
  static InstId toInstId(InstId::ValueType const _v) { return InstId{_v}; }
506
507
  InstId makeBuiltinCall(
508
    BlockId const _block,
509
    BuiltinCall _payload,
510
    std::vector<InstId> _inputs,
511
    langutil::DebugData::ConstPtr _debugData = {}
512
  )
513
0
  {
514
0
    InstId const id = scheduleInBlock(
515
0
      m_instructions.appendBuiltinCall(_block, std::move(_payload), std::move(_inputs)),
516
0
      _block
517
0
    );
518
0
    if (debugInfo && _debugData)
519
0
      debugInfo->setInstDebugData(id, std::move(_debugData));
520
0
    return id;
521
0
  }
522
523
  InstId makeCall(
524
    BlockId const _block,
525
    Call _payload,
526
    std::vector<InstId> _inputs,
527
    langutil::DebugData::ConstPtr _debugData = {}
528
  )
529
0
  {
530
0
    InstId const id = scheduleInBlock(
531
0
      m_instructions.appendCall(_block, std::move(_payload), std::move(_inputs)),
532
0
      _block
533
0
    );
534
0
    if (debugInfo && _debugData)
535
0
      debugInfo->setInstDebugData(id, std::move(_debugData));
536
0
    return id;
537
0
  }
538
539
  /// Shared implementation for the public `forEach*` overloads; works for both
540
  /// `SSACFG&` and `SSACFG const&` since `_self.inst(id)` propagates const-ness.
541
  template<typename Self, typename Pred, typename Callable>
542
  static void forEachInstWhere(Self& _self, BasicBlock const& _block, Pred _pred, Callable&& _fn)
543
0
  {
544
0
    for (InstId const id: _block.instructions)
545
0
    {
546
0
      auto& inst = _self.inst(id);
547
0
      if (std::invoke(_pred, inst))
548
0
        _fn(id, inst);
549
0
    }
550
0
  }
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_1>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_1&&)
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_0::operator()(solidity::yul::ssa::BlockId const&) const::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_0::operator()(solidity::yul::ssa::BlockId const&) const::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}&&)
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_3>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runDagDfs()::$_3&&)
Unexecuted instantiation: LivenessAnalysis.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runLoopTreeDfs(unsigned int)::$_0>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::LivenessAnalysis::runLoopTreeDfs(unsigned int)::$_0&&)
Unexecuted instantiation: PhiInverse.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::PhiInverse::PhiInverse(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId const&, solidity::yul::ssa::BlockId const&)::$_0>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::PhiInverse::PhiInverse(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId const&, solidity::yul::ssa::BlockId const&)::$_0&&)
Unexecuted instantiation: SSACFG.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::SSACFGDotExporter::writeBlockLabel(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, solidity::yul::ssa::BlockId)::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::SSACFGDotExporter::writeBlockLabel(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, solidity::yul::ssa::BlockId)::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#1}&&)
Unexecuted instantiation: SSACFG.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::formatPhi(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::InstId)::$_0>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::formatPhi(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::InstId)::$_0&&)
Unexecuted instantiation: SSACFG.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::SSACFGDotExporter::writeBlockLabel(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, solidity::yul::ssa::BlockId)::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#2}>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::SSACFGDotExporter::writeBlockLabel(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, solidity::yul::ssa::BlockId)::{lambda(solidity::yul::ssa::InstId, solidity::yul::ssa::InstructionStore::Inst const&)#2}&&)
Unexecuted instantiation: StackLayoutGenerator.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::StackLayoutGenerator::visitBlock(solidity::yul::ssa::BlockId const&)::$_0>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, solidity::yul::ssa::StackLayoutGenerator::visitBlock(solidity::yul::ssa::BlockId const&)::$_0&&)
Unexecuted instantiation: JSONExporter.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::toJson(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId, solidity::yul::ssa::LivenessAnalysis const*, solidity::yul::ssa::ControlFlowGraphs const&)::$_4>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::toJson(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId, solidity::yul::ssa::LivenessAnalysis const*, solidity::yul::ssa::ControlFlowGraphs const&)::$_4&&)
Unexecuted instantiation: JSONExporter.cpp:void solidity::yul::ssa::SSACFG::forEachInstWhere<solidity::yul::ssa::SSACFG const, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::toJson(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId, solidity::yul::ssa::LivenessAnalysis const*, solidity::yul::ssa::ControlFlowGraphs const&)::$_3>(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::SSACFG::BasicBlock const&, bool (solidity::yul::ssa::InstructionStore::Inst::*)() noexcept const, (anonymous namespace)::toJson(solidity::yul::ssa::SSACFG const&, solidity::yul::ssa::BlockId, solidity::yul::ssa::LivenessAnalysis const*, solidity::yul::ssa::ControlFlowGraphs const&)::$_3&&)
551
};
552
553
}