/src/solidity/libyul/optimiser/DataFlowAnalyzer.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 | | * Base class to perform data flow analysis during AST walks. |
20 | | * Tracks assignments and is used as base class for both Rematerialiser and |
21 | | * Common Subexpression Eliminator. |
22 | | */ |
23 | | |
24 | | #pragma once |
25 | | |
26 | | #include <libyul/optimiser/ASTWalker.h> |
27 | | #include <libyul/optimiser/KnowledgeBase.h> |
28 | | #include <libyul/YulName.h> |
29 | | #include <libyul/AST.h> // Needed for m_zero below. |
30 | | #include <libyul/SideEffects.h> |
31 | | |
32 | | #include <libsolutil/Numeric.h> |
33 | | #include <libsolutil/Common.h> |
34 | | |
35 | | #include <map> |
36 | | #include <set> |
37 | | |
38 | | namespace solidity::yul |
39 | | { |
40 | | class Dialect; |
41 | | struct SideEffects; |
42 | | class KnowledgeBase; |
43 | | |
44 | | /// Value assigned to a variable. |
45 | | struct AssignedValue |
46 | | { |
47 | | Expression const* value{nullptr}; |
48 | | /// Loop nesting depth of the definition of the variable. |
49 | | size_t loopDepth{0}; |
50 | | }; |
51 | | |
52 | | /** |
53 | | * Base class to perform data flow analysis during AST walks. |
54 | | * Tracks assignments and is used as base class for both Rematerialiser and |
55 | | * Common Subexpression Eliminator. |
56 | | * |
57 | | * A special zero constant expression is used for the default value of variables. |
58 | | * |
59 | | * The class also tracks contents in storage and memory. Both keys and values |
60 | | * are names of variables. Whenever such a variable is re-assigned, the knowledge |
61 | | * is cleared. |
62 | | * |
63 | | * For elementary statements, we check if it is an SSTORE(x, y) / MSTORE(x, y) |
64 | | * If yes, visit the statement. Then record that fact and clear all storage slots t |
65 | | * where we cannot prove x != t or y == m_storage[t] using the current values of the variables x and t. |
66 | | * Otherwise, determine if the statement invalidates storage/memory. If yes, clear all knowledge |
67 | | * about storage/memory before visiting the statement. Then visit the statement. |
68 | | * |
69 | | * For forward-joining control flow, storage/memory information from the branches is combined. |
70 | | * If the keys or values are different or non-existent in one branch, the key is deleted. |
71 | | * This works also for memory (where addresses overlap) because one branch is always an |
72 | | * older version of the other and thus overlapping contents would have been deleted already |
73 | | * at the point of assignment. |
74 | | * |
75 | | * The DataFlowAnalyzer currently does not deal with the ``leave`` statement. This is because |
76 | | * it only matters at the end of a function body, which is a point in the code a derived class |
77 | | * can not easily deal with. |
78 | | * |
79 | | * Prerequisite: Disambiguator, ForLoopInitRewriter. |
80 | | */ |
81 | | class DataFlowAnalyzer: public ASTModifier |
82 | | { |
83 | | public: |
84 | | enum class MemoryAndStorage { Analyze, Ignore }; |
85 | | /// @param _functionSideEffects |
86 | | /// Side-effects of user-defined functions. Worst-case side-effects are assumed |
87 | | /// if this is not provided or the function is not found. |
88 | | /// The parameter is mostly used to determine movability of expressions. |
89 | | explicit DataFlowAnalyzer( |
90 | | Dialect const& _dialect, |
91 | | MemoryAndStorage _analyzeStores, |
92 | | std::map<FunctionHandle, SideEffects> _functionSideEffects = {} |
93 | | ); |
94 | | |
95 | | using ASTModifier::operator(); |
96 | | void operator()(ExpressionStatement& _statement) override; |
97 | | void operator()(Assignment& _assignment) override; |
98 | | void operator()(VariableDeclaration& _varDecl) override; |
99 | | void operator()(If& _if) override; |
100 | | void operator()(Switch& _switch) override; |
101 | | void operator()(FunctionDefinition&) override; |
102 | | void operator()(ForLoop&) override; |
103 | | void operator()(Block& _block) override; |
104 | | |
105 | | /// @returns the current value of the given variable, if known - always movable. |
106 | 228M | AssignedValue const* variableValue(YulName _variable) const { return util::valueOrNullptr(m_state.value, _variable); } |
107 | 458k | std::vector<YulName> const* sortedReferences(YulName _variable) const { return util::valueOrNullptr(m_state.sortedReferences, _variable); } |
108 | 0 | std::map<YulName, AssignedValue> const& allValues() const { return m_state.value; } |
109 | | std::optional<YulName> storageValue(YulName _key) const; |
110 | | std::optional<YulName> memoryValue(YulName _key) const; |
111 | | std::optional<YulName> keccakValue(YulName _start, YulName _length) const; |
112 | | |
113 | | protected: |
114 | | /// Registers the assignment. |
115 | | void handleAssignment(std::set<YulName> const& _names, Expression* _value, bool _isDeclaration); |
116 | | |
117 | | /// Creates a new inner scope. |
118 | | void pushScope(bool _functionScope); |
119 | | |
120 | | /// Removes the innermost scope and clears all variables in it. |
121 | | void popScope(); |
122 | | |
123 | | /// Clears information about the values assigned to the given variables, |
124 | | /// for example at points where control flow is merged. |
125 | | void clearValues(std::set<YulName> const& _variablesToClear); |
126 | | |
127 | | virtual void assignValue(YulName _variable, Expression const* _value); |
128 | | |
129 | | /// Clears knowledge about storage or memory if they may be modified inside the block. |
130 | | void clearKnowledgeIfInvalidated(Block const& _block); |
131 | | |
132 | | /// Clears knowledge about storage or memory if they may be modified inside the expression. |
133 | | void clearKnowledgeIfInvalidated(Expression const& _expression); |
134 | | |
135 | | /// Returns true iff the variable is in scope. |
136 | | bool inScope(YulName _variableName) const; |
137 | | |
138 | | /// Returns the literal value of the identifier, if it exists. |
139 | | std::optional<u256> valueOfIdentifier(YulName const& _name) const; |
140 | | |
141 | | enum class StoreLoadLocation { |
142 | | Memory = 0, |
143 | | Storage = 1, |
144 | | Last = Storage |
145 | | }; |
146 | | |
147 | | /// Checks if the statement is sstore(a, b) / mstore(a, b) |
148 | | /// where a and b are variables and returns these variables in that case. |
149 | | std::optional<std::pair<YulName, YulName>> isSimpleStore( |
150 | | StoreLoadLocation _location, |
151 | | ExpressionStatement const& _statement |
152 | | ) const; |
153 | | |
154 | | /// Checks if the expression is sload(a) / mload(a) |
155 | | /// where a is a variable and returns the variable in that case. |
156 | | std::optional<YulName> isSimpleLoad( |
157 | | StoreLoadLocation _location, |
158 | | Expression const& _expression |
159 | | ) const; |
160 | | |
161 | | /// Checks if the expression is keccak256(s, l) |
162 | | /// where s and l are variables and returns these variables in that case. |
163 | | std::optional<std::pair<YulName, YulName>> isKeccak(Expression const& _expression) const; |
164 | | |
165 | | Dialect const& m_dialect; |
166 | | /// Side-effects of user-defined functions. Worst-case side-effects are assumed |
167 | | /// if this is not provided or the function is not found. |
168 | | std::map<FunctionHandle, SideEffects> m_functionSideEffects; |
169 | | |
170 | | private: |
171 | | struct Environment |
172 | | { |
173 | | std::unordered_map<YulName, YulName> storage; |
174 | | std::unordered_map<YulName, YulName> memory; |
175 | | /// If keccak[s, l] = y then y := keccak256(s, l) occurs in the code. |
176 | | std::map<std::pair<YulName, YulName>, YulName> keccak; |
177 | | }; |
178 | | struct State |
179 | | { |
180 | | /// Current values of variables, always movable. |
181 | | std::map<YulName, AssignedValue> value; |
182 | | /// m_references[a].contains(b) <=> the current expression assigned to a references b |
183 | | /// The mapped vectors _must always_ be sorted |
184 | | std::unordered_map<YulName, std::vector<YulName>> sortedReferences; |
185 | | |
186 | | Environment environment; |
187 | | }; |
188 | | |
189 | | /// Joins knowledge about storage and memory with an older point in the control-flow. |
190 | | /// This only works if the current state is a direct successor of the older point, |
191 | | /// i.e. `_olderState.storage` and `_olderState.memory` cannot have additional changes. |
192 | | /// Does nothing if memory and storage analysis is disabled / ignored. |
193 | | void joinKnowledge(Environment const& _olderEnvironment); |
194 | | |
195 | | static void joinKnowledgeHelper( |
196 | | std::unordered_map<YulName, YulName>& _thisData, |
197 | | std::unordered_map<YulName, YulName> const& _olderData |
198 | | ); |
199 | | |
200 | | State m_state; |
201 | | |
202 | | protected: |
203 | | KnowledgeBase m_knowledgeBase; |
204 | | |
205 | | /// If true, analyzes memory and storage content via mload/mstore and sload/sstore. |
206 | | bool m_analyzeStores = true; |
207 | | std::optional<BuiltinHandle> m_storeFunctionName[static_cast<unsigned>(StoreLoadLocation::Last) + 1]; |
208 | | std::optional<BuiltinHandle> m_loadFunctionName[static_cast<unsigned>(StoreLoadLocation::Last) + 1]; |
209 | | |
210 | | /// Current nesting depth of loops. |
211 | | size_t m_loopDepth{0}; |
212 | | |
213 | | struct Scope |
214 | | { |
215 | 32.2M | explicit Scope(bool _isFunction): isFunction(_isFunction) {} |
216 | | std::set<YulName> variables; |
217 | | bool isFunction; |
218 | | }; |
219 | | /// Special expression whose address will be used in m_value. |
220 | | /// YulName does not need to be reset because DataFlowAnalyzer is short-lived. |
221 | | Expression const m_zero{Literal{{}, LiteralKind::Number, LiteralValue{0, std::nullopt}}}; |
222 | | /// List of scopes. |
223 | | std::vector<Scope> m_variableScopes; |
224 | | }; |
225 | | |
226 | | } |