/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 | | } |