Coverage Report

Created: 2025-06-24 07:59

/src/solidity/libyul/backends/evm/ControlFlowGraph.h
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
 * Control flow graph and stack layout structures used during code generation.
20
 */
21
22
#pragma once
23
24
#include <libyul/AST.h>
25
#include <libyul/AsmAnalysisInfo.h>
26
#include <libyul/Dialect.h>
27
#include <libyul/Exceptions.h>
28
#include <libyul/Scope.h>
29
30
#include <libsolutil/Numeric.h>
31
32
#include <functional>
33
#include <list>
34
#include <vector>
35
36
namespace solidity::yul
37
{
38
39
/// The following structs describe different kinds of stack slots.
40
/// Each stack slot is equality- and less-than-comparable and
41
/// specifies an attribute ``canBeFreelyGenerated`` that is true,
42
/// if a slot of this kind always has a known value at compile time and
43
/// therefore can safely be removed from the stack at any time and then
44
/// regenerated later.
45
46
/// The label pushed as return label before a function call, i.e. the label the call is supposed to return to.
47
struct FunctionCallReturnLabelSlot
48
{
49
  std::reference_wrapper<yul::FunctionCall const> call;
50
112M
  bool operator==(FunctionCallReturnLabelSlot const& _rhs) const { return &call.get() == &_rhs.call.get(); }
51
679M
  bool operator<(FunctionCallReturnLabelSlot const& _rhs) const { return &call.get() < &_rhs.call.get(); }
52
  static constexpr bool canBeFreelyGenerated = true;
53
};
54
/// The return jump target of a function while generating the code of the function body.
55
/// I.e. the caller of a function pushes a ``FunctionCallReturnLabelSlot`` (see above) before jumping to the function and
56
/// this very slot is viewed as ``FunctionReturnLabelSlot`` inside the function body and jumped to when returning from
57
/// the function.
58
struct FunctionReturnLabelSlot
59
{
60
  std::reference_wrapper<Scope::Function const> function;
61
  bool operator==(FunctionReturnLabelSlot const& _rhs) const
62
54.8M
  {
63
    // There can never be return label slots of different functions on stack simultaneously.
64
54.8M
    yulAssert(&function.get() == &_rhs.function.get(), "");
65
54.8M
    return true;
66
54.8M
  }
67
  bool operator<(FunctionReturnLabelSlot const& _rhs) const
68
0
  {
69
    // There can never be return label slots of different functions on stack simultaneously.
70
0
    yulAssert(&function.get() == &_rhs.function.get(), "");
71
0
    return false;
72
0
  }
73
  static constexpr bool canBeFreelyGenerated = false;
74
};
75
/// A slot containing the current value of a particular variable.
76
struct VariableSlot
77
{
78
  std::reference_wrapper<Scope::Variable const> variable;
79
  langutil::DebugData::ConstPtr debugData{};
80
2.42G
  bool operator==(VariableSlot const& _rhs) const { return &variable.get() == &_rhs.variable.get(); }
81
14.1G
  bool operator<(VariableSlot const& _rhs) const { return &variable.get() < &_rhs.variable.get(); }
82
  static constexpr bool canBeFreelyGenerated = false;
83
};
84
/// A slot containing a literal value.
85
struct LiteralSlot
86
{
87
  u256 value;
88
  langutil::DebugData::ConstPtr debugData{};
89
1.63G
  bool operator==(LiteralSlot const& _rhs) const { return value == _rhs.value; }
90
6.35G
  bool operator<(LiteralSlot const& _rhs) const { return value < _rhs.value; }
91
  static constexpr bool canBeFreelyGenerated = true;
92
};
93
/// A slot containing the index-th return value of a previous call.
94
struct TemporarySlot
95
{
96
  /// The call that returned this slot.
97
  std::reference_wrapper<yul::FunctionCall const> call;
98
  /// Specifies to which of the values returned by the call this slot refers.
99
  /// index == 0 refers to the slot deepest in the stack after the call.
100
  size_t index = 0;
101
59.7M
  bool operator==(TemporarySlot const& _rhs) const { return &call.get() == &_rhs.call.get() && index == _rhs.index; }
102
157M
  bool operator<(TemporarySlot const& _rhs) const { return std::make_pair(&call.get(), index) < std::make_pair(&_rhs.call.get(), _rhs.index); }
103
  static constexpr bool canBeFreelyGenerated = false;
104
};
105
/// A slot containing an arbitrary value that is always eventually popped and never used.
106
/// Used to maintain stack balance on control flow joins.
107
struct JunkSlot
108
{
109
42.5M
  bool operator==(JunkSlot const&) const { return true; }
110
0
  bool operator<(JunkSlot const&) const { return false; }
111
  static constexpr bool canBeFreelyGenerated = true;
112
};
113
using StackSlot = std::variant<FunctionCallReturnLabelSlot, FunctionReturnLabelSlot, VariableSlot, LiteralSlot, TemporarySlot, JunkSlot>;
114
/// The stack top is usually the last element of the vector.
115
using Stack = std::vector<StackSlot>;
116
117
/// @returns true if @a _slot can be generated on the stack at any time.
118
inline bool canBeFreelyGenerated(StackSlot const& _slot)
119
145M
{
120
145M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
auto solidity::yul::canBeFreelyGenerated(std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> const&)::{lambda(auto:1 const&)#1}::operator()<solidity::yul::FunctionCallReturnLabelSlot>(solidity::yul::FunctionCallReturnLabelSlot const&) const
Line
Count
Source
120
6.17M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
auto solidity::yul::canBeFreelyGenerated(std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> const&)::{lambda(auto:1 const&)#1}::operator()<solidity::yul::FunctionReturnLabelSlot>(solidity::yul::FunctionReturnLabelSlot const&) const
Line
Count
Source
120
2.64M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
auto solidity::yul::canBeFreelyGenerated(std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> const&)::{lambda(auto:1 const&)#1}::operator()<solidity::yul::VariableSlot>(solidity::yul::VariableSlot const&) const
Line
Count
Source
120
84.6M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
auto solidity::yul::canBeFreelyGenerated(std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> const&)::{lambda(auto:1 const&)#1}::operator()<solidity::yul::LiteralSlot>(solidity::yul::LiteralSlot const&) const
Line
Count
Source
120
37.9M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
auto solidity::yul::canBeFreelyGenerated(std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> const&)::{lambda(auto:1 const&)#1}::operator()<solidity::yul::TemporarySlot>(solidity::yul::TemporarySlot const&) const
Line
Count
Source
120
11.3M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
auto solidity::yul::canBeFreelyGenerated(std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> const&)::{lambda(auto:1 const&)#1}::operator()<solidity::yul::JunkSlot>(solidity::yul::JunkSlot const&) const
Line
Count
Source
120
2.53M
  return std::visit([](auto const& _typedSlot) { return std::decay_t<decltype(_typedSlot)>::canBeFreelyGenerated; }, _slot);
121
145M
}
122
123
/// Control flow graph consisting of ``CFG::BasicBlock``s connected by control flow.
124
struct CFG
125
{
126
206k
  explicit CFG() {}
127
  CFG(CFG const&) = delete;
128
  CFG(CFG&&) = delete;
129
  CFG& operator=(CFG const&) = delete;
130
  CFG& operator=(CFG&&) = delete;
131
206k
  ~CFG() = default;
132
133
  struct BuiltinCall
134
  {
135
    langutil::DebugData::ConstPtr debugData;
136
    std::reference_wrapper<BuiltinFunction const> builtin;
137
    std::reference_wrapper<yul::FunctionCall const> functionCall;
138
    /// Number of proper arguments with a position on the stack, excluding literal arguments.
139
    /// Literal arguments (like the literal string in ``datasize``) do not have a location on the stack,
140
    /// but are handled internally by the builtin's code generation function.
141
    size_t arguments = 0;
142
  };
143
  struct FunctionCall
144
  {
145
    langutil::DebugData::ConstPtr debugData;
146
    std::reference_wrapper<Scope::Function const> function;
147
    std::reference_wrapper<yul::FunctionCall const> functionCall;
148
    /// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving
149
    /// more intermediate function calls) that leads back to this very call.
150
    bool recursive = false;
151
    /// True, if the call can return.
152
    bool canContinue = true;
153
  };
154
  struct Assignment
155
  {
156
    langutil::DebugData::ConstPtr debugData;
157
    /// The variables being assigned to also occur as ``output`` in the ``Operation`` containing
158
    /// the assignment, but are also stored here for convenience.
159
    std::vector<VariableSlot> variables;
160
  };
161
162
  struct Operation
163
  {
164
    /// Stack slots this operation expects at the top of the stack and consumes.
165
    Stack input;
166
    /// Stack slots this operation leaves on the stack as output.
167
    Stack output;
168
    std::variant<FunctionCall, BuiltinCall, Assignment> operation;
169
  };
170
171
  struct FunctionInfo;
172
  /// A basic control flow block containing ``Operation``s acting on the stack.
173
  /// Maintains a list of entry blocks and a typed exit.
174
  struct BasicBlock
175
  {
176
    struct MainExit {};
177
    struct ConditionalJump
178
    {
179
      langutil::DebugData::ConstPtr debugData;
180
      StackSlot condition;
181
      BasicBlock* nonZero = nullptr;
182
      BasicBlock* zero = nullptr;
183
    };
184
    struct Jump
185
    {
186
      langutil::DebugData::ConstPtr debugData;
187
      BasicBlock* target = nullptr;
188
      /// The only backwards jumps are jumps from loop post to loop condition.
189
      bool backwards = false;
190
    };
191
    struct FunctionReturn
192
    {
193
      langutil::DebugData::ConstPtr debugData;
194
      CFG::FunctionInfo* info = nullptr;
195
    };
196
    struct Terminated {};
197
    langutil::DebugData::ConstPtr debugData;
198
    std::vector<BasicBlock*> entries;
199
    std::vector<Operation> operations;
200
    /// True, if the block is the beginning of a disconnected subgraph. That is, if no block that is reachable
201
    /// from this block is an ancestor of this block. In other words, this is true, if this block is the target
202
    /// of a cut-edge/bridge in the CFG or if the block itself terminates.
203
    bool isStartOfSubGraph = false;
204
    /// True, if there is a path from this block to a function return.
205
    bool needsCleanStack = false;
206
    /// If the block starts a sub-graph and does not lead to a function return, we are free to add junk to it.
207
3.03M
    bool allowsJunk() const { return isStartOfSubGraph && !needsCleanStack; }
208
    std::variant<MainExit, Jump, ConditionalJump, FunctionReturn, Terminated> exit = MainExit{};
209
  };
210
211
  struct FunctionInfo
212
  {
213
    langutil::DebugData::ConstPtr debugData;
214
    Scope::Function const& function;
215
    FunctionDefinition const& functionDefinition;
216
    BasicBlock* entry = nullptr;
217
    std::vector<VariableSlot> parameters;
218
    std::vector<VariableSlot> returnVariables;
219
    std::vector<BasicBlock*> exits;
220
    bool canContinue = true;
221
  };
222
223
  /// The main entry point, i.e. the start of the outermost Yul block.
224
  BasicBlock* entry = nullptr;
225
  /// Subgraphs for functions.
226
  std::map<Scope::Function const*, FunctionInfo> functionInfo;
227
  /// List of functions in order of declaration.
228
  std::list<Scope::Function const*> functions;
229
230
  /// Container for blocks for explicit ownership.
231
  std::list<BasicBlock> blocks;
232
  /// Container for generated variables for explicit ownership.
233
  /// Ghost variables are generated to store switch conditions when transforming the control flow
234
  /// of a switch to a sequence of conditional jumps.
235
  std::list<Scope::Variable> ghostVariables;
236
  /// Container for generated calls for explicit ownership.
237
  /// Ghost calls are used for the equality comparisons of the switch condition ghost variable with
238
  /// the switch case literals when transforming the control flow of a switch to a sequence of conditional jumps.
239
  std::list<yul::FunctionCall> ghostCalls;
240
241
  BasicBlock& makeBlock(langutil::DebugData::ConstPtr _debugData)
242
3.66M
  {
243
3.66M
    return blocks.emplace_back(BasicBlock{std::move(_debugData), {}, {}});
244
3.66M
  }
245
};
246
247
}