/src/solidity/libyul/optimiser/Semantics.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 | | * Specific AST walkers that collect semantical facts. |
20 | | */ |
21 | | |
22 | | #pragma once |
23 | | |
24 | | #include <libyul/AST.h> |
25 | | #include <libyul/Object.h> |
26 | | #include <libyul/SideEffects.h> |
27 | | #include <libyul/optimiser/ASTWalker.h> |
28 | | #include <libyul/optimiser/CallGraphGenerator.h> |
29 | | |
30 | | #include <set> |
31 | | |
32 | | namespace solidity::yul |
33 | | { |
34 | | class Dialect; |
35 | | |
36 | | /** |
37 | | * Specific AST walker that determines side-effect free-ness and movability of code. |
38 | | * Enters into function definitions. |
39 | | */ |
40 | | class SideEffectsCollector: public ASTWalker |
41 | | { |
42 | | public: |
43 | | explicit SideEffectsCollector( |
44 | | Dialect const& _dialect, |
45 | | std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr |
46 | 427M | ): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {} |
47 | | SideEffectsCollector( |
48 | | Dialect const& _dialect, |
49 | | Expression const& _expression, |
50 | | std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr |
51 | | ); |
52 | | SideEffectsCollector(Dialect const& _dialect, Statement const& _statement); |
53 | | SideEffectsCollector( |
54 | | Dialect const& _dialect, |
55 | | Block const& _ast, |
56 | | std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr |
57 | | ); |
58 | | SideEffectsCollector( |
59 | | Dialect const& _dialect, |
60 | | ForLoop const& _ast, |
61 | | std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr |
62 | | ); |
63 | | |
64 | | using ASTWalker::operator(); |
65 | | void operator()(FunctionCall const& _functionCall) override; |
66 | | |
67 | 263M | bool movable() const { return m_sideEffects.movable; } |
68 | | |
69 | | bool movableRelativeTo(SideEffects const& _other, bool _codeContainsMSize) |
70 | 2.93M | { |
71 | 2.93M | if (!m_sideEffects.cannotLoop) |
72 | 1.88k | return false; |
73 | | |
74 | 2.93M | if (m_sideEffects.movable) |
75 | 2.65M | return true; |
76 | | |
77 | 279k | if ( |
78 | 279k | !m_sideEffects.movableApartFromEffects || |
79 | 194k | m_sideEffects.storage == SideEffects::Write || |
80 | 194k | m_sideEffects.otherState == SideEffects::Write || |
81 | 194k | m_sideEffects.memory == SideEffects::Write || |
82 | 194k | m_sideEffects.transientStorage == SideEffects::Write |
83 | 279k | ) |
84 | 85.1k | return false; |
85 | | |
86 | 194k | if (m_sideEffects.otherState == SideEffects::Read) |
87 | 97.7k | if (_other.otherState == SideEffects::Write) |
88 | 5.43k | return false; |
89 | | |
90 | 188k | if (m_sideEffects.storage == SideEffects::Read) |
91 | 53.0k | if (_other.storage == SideEffects::Write) |
92 | 45.2k | return false; |
93 | | |
94 | 143k | if (m_sideEffects.memory == SideEffects::Read) |
95 | 34.6k | if (_codeContainsMSize || _other.memory == SideEffects::Write) |
96 | 24.7k | return false; |
97 | | |
98 | 118k | if (m_sideEffects.transientStorage == SideEffects::Read) |
99 | 8.82k | if (_other.transientStorage == SideEffects::Write) |
100 | 4.96k | return false; |
101 | | |
102 | 114k | return true; |
103 | 118k | } |
104 | | |
105 | | bool canBeRemoved(bool _allowMSizeModification = false) const |
106 | 34.0M | { |
107 | 34.0M | if (_allowMSizeModification) |
108 | 30.2M | return m_sideEffects.canBeRemovedIfNoMSize; |
109 | 3.76M | else |
110 | 3.76M | return m_sideEffects.canBeRemoved; |
111 | 34.0M | } |
112 | 0 | bool cannotLoop() const { return m_sideEffects.cannotLoop; } |
113 | 35.5M | bool invalidatesStorage() const { return m_sideEffects.storage == SideEffects::Write; } |
114 | 35.5M | bool invalidatesMemory() const { return m_sideEffects.memory == SideEffects::Write; } |
115 | | |
116 | 722k | SideEffects sideEffects() { return m_sideEffects; } |
117 | | |
118 | | private: |
119 | | Dialect const& m_dialect; |
120 | | std::map<FunctionHandle, SideEffects> const* m_functionSideEffects = nullptr; |
121 | | SideEffects m_sideEffects; |
122 | | }; |
123 | | |
124 | | /** |
125 | | * This class can be used to determine the side-effects of user-defined functions. |
126 | | * |
127 | | * It is given a dialect and a mapping that represents the direct calls from user-defined |
128 | | * functions to other user-defined functions and built-in functions. |
129 | | */ |
130 | | class SideEffectsPropagator |
131 | | { |
132 | | public: |
133 | | static std::map<FunctionHandle, SideEffects> sideEffects( |
134 | | Dialect const& _dialect, |
135 | | CallGraph const& _directCallGraph |
136 | | ); |
137 | | }; |
138 | | |
139 | | /** |
140 | | * Class that can be used to find out if certain code contains the MSize instruction |
141 | | * or a verbatim bytecode builtin (which is always assumed that it could contain MSize). |
142 | | * |
143 | | * Note that this is a purely syntactic property meaning that even if this is false, |
144 | | * the code can still contain calls to functions that contain the msize instruction. |
145 | | * |
146 | | * The only safe way to determine this is by passing the full AST. |
147 | | */ |
148 | | class MSizeFinder: public ASTWalker |
149 | | { |
150 | | public: |
151 | | static bool containsMSize(Dialect const& _dialect, Block const& _ast); |
152 | | static bool containsMSize(Object const& _object); |
153 | | |
154 | | using ASTWalker::operator(); |
155 | | void operator()(FunctionCall const& _funCall) override; |
156 | | |
157 | | private: |
158 | 2.14M | MSizeFinder(Dialect const& _dialect): m_dialect(_dialect) {} |
159 | | Dialect const& m_dialect; |
160 | | bool m_msizeFound = false; |
161 | | }; |
162 | | |
163 | | /** |
164 | | * Class that can be used to find out if the given function contains the ``leave`` statement. |
165 | | * |
166 | | * Returns true even in the case where the function definition contains another function definition |
167 | | * that contains the leave statement. |
168 | | */ |
169 | | class LeaveFinder: public ASTWalker |
170 | | { |
171 | | public: |
172 | | static bool containsLeave(FunctionDefinition const& _fun) |
173 | 582k | { |
174 | 582k | LeaveFinder f; |
175 | 582k | f(_fun); |
176 | 582k | return f.m_leaveFound; |
177 | 582k | } |
178 | | |
179 | | using ASTWalker::operator(); |
180 | 76.9k | void operator()(Leave const&) override { m_leaveFound = true; } |
181 | | |
182 | | private: |
183 | 582k | LeaveFinder() = default; |
184 | | |
185 | | bool m_leaveFound = false; |
186 | | }; |
187 | | |
188 | | /** |
189 | | * Specific AST walker that determines whether an expression is movable |
190 | | * and collects the referenced variables. |
191 | | * Can only be used on expressions. |
192 | | */ |
193 | | class MovableChecker: public SideEffectsCollector |
194 | | { |
195 | | public: |
196 | | explicit MovableChecker( |
197 | | Dialect const& _dialect, |
198 | | std::map<FunctionHandle, SideEffects> const* _functionSideEffects = nullptr |
199 | 350M | ): SideEffectsCollector(_dialect, _functionSideEffects) {} |
200 | | MovableChecker(Dialect const& _dialect, Expression const& _expression); |
201 | | |
202 | | void operator()(Identifier const& _identifier) override; |
203 | | |
204 | | /// Disallow visiting anything apart from Expressions (this throws). |
205 | | void visit(Statement const&) override; |
206 | | using ASTWalker::visit; |
207 | | |
208 | 850M | std::set<YulName> const& referencedVariables() const { return m_variableReferences; } |
209 | | |
210 | | private: |
211 | | /// Which variables the current expression references. |
212 | | std::set<YulName> m_variableReferences; |
213 | | }; |
214 | | |
215 | | struct ControlFlowSideEffects; |
216 | | |
217 | | /** |
218 | | * Helper class to find "irregular" control flow. |
219 | | * This includes termination, break, continue and leave. |
220 | | * In general, it is applied only to "simple" statements. The control-flow |
221 | | * of loops, switches and if statements is always "FlowOut" with the assumption |
222 | | * that the caller will descend into them. |
223 | | */ |
224 | | class TerminationFinder |
225 | | { |
226 | | public: |
227 | | /// "Terminate" here means that there is no continuing control-flow. |
228 | | /// If this is applied to a function that can revert or stop, but can also |
229 | | /// exit regularly, the property is set to "FlowOut". |
230 | | enum class ControlFlow { FlowOut, Break, Continue, Terminate, Leave }; |
231 | | |
232 | | TerminationFinder( |
233 | | Dialect const& _dialect, |
234 | | std::map<YulName, ControlFlowSideEffects> const* _functionSideEffects = nullptr |
235 | 8.73M | ): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {} |
236 | | |
237 | | /// @returns the index of the first statement in the provided sequence |
238 | | /// that is an unconditional ``break``, ``continue``, ``leave`` or a |
239 | | /// call to a terminating function. |
240 | | /// If control flow can continue at the end of the list, |
241 | | /// returns `FlowOut` and ``size_t(-1)``. |
242 | | /// The function might return ``FlowOut`` even though control |
243 | | /// flow cannot actually continue. |
244 | | std::pair<ControlFlow, size_t> firstUnconditionalControlFlowChange( |
245 | | std::vector<Statement> const& _statements |
246 | | ); |
247 | | |
248 | | /// @returns the control flow type of the given statement. |
249 | | /// This function could return FlowOut even if control flow never continues. |
250 | | ControlFlow controlFlowKind(Statement const& _statement); |
251 | | |
252 | | /// @returns true if the expression contains a |
253 | | /// call to a terminating function, i.e. a function that does not have |
254 | | /// a regular "flow out" control-flow (it might also be recursive). |
255 | | bool containsNonContinuingFunctionCall(Expression const& _expr); |
256 | | |
257 | | private: |
258 | | Dialect const& m_dialect; |
259 | | std::map<YulName, ControlFlowSideEffects> const* m_functionSideEffects; |
260 | | }; |
261 | | |
262 | | } |