/src/solidity/libyul/optimiser/StackToMemoryMover.cpp
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 | | #include <libyul/optimiser/StackToMemoryMover.h> |
18 | | #include <libyul/optimiser/NameCollector.h> |
19 | | #include <libyul/optimiser/NameDispenser.h> |
20 | | #include <libyul/backends/evm/EVMDialect.h> |
21 | | |
22 | | #include <libyul/AST.h> |
23 | | #include <libyul/Utilities.h> |
24 | | |
25 | | #include <libsolutil/CommonData.h> |
26 | | |
27 | | #include <range/v3/algorithm/none_of.hpp> |
28 | | #include <range/v3/view/filter.hpp> |
29 | | #include <range/v3/view/transform.hpp> |
30 | | #include <range/v3/view/zip.hpp> |
31 | | #include <range/v3/range/conversion.hpp> |
32 | | |
33 | | using namespace solidity; |
34 | | using namespace solidity::yul; |
35 | | |
36 | | namespace |
37 | | { |
38 | | std::vector<Statement> generateMemoryStore( |
39 | | Dialect const& _dialect, |
40 | | langutil::DebugData::ConstPtr const& _debugData, |
41 | | LiteralValue const& _mpos, |
42 | | Expression _value |
43 | | ) |
44 | 14.0k | { |
45 | 14.0k | std::optional<BuiltinHandle> memoryStoreFunctionHandle = _dialect.memoryStoreFunctionHandle(); |
46 | 14.0k | yulAssert(memoryStoreFunctionHandle); |
47 | 14.0k | std::vector<Statement> result; |
48 | 14.0k | result.emplace_back(ExpressionStatement{_debugData, FunctionCall{ |
49 | 14.0k | _debugData, |
50 | 14.0k | BuiltinName{_debugData, *memoryStoreFunctionHandle}, |
51 | 14.0k | { |
52 | 14.0k | Literal{_debugData, LiteralKind::Number, _mpos}, |
53 | 14.0k | std::move(_value) |
54 | 14.0k | } |
55 | 14.0k | }}); |
56 | 14.0k | return result; |
57 | 14.0k | } |
58 | | |
59 | | FunctionCall generateMemoryLoad(Dialect const& _dialect, langutil::DebugData::ConstPtr const& _debugData, LiteralValue const& _mpos) |
60 | 18.7k | { |
61 | 18.7k | std::optional<BuiltinHandle> const& memoryLoadHandle = _dialect.memoryLoadFunctionHandle(); |
62 | 18.7k | yulAssert(memoryLoadHandle); |
63 | 18.7k | return FunctionCall{ |
64 | 18.7k | _debugData, |
65 | 18.7k | BuiltinName{_debugData, *memoryLoadHandle}, { |
66 | 18.7k | Literal{ |
67 | 18.7k | _debugData, |
68 | 18.7k | LiteralKind::Number, |
69 | 18.7k | _mpos |
70 | 18.7k | } |
71 | 18.7k | } |
72 | 18.7k | }; |
73 | 18.7k | } |
74 | | } |
75 | | |
76 | | void StackToMemoryMover::run( |
77 | | OptimiserStepContext& _context, |
78 | | u256 _reservedMemory, |
79 | | std::map<YulName, uint64_t> const& _memorySlots, |
80 | | uint64_t _numRequiredSlots, |
81 | | Block& _block |
82 | | ) |
83 | 45.2k | { |
84 | 45.2k | VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots); |
85 | 45.2k | StackToMemoryMover stackToMemoryMover( |
86 | 45.2k | _context, |
87 | 45.2k | memoryOffsetTracker, |
88 | 45.2k | util::applyMap( |
89 | 45.2k | allFunctionDefinitions(_block), |
90 | 132k | util::mapTuple([](YulName _name, FunctionDefinition const* _funDef) { |
91 | 132k | return make_pair(_name, _funDef->returnVariables); |
92 | 132k | }), |
93 | 45.2k | std::map<YulName, NameWithDebugDataList>{} |
94 | 45.2k | ) |
95 | 45.2k | ); |
96 | 45.2k | stackToMemoryMover(_block); |
97 | 45.2k | _block.statements += std::move(stackToMemoryMover.m_newFunctionDefinitions); |
98 | 45.2k | } |
99 | | |
100 | | StackToMemoryMover::StackToMemoryMover( |
101 | | OptimiserStepContext& _context, |
102 | | VariableMemoryOffsetTracker const& _memoryOffsetTracker, |
103 | | std::map<YulName, NameWithDebugDataList> _functionReturnVariables |
104 | | ): |
105 | 45.2k | m_context(_context), |
106 | 45.2k | m_memoryOffsetTracker(_memoryOffsetTracker), |
107 | 45.2k | m_nameDispenser(_context.dispenser), |
108 | 45.2k | m_functionReturnVariables(std::move(_functionReturnVariables)) |
109 | 45.2k | { |
110 | 45.2k | auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect); |
111 | 45.2k | yulAssert( |
112 | 45.2k | evmDialect && evmDialect->providesObjectAccess(), |
113 | 45.2k | "StackToMemoryMover can only be run on objects using the EVMDialect with object access." |
114 | 45.2k | ); |
115 | 45.2k | } |
116 | | |
117 | | void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) |
118 | 132k | { |
119 | | // It is important to first visit the function body, so that it doesn't replace the memory inits for |
120 | | // variable arguments we might generate below. |
121 | 132k | ASTModifier::operator()(_functionDefinition); |
122 | | |
123 | 132k | std::vector<Statement> memoryVariableInits; |
124 | | |
125 | | // All function parameters with a memory slot are moved at the beginning of the function body. |
126 | 132k | for (NameWithDebugData const& param: _functionDefinition.parameters) |
127 | 129k | if (auto slot = m_memoryOffsetTracker(param.name)) |
128 | 653 | memoryVariableInits += generateMemoryStore( |
129 | 653 | m_context.dialect, |
130 | 653 | param.debugData, |
131 | 653 | *slot, |
132 | 653 | Identifier{param.debugData, param.name} |
133 | 653 | ); |
134 | | |
135 | | // All memory return variables have to be initialized to zero in memory. |
136 | 132k | for (NameWithDebugData const& returnVariable: _functionDefinition.returnVariables) |
137 | 109k | if (auto slot = m_memoryOffsetTracker(returnVariable.name)) |
138 | 1.04k | memoryVariableInits += generateMemoryStore( |
139 | 1.04k | m_context.dialect, |
140 | 1.04k | returnVariable.debugData, |
141 | 1.04k | *slot, |
142 | 1.04k | Literal{returnVariable.debugData, LiteralKind::Number, LiteralValue(u256{0})} |
143 | 1.04k | ); |
144 | | |
145 | | // Special case of a function with a single return argument that needs to move to memory. |
146 | 132k | if (_functionDefinition.returnVariables.size() == 1 && m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name)) |
147 | 843 | { |
148 | 843 | NameWithDebugDataList stackParameters = _functionDefinition.parameters | ranges::views::filter( |
149 | 843 | std::not_fn(m_memoryOffsetTracker) |
150 | 843 | ) | ranges::to<NameWithDebugDataList>; |
151 | | // Generate new function without return variable and with only the non-moved parameters. |
152 | 843 | YulName newFunctionName = m_context.dispenser.newName(_functionDefinition.name); |
153 | 843 | m_newFunctionDefinitions.emplace_back(FunctionDefinition{ |
154 | 843 | _functionDefinition.debugData, |
155 | 843 | newFunctionName, |
156 | 843 | stackParameters, |
157 | 843 | {}, |
158 | 843 | std::move(_functionDefinition.body) |
159 | 843 | }); |
160 | | // Generate new names for the arguments to maintain disambiguation. |
161 | 843 | std::map<YulName, YulName> newArgumentNames; |
162 | 843 | for (NameWithDebugData const& _var: stackParameters) |
163 | 662 | newArgumentNames[_var.name] = m_context.dispenser.newName(_var.name); |
164 | 843 | for (auto& parameter: _functionDefinition.parameters) |
165 | 895 | parameter.name = util::valueOrDefault(newArgumentNames, parameter.name, parameter.name); |
166 | | // Replace original function by a call to the new function and an assignment to the return variable from memory. |
167 | 843 | _functionDefinition.body = Block{_functionDefinition.debugData, std::move(memoryVariableInits)}; |
168 | 843 | _functionDefinition.body.statements.emplace_back(ExpressionStatement{ |
169 | 843 | _functionDefinition.debugData, |
170 | 843 | FunctionCall{ |
171 | 843 | _functionDefinition.debugData, |
172 | 843 | Identifier{_functionDefinition.debugData, newFunctionName}, |
173 | 843 | stackParameters | ranges::views::transform([&](NameWithDebugData const& _arg) { |
174 | 662 | return Expression{Identifier{_arg.debugData, newArgumentNames.at(_arg.name)}}; |
175 | 662 | }) | ranges::to<std::vector<Expression>> |
176 | 843 | } |
177 | 843 | }); |
178 | 843 | _functionDefinition.body.statements.emplace_back(Assignment{ |
179 | 843 | _functionDefinition.debugData, |
180 | 843 | {Identifier{_functionDefinition.debugData, _functionDefinition.returnVariables.front().name}}, |
181 | 843 | std::make_unique<Expression>(generateMemoryLoad( |
182 | 843 | m_context.dialect, |
183 | 843 | _functionDefinition.debugData, |
184 | 843 | *m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name) |
185 | 843 | )) |
186 | 843 | }); |
187 | 843 | return; |
188 | 843 | } |
189 | | |
190 | 132k | if (!memoryVariableInits.empty()) |
191 | 469 | _functionDefinition.body.statements = std::move(memoryVariableInits) + std::move(_functionDefinition.body.statements); |
192 | | |
193 | 132k | _functionDefinition.returnVariables = _functionDefinition.returnVariables | ranges::views::filter( |
194 | 132k | std::not_fn(m_memoryOffsetTracker) |
195 | 132k | ) | ranges::to<NameWithDebugDataList>; |
196 | 132k | } |
197 | | |
198 | | void StackToMemoryMover::operator()(Block& _block) |
199 | 661k | { |
200 | 661k | using OptionalStatements = std::optional<std::vector<Statement>>; |
201 | | |
202 | 661k | auto rewriteAssignmentOrVariableDeclarationLeftHandSide = [this]( |
203 | 661k | auto& _stmt, |
204 | 661k | auto& _lhsVars |
205 | 661k | ) -> OptionalStatements { |
206 | 500k | using StatementType = std::decay_t<decltype(_stmt)>; |
207 | | |
208 | 500k | auto debugData = _stmt.debugData; |
209 | 500k | if (_lhsVars.size() == 1) |
210 | 484k | { |
211 | 484k | if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name)) |
212 | 11.9k | return generateMemoryStore( |
213 | 11.9k | m_context.dialect, |
214 | 11.9k | debugData, |
215 | 11.9k | *offset, |
216 | 11.9k | _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})} |
217 | 11.9k | ); |
218 | 472k | else |
219 | 472k | return {}; |
220 | 484k | } |
221 | 15.9k | std::vector<std::optional<LiteralValue>> rhsMemorySlots; |
222 | 15.9k | if (_stmt.value) |
223 | 13.5k | { |
224 | 13.5k | FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get()); |
225 | 13.5k | yulAssert(functionCall, ""); |
226 | 13.5k | if (isBuiltinFunctionCall(*functionCall)) |
227 | 0 | rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt); |
228 | 13.5k | else |
229 | 13.5k | { |
230 | 13.5k | yulAssert(std::holds_alternative<Identifier>(functionCall->functionName)); |
231 | 13.5k | rhsMemorySlots = |
232 | 13.5k | m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) | |
233 | 13.5k | ranges::views::transform(m_memoryOffsetTracker) | |
234 | 13.5k | ranges::to<std::vector<std::optional<LiteralValue>>>; |
235 | 13.5k | } |
236 | 13.5k | } |
237 | 2.40k | else |
238 | 2.40k | rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt); |
239 | | |
240 | | // Nothing to do, if the right-hand-side remains entirely on the stack and |
241 | | // none of the variables in the left-hand-side are moved. |
242 | 15.9k | if ( |
243 | 52.0k | ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) &&StackToMemoryMover.cpp:solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::Assignment, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> > >(solidity::yul::Assignment&, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> >&) const::{lambda(std::__1::optional<solidity::yul::LiteralValue> const&)#1}::operator()(std::__1::optional<solidity::yul::LiteralValue> const&) constLine | Count | Source | 243 | 2.76k | ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) && |
StackToMemoryMover.cpp:solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::VariableDeclaration, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> > >(solidity::yul::VariableDeclaration&, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> >&) const::{lambda(std::__1::optional<solidity::yul::LiteralValue> const&)#1}::operator()(std::__1::optional<solidity::yul::LiteralValue> const&) constLine | Count | Source | 243 | 49.2k | ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) && |
|
244 | 15.6k | !util::contains_if(_lhsVars, m_memoryOffsetTracker) |
245 | 15.9k | ) |
246 | 15.3k | return {}; |
247 | | |
248 | 561 | std::vector<Statement> memoryAssignments; |
249 | 561 | std::vector<Statement> variableAssignments; |
250 | 561 | VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)}; |
251 | | |
252 | 561 | yulAssert(rhsMemorySlots.size() == _lhsVars.size(), ""); |
253 | 561 | for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots)) |
254 | 1.90k | { |
255 | 1.90k | std::unique_ptr<Expression> rhs; |
256 | 1.90k | if (rhsSlot) |
257 | 430 | rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot)); |
258 | 1.47k | else |
259 | 1.47k | { |
260 | 1.47k | YulName tempVarName = m_nameDispenser.newName(lhsVar.name); |
261 | 1.47k | tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName}); |
262 | 1.47k | rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName}); |
263 | 1.47k | } |
264 | | |
265 | 1.90k | if (auto offset = m_memoryOffsetTracker(lhsVar.name)) |
266 | 378 | memoryAssignments += generateMemoryStore( |
267 | 378 | m_context.dialect, |
268 | 378 | _stmt.debugData, |
269 | 378 | *offset, |
270 | 378 | std::move(*rhs) |
271 | 378 | ); |
272 | 1.52k | else |
273 | 1.52k | variableAssignments.emplace_back(StatementType{ |
274 | 1.52k | debugData, |
275 | 1.52k | { std::move(lhsVar) }, |
276 | 1.52k | std::move(rhs) |
277 | 1.52k | }); |
278 | 1.90k | } |
279 | | |
280 | 561 | std::vector<Statement> result; |
281 | 561 | if (tempDecl.variables.empty()) |
282 | 52 | result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)}); |
283 | 509 | else |
284 | 509 | result.emplace_back(std::move(tempDecl)); |
285 | 561 | reverse(memoryAssignments.begin(), memoryAssignments.end()); |
286 | 561 | result += std::move(memoryAssignments); |
287 | 561 | reverse(variableAssignments.begin(), variableAssignments.end()); |
288 | 561 | result += std::move(variableAssignments); |
289 | 561 | return OptionalStatements{std::move(result)}; |
290 | 15.9k | }; StackToMemoryMover.cpp:std::__1::optional<std::__1::vector<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block>, std::__1::allocator<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block> > > > solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::Assignment, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> > >(solidity::yul::Assignment&, std::__1::vector<solidity::yul::Identifier, std::__1::allocator<solidity::yul::Identifier> >&) const Line | Count | Source | 205 | 237k | ) -> OptionalStatements { | 206 | 237k | using StatementType = std::decay_t<decltype(_stmt)>; | 207 | | | 208 | 237k | auto debugData = _stmt.debugData; | 209 | 237k | if (_lhsVars.size() == 1) | 210 | 236k | { | 211 | 236k | if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name)) | 212 | 2.48k | return generateMemoryStore( | 213 | 2.48k | m_context.dialect, | 214 | 2.48k | debugData, | 215 | 2.48k | *offset, | 216 | 2.48k | _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})} | 217 | 2.48k | ); | 218 | 234k | else | 219 | 234k | return {}; | 220 | 236k | } | 221 | 1.37k | std::vector<std::optional<LiteralValue>> rhsMemorySlots; | 222 | 1.37k | if (_stmt.value) | 223 | 1.37k | { | 224 | 1.37k | FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get()); | 225 | 1.37k | yulAssert(functionCall, ""); | 226 | 1.37k | if (isBuiltinFunctionCall(*functionCall)) | 227 | 0 | rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt); | 228 | 1.37k | else | 229 | 1.37k | { | 230 | 1.37k | yulAssert(std::holds_alternative<Identifier>(functionCall->functionName)); | 231 | 1.37k | rhsMemorySlots = | 232 | 1.37k | m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) | | 233 | 1.37k | ranges::views::transform(m_memoryOffsetTracker) | | 234 | 1.37k | ranges::to<std::vector<std::optional<LiteralValue>>>; | 235 | 1.37k | } | 236 | 1.37k | } | 237 | 0 | else | 238 | 0 | rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt); | 239 | | | 240 | | // Nothing to do, if the right-hand-side remains entirely on the stack and | 241 | | // none of the variables in the left-hand-side are moved. | 242 | 1.37k | if ( | 243 | 1.37k | ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) && | 244 | 1.37k | !util::contains_if(_lhsVars, m_memoryOffsetTracker) | 245 | 1.37k | ) | 246 | 1.37k | return {}; | 247 | | | 248 | 0 | std::vector<Statement> memoryAssignments; | 249 | 0 | std::vector<Statement> variableAssignments; | 250 | 0 | VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)}; | 251 | |
| 252 | 0 | yulAssert(rhsMemorySlots.size() == _lhsVars.size(), ""); | 253 | 0 | for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots)) | 254 | 0 | { | 255 | 0 | std::unique_ptr<Expression> rhs; | 256 | 0 | if (rhsSlot) | 257 | 0 | rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot)); | 258 | 0 | else | 259 | 0 | { | 260 | 0 | YulName tempVarName = m_nameDispenser.newName(lhsVar.name); | 261 | 0 | tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName}); | 262 | 0 | rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName}); | 263 | 0 | } | 264 | |
| 265 | 0 | if (auto offset = m_memoryOffsetTracker(lhsVar.name)) | 266 | 0 | memoryAssignments += generateMemoryStore( | 267 | 0 | m_context.dialect, | 268 | 0 | _stmt.debugData, | 269 | 0 | *offset, | 270 | 0 | std::move(*rhs) | 271 | 0 | ); | 272 | 0 | else | 273 | 0 | variableAssignments.emplace_back(StatementType{ | 274 | 0 | debugData, | 275 | 0 | { std::move(lhsVar) }, | 276 | 0 | std::move(rhs) | 277 | 0 | }); | 278 | 0 | } | 279 | |
| 280 | 0 | std::vector<Statement> result; | 281 | 0 | if (tempDecl.variables.empty()) | 282 | 0 | result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)}); | 283 | 0 | else | 284 | 0 | result.emplace_back(std::move(tempDecl)); | 285 | 0 | reverse(memoryAssignments.begin(), memoryAssignments.end()); | 286 | 0 | result += std::move(memoryAssignments); | 287 | 0 | reverse(variableAssignments.begin(), variableAssignments.end()); | 288 | 0 | result += std::move(variableAssignments); | 289 | 0 | return OptionalStatements{std::move(result)}; | 290 | 1.37k | }; |
StackToMemoryMover.cpp:std::__1::optional<std::__1::vector<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block>, std::__1::allocator<std::__1::variant<solidity::yul::ExpressionStatement, solidity::yul::Assignment, solidity::yul::VariableDeclaration, solidity::yul::FunctionDefinition, solidity::yul::If, solidity::yul::Switch, solidity::yul::ForLoop, solidity::yul::Break, solidity::yul::Continue, solidity::yul::Leave, solidity::yul::Block> > > > solidity::yul::StackToMemoryMover::operator()(solidity::yul::Block&)::$_0::operator()<solidity::yul::VariableDeclaration, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> > >(solidity::yul::VariableDeclaration&, std::__1::vector<solidity::yul::NameWithDebugData, std::__1::allocator<solidity::yul::NameWithDebugData> >&) const Line | Count | Source | 205 | 262k | ) -> OptionalStatements { | 206 | 262k | using StatementType = std::decay_t<decltype(_stmt)>; | 207 | | | 208 | 262k | auto debugData = _stmt.debugData; | 209 | 262k | if (_lhsVars.size() == 1) | 210 | 248k | { | 211 | 248k | if (auto offset = m_memoryOffsetTracker(_lhsVars.front().name)) | 212 | 9.50k | return generateMemoryStore( | 213 | 9.50k | m_context.dialect, | 214 | 9.50k | debugData, | 215 | 9.50k | *offset, | 216 | 9.50k | _stmt.value ? *std::move(_stmt.value) : Literal{debugData, LiteralKind::Number, LiteralValue(u256{0})} | 217 | 9.50k | ); | 218 | 238k | else | 219 | 238k | return {}; | 220 | 248k | } | 221 | 14.5k | std::vector<std::optional<LiteralValue>> rhsMemorySlots; | 222 | 14.5k | if (_stmt.value) | 223 | 12.1k | { | 224 | 12.1k | FunctionCall const* functionCall = std::get_if<FunctionCall>(_stmt.value.get()); | 225 | 12.1k | yulAssert(functionCall, ""); | 226 | 12.1k | if (isBuiltinFunctionCall(*functionCall)) | 227 | 0 | rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt); | 228 | 12.1k | else | 229 | 12.1k | { | 230 | 12.1k | yulAssert(std::holds_alternative<Identifier>(functionCall->functionName)); | 231 | 12.1k | rhsMemorySlots = | 232 | 12.1k | m_functionReturnVariables.at(std::get<Identifier>(functionCall->functionName).name) | | 233 | 12.1k | ranges::views::transform(m_memoryOffsetTracker) | | 234 | 12.1k | ranges::to<std::vector<std::optional<LiteralValue>>>; | 235 | 12.1k | } | 236 | 12.1k | } | 237 | 2.40k | else | 238 | 2.40k | rhsMemorySlots = std::vector<std::optional<LiteralValue>>(_lhsVars.size(), std::nullopt); | 239 | | | 240 | | // Nothing to do, if the right-hand-side remains entirely on the stack and | 241 | | // none of the variables in the left-hand-side are moved. | 242 | 14.5k | if ( | 243 | 14.5k | ranges::none_of(rhsMemorySlots, [](std::optional<LiteralValue> const& _slot) { return _slot.has_value(); }) && | 244 | 14.2k | !util::contains_if(_lhsVars, m_memoryOffsetTracker) | 245 | 14.5k | ) | 246 | 13.9k | return {}; | 247 | | | 248 | 561 | std::vector<Statement> memoryAssignments; | 249 | 561 | std::vector<Statement> variableAssignments; | 250 | 561 | VariableDeclaration tempDecl{debugData, {}, std::move(_stmt.value)}; | 251 | | | 252 | 561 | yulAssert(rhsMemorySlots.size() == _lhsVars.size(), ""); | 253 | 561 | for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots)) | 254 | 1.90k | { | 255 | 1.90k | std::unique_ptr<Expression> rhs; | 256 | 1.90k | if (rhsSlot) | 257 | 430 | rhs = std::make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot)); | 258 | 1.47k | else | 259 | 1.47k | { | 260 | 1.47k | YulName tempVarName = m_nameDispenser.newName(lhsVar.name); | 261 | 1.47k | tempDecl.variables.emplace_back(NameWithDebugData{lhsVar.debugData, tempVarName}); | 262 | 1.47k | rhs = std::make_unique<Expression>(Identifier{debugData, tempVarName}); | 263 | 1.47k | } | 264 | | | 265 | 1.90k | if (auto offset = m_memoryOffsetTracker(lhsVar.name)) | 266 | 378 | memoryAssignments += generateMemoryStore( | 267 | 378 | m_context.dialect, | 268 | 378 | _stmt.debugData, | 269 | 378 | *offset, | 270 | 378 | std::move(*rhs) | 271 | 378 | ); | 272 | 1.52k | else | 273 | 1.52k | variableAssignments.emplace_back(StatementType{ | 274 | 1.52k | debugData, | 275 | 1.52k | { std::move(lhsVar) }, | 276 | 1.52k | std::move(rhs) | 277 | 1.52k | }); | 278 | 1.90k | } | 279 | | | 280 | 561 | std::vector<Statement> result; | 281 | 561 | if (tempDecl.variables.empty()) | 282 | 52 | result.emplace_back(ExpressionStatement{debugData, *std::move(tempDecl.value)}); | 283 | 509 | else | 284 | 509 | result.emplace_back(std::move(tempDecl)); | 285 | 561 | reverse(memoryAssignments.begin(), memoryAssignments.end()); | 286 | 561 | result += std::move(memoryAssignments); | 287 | 561 | reverse(variableAssignments.begin(), variableAssignments.end()); | 288 | 561 | result += std::move(variableAssignments); | 289 | 561 | return OptionalStatements{std::move(result)}; | 290 | 14.5k | }; |
|
291 | | |
292 | 661k | util::iterateReplacing( |
293 | 661k | _block.statements, |
294 | 661k | [&](Statement& _statement) -> OptionalStatements |
295 | 1.55M | { |
296 | 1.55M | visit(_statement); |
297 | 1.55M | if (auto* assignment = std::get_if<Assignment>(&_statement)) |
298 | 237k | return rewriteAssignmentOrVariableDeclarationLeftHandSide(*assignment, assignment->variableNames); |
299 | 1.31M | else if (auto* varDecl = std::get_if<VariableDeclaration>(&_statement)) |
300 | 262k | return rewriteAssignmentOrVariableDeclarationLeftHandSide(*varDecl, varDecl->variables); |
301 | 1.05M | return {}; |
302 | 1.55M | } |
303 | 661k | ); |
304 | 661k | } |
305 | | |
306 | | void StackToMemoryMover::visit(Expression& _expression) |
307 | 5.64M | { |
308 | 5.64M | ASTModifier::visit(_expression); |
309 | 5.64M | if (Identifier* identifier = std::get_if<Identifier>(&_expression)) |
310 | 708k | if (auto offset = m_memoryOffsetTracker(identifier->name)) |
311 | 17.4k | _expression = generateMemoryLoad(m_context.dialect, identifier->debugData, *offset); |
312 | 5.64M | } |
313 | | |
314 | | std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(YulName const& _variable) const |
315 | 1.74M | { |
316 | 1.74M | if (m_memorySlots.count(_variable)) |
317 | 34.4k | { |
318 | 34.4k | uint64_t slot = m_memorySlots.at(_variable); |
319 | 34.4k | yulAssert(slot < m_numRequiredSlots, ""); |
320 | 34.4k | auto const memoryOffset = m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1); |
321 | 34.4k | return valueOfNumberLiteral(toCompactHexWithPrefix(memoryOffset)); |
322 | 34.4k | } |
323 | 1.70M | else |
324 | 1.70M | return std::nullopt; |
325 | 1.74M | } |
326 | | |
327 | | std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(NameWithDebugData const& _variable) const |
328 | 222k | { |
329 | 222k | return (*this)(_variable.name); |
330 | 222k | } |
331 | | |
332 | | std::optional<LiteralValue> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(Identifier const& _variable) const |
333 | 2.76k | { |
334 | 2.76k | return (*this)(_variable.name); |
335 | 2.76k | } |