/src/solidity/libevmasm/CommonSubexpressionEliminator.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 | | * @file CommonSubexpressionEliminator.h |
20 | | * @author Christian <c@ethdev.com> |
21 | | * @date 2015 |
22 | | * Optimizer step for common subexpression elimination and stack reorganisation. |
23 | | */ |
24 | | |
25 | | #pragma once |
26 | | |
27 | | #include <map> |
28 | | #include <ostream> |
29 | | #include <set> |
30 | | #include <tuple> |
31 | | #include <unordered_map> |
32 | | #include <vector> |
33 | | #include <libsolutil/CommonIO.h> |
34 | | #include <libsolutil/Exceptions.h> |
35 | | #include <libevmasm/ExpressionClasses.h> |
36 | | #include <libevmasm/SemanticInformation.h> |
37 | | #include <libevmasm/KnownState.h> |
38 | | |
39 | | namespace langutil |
40 | | { |
41 | | struct SourceLocation; |
42 | | } |
43 | | |
44 | | namespace solidity::evmasm |
45 | | { |
46 | | |
47 | | class AssemblyItem; |
48 | | using AssemblyItems = std::vector<AssemblyItem>; |
49 | | |
50 | | /** |
51 | | * Optimizer step that performs common subexpression elimination and stack reorganisation, |
52 | | * i.e. it tries to infer equality among expressions and compute the values of two expressions |
53 | | * known to be equal only once. |
54 | | * |
55 | | * The general workings are that for each assembly item that is fed into the eliminator, an |
56 | | * equivalence class is derived from the operation and the equivalence class of its arguments. |
57 | | * DUPi, SWAPi and some arithmetic instructions are used to infer equivalences while these |
58 | | * classes are determined. |
59 | | * |
60 | | * When the list of optimized items is requested, they are generated in a bottom-up fashion, |
61 | | * adding code for equivalence classes that were not yet computed. |
62 | | */ |
63 | | class CommonSubexpressionEliminator |
64 | | { |
65 | | public: |
66 | | using Id = ExpressionClasses::Id; |
67 | | using StoreOperation = KnownState::StoreOperation; |
68 | | |
69 | 0 | explicit CommonSubexpressionEliminator(KnownState const& _state): m_initialState(_state), m_state(_state) {} |
70 | | |
71 | | /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first |
72 | | /// item that must be fed into a new instance of the eliminator. |
73 | | /// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect |
74 | | template <class AssemblyItemIterator> |
75 | | AssemblyItemIterator feedItems(AssemblyItemIterator _iterator, AssemblyItemIterator _end, bool _msizeImportant); |
76 | | |
77 | | /// @returns the resulting items after optimization. |
78 | | AssemblyItems getOptimizedItems(); |
79 | | |
80 | | private: |
81 | | /// Feeds the item into the system for analysis. |
82 | | void feedItem(AssemblyItem const& _item, bool _copyItem = false); |
83 | | |
84 | | /// Tries to optimize the item that breaks the basic block at the end. |
85 | | void optimizeBreakingItem(); |
86 | | |
87 | | KnownState m_initialState; |
88 | | KnownState m_state; |
89 | | /// Keeps information about which storage or memory slots were written to at which sequence |
90 | | /// number with what instruction. |
91 | | std::vector<StoreOperation> m_storeOperations; |
92 | | |
93 | | /// The item that breaks the basic block, can be nullptr. |
94 | | /// It is usually appended to the block but can be optimized in some cases. |
95 | | AssemblyItem const* m_breakingItem = nullptr; |
96 | | }; |
97 | | |
98 | | /** |
99 | | * Unit that generates code from current stack layout, target stack layout and information about |
100 | | * the equivalence classes. |
101 | | */ |
102 | | class CSECodeGenerator |
103 | | { |
104 | | public: |
105 | | using StoreOperation = CommonSubexpressionEliminator::StoreOperation; |
106 | | using StoreOperations = std::vector<StoreOperation>; |
107 | | using Id = ExpressionClasses::Id; |
108 | | |
109 | | /// Initializes the code generator with the given classes and store operations. |
110 | | /// The store operations have to be sorted by sequence number in ascending order. |
111 | | CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations); |
112 | | |
113 | | /// @returns the assembly items generated from the given requirements |
114 | | /// @param _initialSequenceNumber starting sequence number, do not generate sequenced operations |
115 | | /// before this number. |
116 | | /// @param _initialStack current contents of the stack (up to stack height of zero) |
117 | | /// @param _targetStackContents final contents of the stack, by stack height relative to initial |
118 | | /// @note should only be called once on each object. |
119 | | AssemblyItems generateCode( |
120 | | unsigned _initialSequenceNumber, |
121 | | int _initialStackHeight, |
122 | | std::map<int, Id> const& _initialStack, |
123 | | std::map<int, Id> const& _targetStackContents |
124 | | ); |
125 | | |
126 | | private: |
127 | | /// Recursively discovers all dependencies to @a m_requests. |
128 | | void addDependencies(Id _c); |
129 | | |
130 | | /// Produce code that generates the given element if it is not yet present. |
131 | | /// @param _allowSequenced indicates that sequence-constrained operations are allowed |
132 | | void generateClassElement(Id _c, bool _allowSequenced = false); |
133 | | /// @returns the position of the representative of the given id on the stack. |
134 | | /// @note throws an exception if it is not on the stack. |
135 | | int classElementPosition(Id _id) const; |
136 | | |
137 | | /// @returns true if the copy of @a _element can be removed from stack position _fromPosition |
138 | | /// - in general or, if given, while computing @a _result. |
139 | | bool canBeRemoved(Id _element, Id _result = Id(-1), int _fromPosition = c_invalidPosition); |
140 | | |
141 | | /// Appends code to remove the topmost stack element if it can be removed. |
142 | | bool removeStackTopIfPossible(); |
143 | | |
144 | | /// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position. |
145 | | void appendDup(int _fromPosition, langutil::SourceLocation const& _location); |
146 | | /// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position. |
147 | | /// @note this might also remove the last item if it exactly the same swap instruction. |
148 | | void appendOrRemoveSwap(int _fromPosition, langutil::SourceLocation const& _location); |
149 | | /// Appends the given assembly item. |
150 | | void appendItem(AssemblyItem const& _item); |
151 | | |
152 | | static int const c_invalidPosition = -0x7fffffff; |
153 | | |
154 | | AssemblyItems m_generatedItems; |
155 | | /// Current height of the stack relative to the start. |
156 | | int m_stackHeight = 0; |
157 | | /// If (b, a) is in m_requests then b is needed to compute a. |
158 | | std::unordered_multimap<Id, Id> m_neededBy; |
159 | | /// Current content of the stack. |
160 | | std::map<int, Id> m_stack; |
161 | | /// Current positions of equivalence classes, equal to the empty set if already deleted. |
162 | | std::unordered_map<Id, std::set<int>> m_classPositions; |
163 | | |
164 | | /// The actual equivalence class items and how to compute them. |
165 | | ExpressionClasses& m_expressionClasses; |
166 | | /// Keeps information about which storage or memory slots were written to by which operations. |
167 | | /// The operations are sorted ascendingly by sequence number. |
168 | | std::map<std::pair<StoreOperation::Target, Id>, StoreOperations> m_storeOperations; |
169 | | /// The set of equivalence classes that should be present on the stack at the end. |
170 | | std::set<Id> m_finalClasses; |
171 | | std::map<int, Id> m_targetStack; |
172 | | }; |
173 | | |
174 | | template <class AssemblyItemIterator> |
175 | | AssemblyItemIterator CommonSubexpressionEliminator::feedItems( |
176 | | AssemblyItemIterator _iterator, |
177 | | AssemblyItemIterator _end, |
178 | | bool _msizeImportant |
179 | | ) |
180 | 0 | { |
181 | 0 | assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator."); |
182 | 0 | unsigned const maxChunkSize = 2000; |
183 | 0 | unsigned chunkSize = 0; |
184 | 0 | for ( |
185 | 0 | ; |
186 | 0 | _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator, _msizeImportant) && chunkSize < maxChunkSize; |
187 | 0 | ++_iterator, ++chunkSize |
188 | 0 | ) |
189 | 0 | feedItem(*_iterator); |
190 | 0 | if (_iterator != _end && chunkSize < maxChunkSize) |
191 | 0 | m_breakingItem = &(*_iterator++); |
192 | 0 | return _iterator; |
193 | 0 | } |
194 | | |
195 | | } |