/src/solidity/libyul/optimiser/UnusedAssignEliminator.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 | | * Optimiser component that removes assignments to variables that are not used |
20 | | * until they go out of scope or are re-assigned. |
21 | | */ |
22 | | |
23 | | #pragma once |
24 | | |
25 | | #include <libyul/ASTForward.h> |
26 | | #include <libyul/optimiser/ASTWalker.h> |
27 | | #include <libyul/optimiser/OptimiserStep.h> |
28 | | #include <libyul/optimiser/UnusedStoreBase.h> |
29 | | |
30 | | #include <map> |
31 | | #include <vector> |
32 | | |
33 | | namespace solidity::yul |
34 | | { |
35 | | struct Dialect; |
36 | | |
37 | | /** |
38 | | * Optimiser component that removes assignments to variables that are not used |
39 | | * until they go out of scope or are re-assigned. This component |
40 | | * respects the control-flow and takes it into account for removal. |
41 | | * |
42 | | * Example: |
43 | | * |
44 | | * { |
45 | | * let a |
46 | | * a := 1 |
47 | | * a := 2 |
48 | | * b := 2 |
49 | | * if calldataload(0) |
50 | | * { |
51 | | * b := mload(a) |
52 | | * } |
53 | | * a := b |
54 | | * } |
55 | | * |
56 | | * In the example, "a := 1" can be removed because the value from this assignment |
57 | | * is not used in any control-flow branch (it is replaced right away). |
58 | | * The assignment "a := 2" is also overwritten by "a := b" at the end, |
59 | | * but there is a control-flow path (through the condition body) which uses |
60 | | * the value from "a := 2" and thus, this assignment cannot be removed. |
61 | | * |
62 | | * Detailed rules: |
63 | | * |
64 | | * The AST is traversed twice: in an information gathering step and in the |
65 | | * actual removal step. During information gathering, we maintain a |
66 | | * mapping from assignment statements to the three states |
67 | | * "unused", "undecided" and "used". |
68 | | * When an assignment is visited, it is added to the mapping in the "undecided" state |
69 | | * (see remark about for loops below) and every other assignment to the same variable |
70 | | * that is still in the "undecided" state is changed to "unused". |
71 | | * When a variable is referenced, the state of any assignment to that variable still |
72 | | * in the "undecided" state is changed to "used". |
73 | | * At points where control flow splits, a copy |
74 | | * of the mapping is handed over to each branch. At points where control flow |
75 | | * joins, the two mappings coming from the two branches are combined in the following way: |
76 | | * Statements that are only in one mapping or have the same state are used unchanged. |
77 | | * Conflicting values are resolved in the following way: |
78 | | * "unused", "undecided" -> "undecided" |
79 | | * "unused", "used" -> "used" |
80 | | * "undecided, "used" -> "used". |
81 | | * |
82 | | * For for-loops, the condition, body and post-part are visited twice, taking |
83 | | * the joining control-flow at the condition into account. |
84 | | * In other words, we create three control flow paths: Zero runs of the loop, |
85 | | * one run and two runs and then combine them at the end. |
86 | | * Running at most twice is enough because there are only three different states. |
87 | | * |
88 | | * Since this algorithm has exponential runtime in the nesting depth of for loops, |
89 | | * a shortcut is taken at a certain nesting level: We only use the zero- and |
90 | | * once-run of the for loop and change any assignment that was newly introduced |
91 | | * in the for loop from to "used". |
92 | | * |
93 | | * For switch statements that have a "default"-case, there is no control-flow |
94 | | * part that skips the switch. |
95 | | * |
96 | | * At ``leave`` statements, all return variables are set to "used". |
97 | | * |
98 | | * When a variable goes out of scope, all statements still in the "undecided" |
99 | | * state are changed to "unused", unless the variable is the return |
100 | | * parameter of a function - there, the state changes to "used". |
101 | | * |
102 | | * In the second traversal, all assignments that are in the "unused" state are removed. |
103 | | * |
104 | | * |
105 | | * This step is usually run right after the SSA transform to complete |
106 | | * the generation of the pseudo-SSA. |
107 | | * |
108 | | * Prerequisite: Disambiguator, ForLoopInitRewriter. |
109 | | */ |
110 | | class UnusedAssignEliminator: public UnusedStoreBase |
111 | | { |
112 | | public: |
113 | | static constexpr char const* name{"UnusedAssignEliminator"}; |
114 | | static void run(OptimiserStepContext&, Block& _ast); |
115 | | |
116 | 128k | explicit UnusedAssignEliminator(Dialect const& _dialect): UnusedStoreBase(_dialect) {} |
117 | | |
118 | | void operator()(Identifier const& _identifier) override; |
119 | | void operator()(VariableDeclaration const& _variableDeclaration) override; |
120 | | void operator()(Assignment const& _assignment) override; |
121 | | void operator()(FunctionDefinition const&) override; |
122 | | void operator()(Leave const&) override; |
123 | | void operator()(Block const& _block) override; |
124 | | |
125 | | using UnusedStoreBase::visit; |
126 | | void visit(Statement const& _statement) override; |
127 | | |
128 | | private: |
129 | | void shortcutNestedLoop(TrackedStores const& _beforeLoop) override; |
130 | | void finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition) override; |
131 | | |
132 | | void changeUndecidedTo(YulString _variable, State _newState); |
133 | | /// Called when a variable goes out of scope. Sets the state of all still undecided |
134 | | /// assignments to the final state. In this case, this also applies to pending |
135 | | /// break and continue TrackedStores. |
136 | | void finalize(YulString _variable, State _finalState); |
137 | | |
138 | | |
139 | | std::set<YulString> m_declaredVariables; |
140 | | std::set<YulString> m_returnVariables; |
141 | | }; |
142 | | |
143 | | } |