/src/solidity/libyul/backends/evm/ConstantOptimiser.cpp
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 | | * Optimisation stage that replaces constants by expressions that compute them. |
20 | | */ |
21 | | |
22 | | #include <libyul/backends/evm/ConstantOptimiser.h> |
23 | | |
24 | | #include <libyul/optimiser/ASTCopier.h> |
25 | | #include <libyul/backends/evm/EVMMetrics.h> |
26 | | #include <libyul/AST.h> |
27 | | #include <libyul/Utilities.h> |
28 | | |
29 | | #include <libsolutil/CommonData.h> |
30 | | |
31 | | #include <variant> |
32 | | |
33 | | using namespace solidity; |
34 | | using namespace solidity::yul; |
35 | | using namespace solidity::util; |
36 | | |
37 | | using Representation = ConstantOptimiser::Representation; |
38 | | |
39 | | namespace |
40 | | { |
41 | | struct MiniEVMInterpreter |
42 | | { |
43 | 439k | explicit MiniEVMInterpreter(EVMDialect const& _dialect): m_dialect(_dialect) {} |
44 | | |
45 | | u256 eval(Expression const& _expr) |
46 | 826k | { |
47 | 826k | return std::visit(*this, _expr); |
48 | 826k | } |
49 | | |
50 | | u256 eval(evmasm::Instruction _instr, std::vector<Expression> const& _arguments) |
51 | 203k | { |
52 | 203k | std::vector<u256> args; |
53 | 203k | for (auto const& arg: _arguments) |
54 | 387k | args.emplace_back(eval(arg)); |
55 | 203k | switch (_instr) |
56 | 203k | { |
57 | 14.9k | case evmasm::Instruction::ADD: |
58 | 14.9k | return args.at(0) + args.at(1); |
59 | 59.5k | case evmasm::Instruction::SUB: |
60 | 59.5k | return args.at(0) - args.at(1); |
61 | 4 | case evmasm::Instruction::MUL: |
62 | 4 | return args.at(0) * args.at(1); |
63 | 1.46k | case evmasm::Instruction::EXP: |
64 | 1.46k | return exp256(args.at(0), args.at(1)); |
65 | 107k | case evmasm::Instruction::SHL: |
66 | 107k | return args.at(0) > 255 ? 0 : (args.at(1) << unsigned(args.at(0))); |
67 | 19.1k | case evmasm::Instruction::NOT: |
68 | 19.1k | return ~args.at(0); |
69 | 0 | default: |
70 | 0 | yulAssert(false, "Invalid operation generated in constant optimizer."); |
71 | 203k | } |
72 | 0 | return 0; |
73 | 203k | } |
74 | | |
75 | | u256 operator()(FunctionCall const& _funCall) |
76 | 203k | { |
77 | 203k | BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, m_dialect); |
78 | 203k | yulAssert(builtin, "Expected builtin function."); |
79 | 203k | yulAssert(builtin->instruction, "Expected EVM instruction."); |
80 | 203k | return eval(*builtin->instruction, _funCall.arguments); |
81 | 203k | } |
82 | | u256 operator()(Literal const& _literal) |
83 | 623k | { |
84 | 623k | return _literal.value.value(); |
85 | 623k | } |
86 | 0 | u256 operator()(Identifier const&) { yulAssert(false, ""); } |
87 | | |
88 | | EVMDialect const& m_dialect; |
89 | | }; |
90 | | } |
91 | | |
92 | | void ConstantOptimiser::visit(Expression& _e) |
93 | 5.94M | { |
94 | 5.94M | if (std::holds_alternative<Literal>(_e)) |
95 | 2.57M | { |
96 | 2.57M | Literal const& literal = std::get<Literal>(_e); |
97 | 2.57M | if (literal.kind != LiteralKind::Number) |
98 | 38.7k | return; |
99 | | |
100 | 2.54M | if ( |
101 | 2.54M | Expression const* repr = |
102 | 2.54M | RepresentationFinder(m_dialect, m_meter, debugDataOf(_e), m_cache) |
103 | 2.54M | .tryFindRepresentation(literal.value.value()) |
104 | 2.54M | ) |
105 | 226k | _e = ASTCopier{}.translate(*repr); |
106 | 2.54M | } |
107 | 3.36M | else |
108 | 3.36M | ASTModifier::visit(_e); |
109 | 5.94M | } |
110 | | |
111 | | Expression const* RepresentationFinder::tryFindRepresentation(u256 const& _value) |
112 | 2.54M | { |
113 | 2.54M | if (_value < 0x10000) |
114 | 2.06M | return nullptr; |
115 | | |
116 | 479k | Representation const& repr = findRepresentation(_value); |
117 | 479k | if (std::holds_alternative<Literal>(*repr.expression)) |
118 | 252k | return nullptr; |
119 | 226k | else |
120 | 226k | return repr.expression.get(); |
121 | 479k | } |
122 | | |
123 | | Representation const& RepresentationFinder::findRepresentation(u256 const& _value) |
124 | 1.70M | { |
125 | 1.70M | if (m_cache.count(_value)) |
126 | 1.26M | return m_cache.at(_value); |
127 | | |
128 | 439k | yulAssert(m_dialect.auxiliaryBuiltinHandles().not_); |
129 | 439k | yulAssert(m_dialect.auxiliaryBuiltinHandles().exp); |
130 | 439k | yulAssert(m_dialect.auxiliaryBuiltinHandles().mul); |
131 | 439k | yulAssert(m_dialect.auxiliaryBuiltinHandles().add); |
132 | 439k | yulAssert(m_dialect.auxiliaryBuiltinHandles().sub); |
133 | | |
134 | 439k | auto const& auxHandles = m_dialect.auxiliaryBuiltinHandles(); |
135 | | |
136 | 439k | Representation routine = represent(_value); |
137 | | |
138 | 439k | if (numberEncodingSize(~_value) < numberEncodingSize(_value)) |
139 | | // Negated is shorter to represent |
140 | 20.7k | routine = min(std::move(routine), represent(*auxHandles.not_, findRepresentation(~_value))); |
141 | | |
142 | | // Decompose value into a * 2**k + b where abs(b) << 2**k |
143 | 109M | for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits) |
144 | 108M | { |
145 | 108M | unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff); |
146 | 108M | if (gapDetector != 0xff && gapDetector != 0x100) |
147 | 107M | continue; |
148 | | |
149 | 782k | u256 powerOfTwo = u256(1) << bits; |
150 | 782k | u256 upperPart = _value >> bits; |
151 | 782k | bigint lowerPart = _value & (powerOfTwo - 1); |
152 | 782k | if ((powerOfTwo - lowerPart) < lowerPart) |
153 | 339k | { |
154 | 339k | lowerPart = lowerPart - powerOfTwo; // make it negative |
155 | 339k | upperPart++; |
156 | 339k | } |
157 | 782k | if (upperPart == 0) |
158 | 0 | continue; |
159 | 782k | if (abs(lowerPart) >= (powerOfTwo >> 8)) |
160 | 789 | continue; |
161 | 781k | Representation newRoutine; |
162 | 781k | if (m_dialect.evmVersion().hasBitwiseShifting()) |
163 | 749k | newRoutine = represent(*auxHandles.shl, represent(bits), findRepresentation(upperPart)); |
164 | 32.3k | else |
165 | 32.3k | { |
166 | 32.3k | newRoutine = represent(*auxHandles.exp, represent(2), represent(bits)); |
167 | 32.3k | if (upperPart != 1) |
168 | 16.2k | newRoutine = represent(*auxHandles.mul, findRepresentation(upperPart), newRoutine); |
169 | 32.3k | } |
170 | | |
171 | 781k | if (newRoutine.cost >= routine.cost) |
172 | 305k | continue; |
173 | | |
174 | 476k | if (lowerPart > 0) |
175 | 209k | newRoutine = represent(*auxHandles.add, newRoutine, findRepresentation(u256(abs(lowerPart)))); |
176 | 266k | else if (lowerPart < 0) |
177 | 233k | newRoutine = represent(*auxHandles.sub, newRoutine, findRepresentation(u256(abs(lowerPart)))); |
178 | | |
179 | 476k | if (m_maxSteps > 0) |
180 | 476k | m_maxSteps--; |
181 | 476k | routine = min(std::move(routine), std::move(newRoutine)); |
182 | 476k | } |
183 | 439k | yulAssert(MiniEVMInterpreter{m_dialect}.eval(*routine.expression) == _value, "Invalid expression generated."); |
184 | 439k | return m_cache[_value] = std::move(routine); |
185 | 439k | } |
186 | | |
187 | | Representation RepresentationFinder::represent(u256 const& _value) const |
188 | 1.25M | { |
189 | 1.25M | Representation repr; |
190 | 1.25M | repr.expression = std::make_unique<Expression>(Literal{m_debugData, LiteralKind::Number, LiteralValue{_value, formatNumber(_value)}}); |
191 | 1.25M | repr.cost = m_meter.costs(*repr.expression); |
192 | 1.25M | return repr; |
193 | 1.25M | } |
194 | | |
195 | | Representation RepresentationFinder::represent( |
196 | | BuiltinHandle const& _instruction, |
197 | | Representation const& _argument |
198 | | ) const |
199 | 20.7k | { |
200 | 20.7k | Representation repr; |
201 | 20.7k | repr.expression = std::make_unique<Expression>(FunctionCall{ |
202 | 20.7k | m_debugData, |
203 | 20.7k | BuiltinName{m_debugData, _instruction}, |
204 | 20.7k | {ASTCopier{}.translate(*_argument.expression)} |
205 | 20.7k | }); |
206 | 20.7k | repr.cost = _argument.cost + m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction); |
207 | 20.7k | return repr; |
208 | 20.7k | } |
209 | | |
210 | | Representation RepresentationFinder::represent( |
211 | | BuiltinHandle const& _instruction, |
212 | | Representation const& _arg1, |
213 | | Representation const& _arg2 |
214 | | ) const |
215 | 1.24M | { |
216 | 1.24M | Representation repr; |
217 | 1.24M | repr.expression = std::make_unique<Expression>(FunctionCall{ |
218 | 1.24M | m_debugData, |
219 | 1.24M | BuiltinName{m_debugData, _instruction}, |
220 | 1.24M | {ASTCopier{}.translate(*_arg1.expression), ASTCopier{}.translate(*_arg2.expression)} |
221 | 1.24M | }); |
222 | 1.24M | repr.cost = m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction) + _arg1.cost + _arg2.cost; |
223 | 1.24M | return repr; |
224 | 1.24M | } |
225 | | |
226 | | Representation RepresentationFinder::min(Representation _a, Representation _b) |
227 | 497k | { |
228 | 497k | if (_a.cost <= _b.cost) |
229 | 368k | return _a; |
230 | 128k | else |
231 | 128k | return _b; |
232 | 497k | } |