/src/solidity/libevmasm/Assembly.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 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** @file Assembly.cpp |
19 | | * @author Gav Wood <i@gavwood.com> |
20 | | * @date 2014 |
21 | | */ |
22 | | |
23 | | #include <libevmasm/Assembly.h> |
24 | | |
25 | | #include <libevmasm/CommonSubexpressionEliminator.h> |
26 | | #include <libevmasm/ControlFlowGraph.h> |
27 | | #include <libevmasm/PeepholeOptimiser.h> |
28 | | #include <libevmasm/Inliner.h> |
29 | | #include <libevmasm/JumpdestRemover.h> |
30 | | #include <libevmasm/BlockDeduplicator.h> |
31 | | #include <libevmasm/ConstantOptimiser.h> |
32 | | |
33 | | #include <liblangutil/CharStream.h> |
34 | | #include <liblangutil/Exceptions.h> |
35 | | |
36 | | #include <libsolutil/JSON.h> |
37 | | #include <libsolutil/StringUtils.h> |
38 | | |
39 | | #include <fmt/format.h> |
40 | | |
41 | | #include <range/v3/algorithm/any_of.hpp> |
42 | | #include <range/v3/algorithm/find_if.hpp> |
43 | | #include <range/v3/view/drop_exactly.hpp> |
44 | | #include <range/v3/view/enumerate.hpp> |
45 | | #include <range/v3/view/map.hpp> |
46 | | |
47 | | #include <fstream> |
48 | | #include <limits> |
49 | | #include <iterator> |
50 | | #include <stack> |
51 | | |
52 | | using namespace solidity; |
53 | | using namespace solidity::evmasm; |
54 | | using namespace solidity::langutil; |
55 | | using namespace solidity::util; |
56 | | |
57 | | namespace |
58 | | { |
59 | | |
60 | | /// Produces instruction location info in RAII style. When an assembly instruction is added to the bytecode, |
61 | | /// this class can be instantiated in that scope. It will record the current bytecode size (before addition) |
62 | | /// and, at destruction time, record the new bytecode size. This information is then added to an external |
63 | | /// instruction locations vector. |
64 | | /// If the instruction decomposes into multiple individual evm instructions, `emit` can be |
65 | | /// called for all but the last one (which will be emitted by the destructor). |
66 | | class InstructionLocationEmitter |
67 | | { |
68 | | public: |
69 | | InstructionLocationEmitter( |
70 | | std::vector<LinkerObject::InstructionLocation>& _instructionLocations, |
71 | | bytes const& _bytecode, |
72 | | size_t const _assemblyItemIndex |
73 | | ): |
74 | 19.1M | m_instructionLocations(_instructionLocations), |
75 | 19.1M | m_bytecode(_bytecode), |
76 | 19.1M | m_assemblyItemIndex(_assemblyItemIndex), |
77 | 19.1M | m_instructionLocationStart(_bytecode.size()) |
78 | 19.1M | {} |
79 | | |
80 | | ~InstructionLocationEmitter() |
81 | 19.1M | { |
82 | 19.1M | emit(); |
83 | 19.1M | } |
84 | | |
85 | | void emit() |
86 | 19.1M | { |
87 | 19.1M | auto const end = m_bytecode.size(); |
88 | 19.1M | m_instructionLocations.push_back(LinkerObject::InstructionLocation{ |
89 | 19.1M | .start = m_instructionLocationStart, |
90 | 19.1M | .end = end, |
91 | 19.1M | .assemblyItemIndex = m_assemblyItemIndex |
92 | 19.1M | }); |
93 | 19.1M | m_instructionLocationStart = end; |
94 | 19.1M | } |
95 | | |
96 | | private: |
97 | | std::vector<LinkerObject::InstructionLocation>& m_instructionLocations; |
98 | | bytes const& m_bytecode; |
99 | | size_t const m_assemblyItemIndex{}; |
100 | | size_t m_instructionLocationStart{}; |
101 | | }; |
102 | | |
103 | | } |
104 | | |
105 | | std::map<std::string, std::shared_ptr<std::string const>> Assembly::s_sharedSourceNames; |
106 | | |
107 | | AssemblyItem const& Assembly::append(AssemblyItem _i) |
108 | 20.0M | { |
109 | 20.0M | assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); |
110 | 20.0M | m_deposit += static_cast<int>(_i.deposit()); |
111 | 20.0M | m_items.emplace_back(std::move(_i)); |
112 | 20.0M | if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid()) |
113 | 19.9M | m_items.back().setLocation(m_currentSourceLocation); |
114 | 20.0M | m_items.back().m_modifierDepth = m_currentModifierDepth; |
115 | 20.0M | return m_items.back(); |
116 | 20.0M | } |
117 | | |
118 | | unsigned Assembly::codeSize(unsigned subTagSize) const |
119 | 78.0k | { |
120 | 97.1k | for (unsigned tagSize = subTagSize; true; ++tagSize) |
121 | 97.1k | { |
122 | 97.1k | size_t ret = 1; |
123 | 97.1k | for (auto const& i: m_data) |
124 | 17.2k | ret += i.second.size(); |
125 | | |
126 | 97.1k | for (AssemblyItem const& i: m_items) |
127 | 38.1M | ret += i.bytesRequired(tagSize, m_evmVersion, Precision::Precise); |
128 | 97.1k | if (numberEncodingSize(ret) <= tagSize) |
129 | 78.0k | return static_cast<unsigned>(ret); |
130 | 97.1k | } |
131 | 78.0k | } |
132 | | |
133 | | void Assembly::importAssemblyItemsFromJSON(Json const& _code, std::vector<std::string> const& _sourceList) |
134 | 0 | { |
135 | 0 | solAssert(m_items.empty()); |
136 | 0 | solRequire(_code.is_array(), AssemblyImportException, "Supplied JSON is not an array."); |
137 | 0 | for (auto jsonItemIter = std::begin(_code); jsonItemIter != std::end(_code); ++jsonItemIter) |
138 | 0 | { |
139 | 0 | AssemblyItem const& newItem = m_items.emplace_back(createAssemblyItemFromJSON(*jsonItemIter, _sourceList)); |
140 | 0 | if (newItem == Instruction::JUMPDEST) |
141 | 0 | solThrow(AssemblyImportException, "JUMPDEST instruction without a tag"); |
142 | 0 | else if (newItem.type() == AssemblyItemType::Tag) |
143 | 0 | { |
144 | 0 | ++jsonItemIter; |
145 | 0 | if (jsonItemIter != std::end(_code) && createAssemblyItemFromJSON(*jsonItemIter, _sourceList) != Instruction::JUMPDEST) |
146 | 0 | solThrow(AssemblyImportException, "JUMPDEST expected after tag."); |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | AssemblyItem Assembly::createAssemblyItemFromJSON(Json const& _json, std::vector<std::string> const& _sourceList) |
152 | 0 | { |
153 | 0 | solRequire(_json.is_object(), AssemblyImportException, "Supplied JSON is not an object."); |
154 | 0 | static std::set<std::string> const validMembers{"name", "begin", "end", "source", "value", "modifierDepth", "jumpType"}; |
155 | 0 | for (auto const& [member, _]: _json.items()) |
156 | 0 | solRequire( |
157 | 0 | validMembers.count(member), |
158 | 0 | AssemblyImportException, |
159 | 0 | fmt::format( |
160 | 0 | "Unknown member '{}'. Valid members are: {}.", |
161 | 0 | member, |
162 | 0 | solidity::util::joinHumanReadable(validMembers, ", ") |
163 | 0 | ) |
164 | 0 | ); |
165 | 0 | solRequire(isOfType<std::string>(_json["name"]), AssemblyImportException, "Member 'name' missing or not of type string."); |
166 | 0 | solRequire(isOfTypeIfExists<int>(_json, "begin"), AssemblyImportException, "Optional member 'begin' not of type int."); |
167 | 0 | solRequire(isOfTypeIfExists<int>(_json, "end"), AssemblyImportException, "Optional member 'end' not of type int."); |
168 | 0 | solRequire(isOfTypeIfExists<int>(_json, "source"), AssemblyImportException, "Optional member 'source' not of type int."); |
169 | 0 | solRequire(isOfTypeIfExists<std::string>(_json, "value"), AssemblyImportException, "Optional member 'value' not of type string."); |
170 | 0 | solRequire(isOfTypeIfExists<int>(_json, "modifierDepth"), AssemblyImportException, "Optional member 'modifierDepth' not of type int."); |
171 | 0 | solRequire(isOfTypeIfExists<std::string>(_json, "jumpType"), AssemblyImportException, "Optional member 'jumpType' not of type string."); |
172 | |
|
173 | 0 | std::string name = get<std::string>(_json["name"]); |
174 | 0 | solRequire(!name.empty(), AssemblyImportException, "Member 'name' is empty."); |
175 | |
|
176 | 0 | SourceLocation location; |
177 | 0 | if (_json.contains("begin")) |
178 | 0 | location.start = get<int>(_json["begin"]); |
179 | 0 | if (_json.contains("end")) |
180 | 0 | location.end = get<int>(_json["end"]); |
181 | 0 | int srcIndex = getOrDefault<int>(_json, "source", -1); |
182 | 0 | size_t modifierDepth = static_cast<size_t>(getOrDefault<int>(_json, "modifierDepth", 0)); |
183 | 0 | std::string value = getOrDefault<std::string>(_json, "value", ""); |
184 | 0 | std::string jumpType = getOrDefault<std::string>(_json, "jumpType", ""); |
185 | |
|
186 | 0 | auto updateUsedTags = [&](u256 const& data) |
187 | 0 | { |
188 | 0 | m_usedTags = std::max(m_usedTags, static_cast<unsigned>(data) + 1); |
189 | 0 | return data; |
190 | 0 | }; |
191 | | |
192 | | // m_tagPositionsInBytecode is sized from m_usedTags, so an imported tag ID has to stay within the |
193 | | // same range that newTag() enforces on the ones we hand out ourselves. |
194 | 0 | auto requireTagIDInRange = [&](u256 const& _tagID) |
195 | 0 | { |
196 | 0 | solRequire( |
197 | 0 | _tagID < 0xffffffff, |
198 | 0 | AssemblyImportException, |
199 | 0 | "The 'value' of a tag or tag reference is out of the supported range." |
200 | 0 | ); |
201 | 0 | return _tagID; |
202 | 0 | }; |
203 | |
|
204 | 0 | auto storeImmutableHash = [&](std::string const& _immutableName) -> h256 |
205 | 0 | { |
206 | 0 | h256 hash(util::keccak256(_immutableName)); |
207 | 0 | solAssert(m_immutables.count(hash) == 0 || m_immutables[hash] == _immutableName); |
208 | 0 | m_immutables[hash] = _immutableName; |
209 | 0 | return hash; |
210 | 0 | }; |
211 | |
|
212 | 0 | auto storeLibraryHash = [&](std::string const& _libraryName) -> h256 |
213 | 0 | { |
214 | 0 | h256 hash(util::keccak256(_libraryName)); |
215 | 0 | solAssert(m_libraries.count(hash) == 0 || m_libraries[hash] == _libraryName); |
216 | 0 | m_libraries[hash] = _libraryName; |
217 | 0 | return hash; |
218 | 0 | }; |
219 | |
|
220 | 0 | auto requireValueDefinedForInstruction = [&](std::string const& _name, std::string const& _value) |
221 | 0 | { |
222 | 0 | solRequire( |
223 | 0 | !_value.empty(), |
224 | 0 | AssemblyImportException, |
225 | 0 | "Member 'value' is missing for instruction '" + _name + "', but the instruction needs a value." |
226 | 0 | ); |
227 | 0 | }; |
228 | |
|
229 | 0 | auto requireValueUndefinedForInstruction = [&](std::string const& _name, std::string const& _value) |
230 | 0 | { |
231 | 0 | solRequire( |
232 | 0 | _value.empty(), |
233 | 0 | AssemblyImportException, |
234 | 0 | "Member 'value' defined for instruction '" + _name + "', but the instruction does not need a value." |
235 | 0 | ); |
236 | 0 | }; |
237 | |
|
238 | 0 | solRequire(srcIndex >= -1 && srcIndex < static_cast<int>(_sourceList.size()), AssemblyImportException, "Source index out of bounds."); |
239 | 0 | if (srcIndex != -1) |
240 | 0 | location.sourceName = sharedSourceName(_sourceList[static_cast<size_t>(srcIndex)]); |
241 | |
|
242 | 0 | AssemblyItem result(0); |
243 | |
|
244 | 0 | if (c_instructions.count(name)) |
245 | 0 | { |
246 | 0 | AssemblyItem item{c_instructions.at(name), langutil::DebugData::create(location)}; |
247 | 0 | if (!jumpType.empty()) |
248 | 0 | { |
249 | 0 | if (item.instruction() == Instruction::JUMP || item.instruction() == Instruction::JUMPI) |
250 | 0 | { |
251 | 0 | std::optional<AssemblyItem::JumpType> parsedJumpType = AssemblyItem::parseJumpType(jumpType); |
252 | 0 | if (!parsedJumpType.has_value()) |
253 | 0 | solThrow(AssemblyImportException, "Invalid jump type."); |
254 | 0 | item.setJumpType(parsedJumpType.value()); |
255 | 0 | } |
256 | 0 | else |
257 | 0 | solThrow( |
258 | 0 | AssemblyImportException, |
259 | 0 | "Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')" |
260 | 0 | ); |
261 | 0 | } |
262 | 0 | requireValueUndefinedForInstruction(name, value); |
263 | 0 | result = item; |
264 | 0 | } |
265 | 0 | else |
266 | 0 | { |
267 | 0 | solRequire( |
268 | 0 | jumpType.empty(), |
269 | 0 | AssemblyImportException, |
270 | 0 | "Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')" |
271 | 0 | ); |
272 | 0 | if (name == "PUSH") |
273 | 0 | { |
274 | 0 | requireValueDefinedForInstruction(name, value); |
275 | 0 | result = {AssemblyItemType::Push, u256("0x" + value)}; |
276 | 0 | } |
277 | 0 | else if (name == "PUSH [ErrorTag]") |
278 | 0 | { |
279 | 0 | requireValueUndefinedForInstruction(name, value); |
280 | 0 | result = {AssemblyItemType::PushTag, 0}; |
281 | 0 | } |
282 | 0 | else if (name == "PUSH [tag]") |
283 | 0 | { |
284 | 0 | requireValueDefinedForInstruction(name, value); |
285 | 0 | result = {AssemblyItemType::PushTag, u256(value)}; |
286 | | // The value has the sub-assembly ID packed above the tag ID, so only the latter is range-checked. |
287 | 0 | updateUsedTags(requireTagIDInRange(result.splitForeignPushTag().second)); |
288 | 0 | } |
289 | 0 | else if (name == "PUSH [$]") |
290 | 0 | { |
291 | 0 | requireValueDefinedForInstruction(name, value); |
292 | 0 | result = {AssemblyItemType::PushSub, u256("0x" + value)}; |
293 | 0 | } |
294 | 0 | else if (name == "PUSH #[$]") |
295 | 0 | { |
296 | 0 | requireValueDefinedForInstruction(name, value); |
297 | 0 | result = {AssemblyItemType::PushSubSize, u256("0x" + value)}; |
298 | 0 | } |
299 | 0 | else if (name == "PUSHSIZE") |
300 | 0 | { |
301 | 0 | requireValueUndefinedForInstruction(name, value); |
302 | 0 | result = {AssemblyItemType::PushProgramSize, 0}; |
303 | 0 | } |
304 | 0 | else if (name == "PUSHLIB") |
305 | 0 | { |
306 | 0 | requireValueDefinedForInstruction(name, value); |
307 | 0 | result = {AssemblyItemType::PushLibraryAddress, storeLibraryHash(value)}; |
308 | 0 | } |
309 | 0 | else if (name == "PUSHDEPLOYADDRESS") |
310 | 0 | { |
311 | 0 | requireValueUndefinedForInstruction(name, value); |
312 | 0 | result = {AssemblyItemType::PushDeployTimeAddress, 0}; |
313 | 0 | } |
314 | 0 | else if (name == "PUSHIMMUTABLE") |
315 | 0 | { |
316 | 0 | requireValueDefinedForInstruction(name, value); |
317 | 0 | result = {AssemblyItemType::PushImmutable, storeImmutableHash(value)}; |
318 | 0 | } |
319 | 0 | else if (name == "ASSIGNIMMUTABLE") |
320 | 0 | { |
321 | 0 | requireValueDefinedForInstruction(name, value); |
322 | 0 | result = {AssemblyItemType::AssignImmutable, storeImmutableHash(value)}; |
323 | 0 | } |
324 | 0 | else if (name == "tag") |
325 | 0 | { |
326 | 0 | requireValueDefinedForInstruction(name, value); |
327 | 0 | result = {AssemblyItemType::Tag, updateUsedTags(requireTagIDInRange(u256(value)))}; |
328 | 0 | } |
329 | 0 | else if (name == "PUSH data") |
330 | 0 | { |
331 | 0 | requireValueDefinedForInstruction(name, value); |
332 | 0 | result = {AssemblyItemType::PushData, u256("0x" + value)}; |
333 | 0 | } |
334 | 0 | else if (name == "VERBATIM") |
335 | 0 | { |
336 | 0 | requireValueDefinedForInstruction(name, value); |
337 | 0 | AssemblyItem item(fromHex(value), 0, 0); |
338 | 0 | result = item; |
339 | 0 | } |
340 | 0 | else |
341 | 0 | solThrow(AssemblyImportException, "Invalid opcode (" + name + ")"); |
342 | 0 | } |
343 | 0 | result.setLocation(location); |
344 | 0 | result.m_modifierDepth = modifierDepth; |
345 | 0 | return result; |
346 | 0 | } |
347 | | |
348 | | namespace |
349 | | { |
350 | | |
351 | | std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) |
352 | 0 | { |
353 | 0 | if (!_location.hasText() || _sourceCodes.empty()) |
354 | 0 | return {}; |
355 | | |
356 | 0 | auto it = _sourceCodes.find(*_location.sourceName); |
357 | 0 | if (it == _sourceCodes.end()) |
358 | 0 | return {}; |
359 | | |
360 | 0 | return CharStream::singleLineSnippet(it->second, _location); |
361 | 0 | } |
362 | | |
363 | | class Functionalizer |
364 | | { |
365 | | public: |
366 | | Functionalizer (std::ostream& _out, std::string const& _prefix, StringMap const& _sourceCodes, Assembly const& _assembly): |
367 | 0 | m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly) |
368 | 0 | {} |
369 | | |
370 | | void feed(AssemblyItem const& _item, DebugInfoSelection const& _debugInfoSelection) |
371 | 0 | { |
372 | 0 | if (_item.location().isValid() && _item.location() != m_location) |
373 | 0 | { |
374 | 0 | flush(); |
375 | 0 | m_location = _item.location(); |
376 | 0 | printLocation(_debugInfoSelection); |
377 | 0 | } |
378 | |
|
379 | 0 | std::string expression = _item.toAssemblyText(m_assembly); |
380 | |
|
381 | 0 | if (!( |
382 | 0 | _item.canBeFunctional() && |
383 | 0 | _item.returnValues() <= 1 && |
384 | 0 | _item.arguments() <= m_pending.size() |
385 | 0 | )) |
386 | 0 | { |
387 | 0 | flush(); |
388 | 0 | m_out << m_prefix << (_item.type() == Tag ? "" : " ") << expression << std::endl; |
389 | 0 | return; |
390 | 0 | } |
391 | 0 | if (_item.arguments() > 0) |
392 | 0 | { |
393 | 0 | expression += "("; |
394 | 0 | for (size_t i = 0; i < _item.arguments(); ++i) |
395 | 0 | { |
396 | 0 | expression += m_pending.back(); |
397 | 0 | m_pending.pop_back(); |
398 | 0 | if (i + 1 < _item.arguments()) |
399 | 0 | expression += ", "; |
400 | 0 | } |
401 | 0 | expression += ")"; |
402 | 0 | } |
403 | |
|
404 | 0 | m_pending.push_back(expression); |
405 | 0 | if (_item.returnValues() != 1) |
406 | 0 | flush(); |
407 | 0 | } |
408 | | |
409 | | void flush() |
410 | 0 | { |
411 | 0 | for (std::string const& expression: m_pending) |
412 | 0 | m_out << m_prefix << " " << expression << std::endl; |
413 | 0 | m_pending.clear(); |
414 | 0 | } |
415 | | |
416 | | void printLocation(DebugInfoSelection const& _debugInfoSelection) |
417 | 0 | { |
418 | 0 | if (!m_location.isValid() || (!_debugInfoSelection.location && !_debugInfoSelection.snippet)) |
419 | 0 | return; |
420 | | |
421 | 0 | m_out << m_prefix << " /*"; |
422 | |
|
423 | 0 | if (_debugInfoSelection.location) |
424 | 0 | { |
425 | 0 | if (m_location.sourceName) |
426 | 0 | m_out << " " + escapeAndQuoteString(*m_location.sourceName); |
427 | 0 | if (m_location.hasText()) |
428 | 0 | m_out << ":" << std::to_string(m_location.start) + ":" + std::to_string(m_location.end); |
429 | 0 | } |
430 | |
|
431 | 0 | if (_debugInfoSelection.snippet) |
432 | 0 | { |
433 | 0 | if (_debugInfoSelection.location) |
434 | 0 | m_out << " "; |
435 | |
|
436 | 0 | m_out << locationFromSources(m_sourceCodes, m_location); |
437 | 0 | } |
438 | |
|
439 | 0 | m_out << " */" << std::endl; |
440 | 0 | } |
441 | | |
442 | | private: |
443 | | strings m_pending; |
444 | | SourceLocation m_location; |
445 | | |
446 | | std::ostream& m_out; |
447 | | std::string const& m_prefix; |
448 | | StringMap const& m_sourceCodes; |
449 | | Assembly const& m_assembly; |
450 | | }; |
451 | | |
452 | | } |
453 | | |
454 | | void Assembly::assemblyStream( |
455 | | std::ostream& _out, |
456 | | DebugInfoSelection const& _debugInfoSelection, |
457 | | std::string const& _prefix, |
458 | | StringMap const& _sourceCodes |
459 | | ) const |
460 | 0 | { |
461 | 0 | Functionalizer f(_out, _prefix, _sourceCodes, *this); |
462 | |
|
463 | 0 | for (auto const& i: m_items) |
464 | 0 | f.feed(i, _debugInfoSelection); |
465 | 0 | f.flush(); |
466 | |
|
467 | 0 | if (!m_data.empty() || !m_subs.empty()) |
468 | 0 | { |
469 | 0 | _out << _prefix << "stop" << std::endl; |
470 | 0 | for (auto const& i: m_data) |
471 | 0 | if (u256(i.first) >= m_subs.size()) |
472 | 0 | _out << _prefix << "data_" << toHex(u256(i.first)) << " " << util::toHex(i.second) << std::endl; |
473 | |
|
474 | 0 | for (size_t i = 0; i < m_subs.size(); ++i) |
475 | 0 | { |
476 | 0 | _out << std::endl << _prefix << "sub_" << i << ": assembly {\n"; |
477 | 0 | m_subs[i]->assemblyStream(_out, _debugInfoSelection, _prefix + " ", _sourceCodes); |
478 | 0 | _out << _prefix << "}" << std::endl; |
479 | 0 | } |
480 | 0 | } |
481 | |
|
482 | 0 | if (m_auxiliaryData.size() > 0) |
483 | 0 | _out << std::endl << _prefix << "auxdata: 0x" << util::toHex(m_auxiliaryData) << std::endl; |
484 | 0 | } |
485 | | |
486 | | std::string Assembly::assemblyString( |
487 | | DebugInfoSelection const& _debugInfoSelection, |
488 | | StringMap const& _sourceCodes |
489 | | ) const |
490 | 0 | { |
491 | 0 | std::ostringstream tmp; |
492 | 0 | assemblyStream(tmp, _debugInfoSelection, "", _sourceCodes); |
493 | 0 | return (_debugInfoSelection.ethdebug ? "/// ethdebug: enabled\n" : "") + tmp.str(); |
494 | 0 | } |
495 | | |
496 | | Json Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourceIndices, bool _includeSourceList) const |
497 | 0 | { |
498 | 0 | Json root; |
499 | 0 | root[".code"] = Json::array(); |
500 | 0 | Json& code = root[".code"]; |
501 | 0 | for (AssemblyItem const& item: m_items) |
502 | 0 | { |
503 | 0 | int sourceIndex = -1; |
504 | 0 | if (item.location().sourceName) |
505 | 0 | { |
506 | 0 | auto iter = _sourceIndices.find(*item.location().sourceName); |
507 | 0 | if (iter != _sourceIndices.end()) |
508 | 0 | sourceIndex = static_cast<int>(iter->second); |
509 | 0 | } |
510 | |
|
511 | 0 | auto [name, data] = item.nameAndData(m_evmVersion); |
512 | 0 | Json jsonItem; |
513 | 0 | jsonItem["name"] = name; |
514 | 0 | jsonItem["begin"] = item.location().start; |
515 | 0 | jsonItem["end"] = item.location().end; |
516 | 0 | if (item.m_modifierDepth != 0) |
517 | 0 | jsonItem["modifierDepth"] = static_cast<int>(item.m_modifierDepth); |
518 | 0 | std::string jumpType = item.getJumpTypeAsString(); |
519 | 0 | if (!jumpType.empty()) |
520 | 0 | jsonItem["jumpType"] = jumpType; |
521 | 0 | if (name == "PUSHLIB") |
522 | 0 | data = m_libraries.at(h256(data)); |
523 | 0 | else if (name == "PUSHIMMUTABLE" || name == "ASSIGNIMMUTABLE") |
524 | 0 | data = m_immutables.at(h256(data)); |
525 | 0 | if (!data.empty()) |
526 | 0 | jsonItem["value"] = data; |
527 | 0 | jsonItem["source"] = sourceIndex; |
528 | 0 | code.emplace_back(std::move(jsonItem)); |
529 | |
|
530 | 0 | if (item.type() == AssemblyItemType::Tag) |
531 | 0 | { |
532 | 0 | Json jumpdest; |
533 | 0 | jumpdest["name"] = "JUMPDEST"; |
534 | 0 | jumpdest["begin"] = item.location().start; |
535 | 0 | jumpdest["end"] = item.location().end; |
536 | 0 | jumpdest["source"] = sourceIndex; |
537 | 0 | if (item.m_modifierDepth != 0) |
538 | 0 | jumpdest["modifierDepth"] = static_cast<int>(item.m_modifierDepth); |
539 | 0 | code.emplace_back(std::move(jumpdest)); |
540 | 0 | } |
541 | 0 | } |
542 | 0 | if (_includeSourceList) |
543 | 0 | { |
544 | 0 | root["sourceList"] = Json::array(); |
545 | 0 | Json& jsonSourceList = root["sourceList"]; |
546 | 0 | unsigned maxSourceIndex = 0; |
547 | 0 | for (auto const& [sourceName, sourceIndex]: _sourceIndices) |
548 | 0 | { |
549 | 0 | maxSourceIndex = std::max(sourceIndex, maxSourceIndex); |
550 | 0 | jsonSourceList[sourceIndex] = sourceName; |
551 | 0 | } |
552 | 0 | solAssert(maxSourceIndex + 1 >= _sourceIndices.size()); |
553 | 0 | solRequire( |
554 | 0 | _sourceIndices.size() == 0 || _sourceIndices.size() == maxSourceIndex + 1, |
555 | 0 | AssemblyImportException, |
556 | 0 | "The 'sourceList' array contains invalid 'null' item." |
557 | 0 | ); |
558 | 0 | } |
559 | |
|
560 | 0 | if (!m_data.empty() || !m_subs.empty()) |
561 | 0 | { |
562 | 0 | root[".data"] = Json::object(); |
563 | 0 | Json& data = root[".data"]; |
564 | 0 | for (auto const& i: m_data) |
565 | 0 | if (u256(i.first) >= m_subs.size()) |
566 | 0 | data[util::toHex(toBigEndian((u256)i.first), util::HexPrefix::DontAdd, util::HexCase::Upper)] = util::toHex(i.second); |
567 | |
|
568 | 0 | for (size_t i = 0; i < m_subs.size(); ++i) |
569 | 0 | { |
570 | 0 | std::stringstream hexStr; |
571 | 0 | hexStr << std::hex << i; |
572 | 0 | data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices, /*_includeSourceList = */false); |
573 | 0 | } |
574 | 0 | } |
575 | |
|
576 | 0 | if (!m_auxiliaryData.empty()) |
577 | 0 | root[".auxdata"] = util::toHex(m_auxiliaryData); |
578 | |
|
579 | 0 | return root; |
580 | 0 | } |
581 | | |
582 | | std::pair<std::shared_ptr<Assembly>, std::vector<std::string>> Assembly::fromJSON( |
583 | | Json const& _json, |
584 | | std::vector<std::string> const& _sourceList, |
585 | | size_t _level |
586 | | ) |
587 | 0 | { |
588 | 0 | solRequire(_json.is_object(), AssemblyImportException, "Supplied JSON is not an object."); |
589 | 0 | static std::set<std::string> const validMembers{".code", ".data", ".auxdata", "sourceList"}; |
590 | 0 | for (auto const& [attribute, _]: _json.items()) |
591 | 0 | solRequire(validMembers.count(attribute), AssemblyImportException, "Unknown attribute '" + attribute + "'."); |
592 | |
|
593 | 0 | if (_level == 0) |
594 | 0 | { |
595 | 0 | if (_json.contains("sourceList")) |
596 | 0 | { |
597 | 0 | solRequire(_json["sourceList"].is_array(), AssemblyImportException, "Optional member 'sourceList' is not an array."); |
598 | 0 | for (Json const& sourceName: _json["sourceList"]) |
599 | 0 | { |
600 | 0 | solRequire(!sourceName.is_null(), AssemblyImportException, "The 'sourceList' array contains invalid 'null' item."); |
601 | 0 | solRequire( |
602 | 0 | sourceName.is_string(), |
603 | 0 | AssemblyImportException, |
604 | 0 | "The 'sourceList' array contains an item that is not a string." |
605 | 0 | ); |
606 | 0 | } |
607 | 0 | } |
608 | 0 | } |
609 | 0 | else |
610 | 0 | solRequire( |
611 | 0 | !_json.contains("sourceList"), |
612 | 0 | AssemblyImportException, |
613 | 0 | "Member 'sourceList' may only be present in the root JSON object." |
614 | 0 | ); |
615 | |
|
616 | 0 | auto result = std::make_shared<Assembly>(EVMVersion{}, _level == 0 /* _creation */, "" /* _name */); |
617 | 0 | std::vector<std::string> parsedSourceList; |
618 | 0 | if (_json.contains("sourceList")) |
619 | 0 | { |
620 | 0 | solAssert(_level == 0); |
621 | 0 | solAssert(_sourceList.empty()); |
622 | 0 | for (Json const& sourceName: _json["sourceList"]) |
623 | 0 | { |
624 | 0 | solRequire( |
625 | 0 | std::find(parsedSourceList.begin(), parsedSourceList.end(), sourceName.get<std::string>()) == parsedSourceList.end(), |
626 | 0 | AssemblyImportException, |
627 | 0 | "Items in 'sourceList' array are not unique." |
628 | 0 | ); |
629 | 0 | parsedSourceList.emplace_back(sourceName.get<std::string>()); |
630 | 0 | } |
631 | 0 | } |
632 | |
|
633 | 0 | solRequire(_json.contains(".code"), AssemblyImportException, "Member '.code' is missing."); |
634 | 0 | solRequire(_json[".code"].is_array(), AssemblyImportException, "Member '.code' is not an array."); |
635 | 0 | for (Json const& codeItem: _json[".code"]) |
636 | 0 | solRequire(codeItem.is_object(), AssemblyImportException, "The '.code' array contains an item that is not an object."); |
637 | |
|
638 | 0 | result->importAssemblyItemsFromJSON(_json[".code"], _level == 0 ? parsedSourceList : _sourceList); |
639 | |
|
640 | 0 | if (_json.contains(".auxdata")) |
641 | 0 | { |
642 | 0 | solRequire(_json[".auxdata"].is_string(), AssemblyImportException, "Optional member '.auxdata' is not a string."); |
643 | 0 | result->m_auxiliaryData = fromHex(_json[".auxdata"].get<std::string>()); |
644 | 0 | solRequire(!result->m_auxiliaryData.empty(), AssemblyImportException, "Optional member '.auxdata' is not a valid hexadecimal string."); |
645 | 0 | } |
646 | |
|
647 | 0 | if (_json.contains(".data")) |
648 | 0 | { |
649 | 0 | solRequire(_json[".data"].is_object(), AssemblyImportException, "Optional member '.data' is not an object."); |
650 | 0 | Json const& data = _json[".data"]; |
651 | 0 | std::map<size_t, std::shared_ptr<Assembly>> subAssemblies; |
652 | 0 | for (auto const& [key, value] : data.items()) |
653 | 0 | { |
654 | 0 | if (value.is_string()) |
655 | 0 | { |
656 | 0 | solRequire( |
657 | 0 | value.get<std::string>().empty() || !fromHex(value.get<std::string>()).empty(), |
658 | 0 | AssemblyImportException, |
659 | 0 | "The value for key '" + key + "' inside '.data' is not a valid hexadecimal string." |
660 | 0 | ); |
661 | 0 | result->m_data[h256(fromHex(key))] = fromHex(value.get<std::string>()); |
662 | 0 | } |
663 | 0 | else if (value.is_object()) |
664 | 0 | { |
665 | 0 | size_t index{}; |
666 | 0 | try |
667 | 0 | { |
668 | | // Using signed variant because stoul() still accepts negative numbers and |
669 | | // just lets them wrap around. |
670 | 0 | int parsedDataItemID = std::stoi(key, nullptr, 16); |
671 | 0 | solRequire(parsedDataItemID >= 0, AssemblyImportException, "The key '" + key + "' inside '.data' is out of the supported integer range."); |
672 | 0 | index = static_cast<size_t>(parsedDataItemID); |
673 | 0 | } |
674 | 0 | catch (std::invalid_argument const&) |
675 | 0 | { |
676 | 0 | solThrow(AssemblyImportException, "The key '" + key + "' inside '.data' is not an integer."); |
677 | 0 | } |
678 | 0 | catch (std::out_of_range const&) |
679 | 0 | { |
680 | 0 | solThrow(AssemblyImportException, "The key '" + key + "' inside '.data' is out of the supported integer range."); |
681 | 0 | } |
682 | |
|
683 | 0 | auto [subAssembly, emptySourceList] = Assembly::fromJSON(value, _level == 0 ? parsedSourceList : _sourceList, _level + 1); |
684 | 0 | solAssert(subAssembly); |
685 | 0 | solAssert(emptySourceList.empty()); |
686 | 0 | solAssert(subAssemblies.count(index) == 0); |
687 | 0 | subAssemblies[index] = subAssembly; |
688 | 0 | } |
689 | 0 | else |
690 | 0 | solThrow(AssemblyImportException, "The value of key '" + key + "' inside '.data' is neither a hex string nor an object."); |
691 | 0 | } |
692 | |
|
693 | 0 | if (!subAssemblies.empty()) |
694 | 0 | solRequire( |
695 | 0 | ranges::max(subAssemblies | ranges::views::keys) == subAssemblies.size() - 1, |
696 | 0 | AssemblyImportException, |
697 | 0 | fmt::format( |
698 | 0 | "Invalid subassembly indices in '.data'. Not all numbers between 0 and {} are present.", |
699 | 0 | subAssemblies.size() - 1 |
700 | 0 | ) |
701 | 0 | ); |
702 | |
|
703 | 0 | result->m_subs = subAssemblies | ranges::views::values | ranges::to<std::vector>; |
704 | 0 | } |
705 | |
|
706 | 0 | if (_level == 0) |
707 | 0 | result->encodeAllPossibleSubPathsInAssemblyTree(); |
708 | |
|
709 | 0 | return std::make_pair(result, _level == 0 ? parsedSourceList : std::vector<std::string>{}); |
710 | 0 | } |
711 | | |
712 | | void Assembly::encodeAllPossibleSubPathsInAssemblyTree(std::vector<SubAssemblyID> _pathFromRoot, std::vector<Assembly*> _assembliesOnPath) |
713 | 0 | { |
714 | 0 | _assembliesOnPath.push_back(this); |
715 | 0 | for (_pathFromRoot.push_back(SubAssemblyID{0}); _pathFromRoot.back().value < m_subs.size(); ++_pathFromRoot.back().value) |
716 | 0 | { |
717 | 0 | for (size_t distanceFromRoot = 0; distanceFromRoot < _assembliesOnPath.size(); ++distanceFromRoot) |
718 | 0 | _assembliesOnPath[distanceFromRoot]->encodeSubPath( |
719 | 0 | _pathFromRoot | ranges::views::drop_exactly(distanceFromRoot) | ranges::to<std::vector> |
720 | 0 | ); |
721 | |
|
722 | 0 | m_subs[_pathFromRoot.back().asIndex()]->encodeAllPossibleSubPathsInAssemblyTree(_pathFromRoot, _assembliesOnPath); |
723 | 0 | } |
724 | 0 | } |
725 | | |
726 | | std::shared_ptr<std::string const> Assembly::sharedSourceName(std::string const& _name) const |
727 | 0 | { |
728 | 0 | if (s_sharedSourceNames.find(_name) == s_sharedSourceNames.end()) |
729 | 0 | s_sharedSourceNames[_name] = std::make_shared<std::string>(_name); |
730 | |
|
731 | 0 | return s_sharedSourceNames[_name]; |
732 | 0 | } |
733 | | |
734 | | AssemblyItem Assembly::namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID) |
735 | 546k | { |
736 | 546k | assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); |
737 | 546k | if (m_namedTags.count(_name)) |
738 | 190k | { |
739 | 190k | assertThrow(m_namedTags.at(_name).params == _params, AssemblyException, ""); |
740 | 190k | assertThrow(m_namedTags.at(_name).returns == _returns, AssemblyException, ""); |
741 | 190k | assertThrow(m_namedTags.at(_name).sourceID == _sourceID, AssemblyException, ""); |
742 | 190k | } |
743 | 355k | else |
744 | 355k | m_namedTags[_name] = {static_cast<size_t>(newTag().data()), _sourceID, _params, _returns}; |
745 | 546k | return AssemblyItem{Tag, m_namedTags.at(_name).id}; |
746 | 546k | } |
747 | | |
748 | | AssemblyItem Assembly::newPushLibraryAddress(std::string const& _identifier) |
749 | 1.56k | { |
750 | 1.56k | h256 h(util::keccak256(_identifier)); |
751 | 1.56k | m_libraries[h] = _identifier; |
752 | 1.56k | return AssemblyItem{PushLibraryAddress, h}; |
753 | 1.56k | } |
754 | | |
755 | | AssemblyItem Assembly::newPushImmutable(std::string const& _identifier) |
756 | 296 | { |
757 | 296 | h256 h(util::keccak256(_identifier)); |
758 | 296 | m_immutables[h] = _identifier; |
759 | 296 | return AssemblyItem{PushImmutable, h}; |
760 | 296 | } |
761 | | |
762 | | AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier) |
763 | 519 | { |
764 | 519 | h256 h(util::keccak256(_identifier)); |
765 | 519 | m_immutables[h] = _identifier; |
766 | 519 | return AssemblyItem{AssignImmutable, h}; |
767 | 519 | } |
768 | | |
769 | | Assembly& Assembly::optimise(OptimiserSettings const& _settings) |
770 | 50.4k | { |
771 | 50.4k | optimiseInternal(_settings, {}); |
772 | 50.4k | return *this; |
773 | 50.4k | } |
774 | | |
775 | | std::map<u256, u256> const& Assembly::optimiseInternal( |
776 | | OptimiserSettings const& _settings, |
777 | | std::set<size_t> _tagsReferencedFromOutside |
778 | | ) |
779 | 71.9k | { |
780 | 71.9k | if (m_tagReplacements) |
781 | 212 | return *m_tagReplacements; |
782 | | |
783 | | // Run optimisation for sub-assemblies. |
784 | 93.1k | for (SubAssemblyID subID{0}; subID.value < m_subs.size(); ++subID.value) |
785 | 21.4k | { |
786 | 21.4k | Assembly& sub = *m_subs[subID.asIndex()]; |
787 | 21.4k | std::map<u256, u256> const& subTagReplacements = sub.optimiseInternal( |
788 | 21.4k | _settings, |
789 | 21.4k | JumpdestRemover::referencedTags(m_items, subID) |
790 | 21.4k | ); |
791 | | // Apply the replacements (can be empty). |
792 | 21.4k | BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subID); |
793 | 21.4k | } |
794 | | |
795 | 71.6k | std::map<u256, u256> tagReplacements; |
796 | | // Iterate until no new optimisation possibilities are found. |
797 | 270k | for (unsigned count = 1; count > 0;) |
798 | 198k | { |
799 | 198k | count = 0; |
800 | | |
801 | 198k | if (_settings.runInliner) |
802 | 98.0k | { |
803 | 98.0k | Inliner{ |
804 | 98.0k | m_items, |
805 | 98.0k | _tagsReferencedFromOutside, |
806 | 98.0k | _settings.expectedExecutionsPerDeployment, |
807 | 98.0k | isCreation(), |
808 | 98.0k | m_evmVersion |
809 | 98.0k | }.optimise(); |
810 | 98.0k | } |
811 | 198k | if (_settings.runJumpdestRemover) |
812 | 198k | { |
813 | 198k | JumpdestRemover jumpdestOpt{m_items}; |
814 | 198k | if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) |
815 | 94.6k | count++; |
816 | 198k | } |
817 | | |
818 | 198k | if (_settings.runPeephole) |
819 | 198k | { |
820 | 198k | PeepholeOptimiser peepOpt{m_items, m_evmVersion}; |
821 | 346k | while (peepOpt.optimise()) |
822 | 147k | { |
823 | 147k | count++; |
824 | 147k | assertThrow(count < 64000, OptimizerException, "Peephole optimizer seems to be stuck."); |
825 | 147k | } |
826 | 198k | } |
827 | | |
828 | | // This only modifies PushTags, we have to run again to actually remove code. |
829 | 198k | if (_settings.runDeduplicate) |
830 | 98.0k | { |
831 | 98.0k | BlockDeduplicator deduplicator{m_items}; |
832 | 98.0k | if (deduplicator.deduplicate()) |
833 | 8.07k | { |
834 | 8.07k | for (auto const& replacement: deduplicator.replacedTags()) |
835 | 87.5k | { |
836 | 87.5k | assertThrow( |
837 | 87.5k | replacement.first <= std::numeric_limits<size_t>::max() && replacement.second <= std::numeric_limits<size_t>::max(), |
838 | 87.5k | OptimizerException, |
839 | 87.5k | "Invalid tag replacement." |
840 | 87.5k | ); |
841 | 87.5k | assertThrow( |
842 | 87.5k | !tagReplacements.count(replacement.first), |
843 | 87.5k | OptimizerException, |
844 | 87.5k | "Replacement already known." |
845 | 87.5k | ); |
846 | 87.5k | tagReplacements[replacement.first] = replacement.second; |
847 | 87.5k | if (_tagsReferencedFromOutside.erase(static_cast<size_t>(replacement.first))) |
848 | 0 | _tagsReferencedFromOutside.insert(static_cast<size_t>(replacement.second)); |
849 | 87.5k | } |
850 | 8.07k | count++; |
851 | 8.07k | } |
852 | 98.0k | } |
853 | | |
854 | 198k | if (_settings.runCSE) |
855 | 98.0k | { |
856 | | // Control flow graph optimization has been here before but is disabled because it |
857 | | // assumes we only jump to tags that are pushed. This is not the case anymore with |
858 | | // function types that can be stored in storage. |
859 | 98.0k | AssemblyItems optimisedItems; |
860 | | |
861 | 19.1M | bool usesMSize = ranges::any_of(m_items, [](AssemblyItem const& _i) { |
862 | 19.1M | return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode; |
863 | 19.1M | }); |
864 | | |
865 | 98.0k | auto iter = m_items.begin(); |
866 | 3.48M | while (iter != m_items.end()) |
867 | 3.39M | { |
868 | 3.39M | KnownState emptyState; |
869 | 3.39M | CommonSubexpressionEliminator eliminator{emptyState, m_evmVersion}; |
870 | 3.39M | auto orig = iter; |
871 | 3.39M | iter = eliminator.feedItems(iter, m_items.end(), usesMSize); |
872 | 3.39M | bool shouldReplace = false; |
873 | 3.39M | AssemblyItems optimisedChunk; |
874 | 3.39M | try |
875 | 3.39M | { |
876 | 3.39M | optimisedChunk = eliminator.getOptimizedItems(); |
877 | 3.39M | shouldReplace = (optimisedChunk.size() < static_cast<size_t>(iter - orig)); |
878 | 3.39M | } |
879 | 3.39M | catch (StackTooDeepException const&) |
880 | 3.39M | { |
881 | | // This might happen if the opcode reconstruction is not as efficient |
882 | | // as the hand-crafted code. |
883 | 2.43k | } |
884 | 3.39M | catch (ItemNotAvailableException const&) |
885 | 3.39M | { |
886 | | // This might happen if e.g. associativity and commutativity rules |
887 | | // reorganise the expression tree, but not all leaves are available. |
888 | 0 | } |
889 | | |
890 | 3.39M | if (shouldReplace) |
891 | 148k | { |
892 | 148k | count++; |
893 | 148k | optimisedItems += optimisedChunk; |
894 | 148k | } |
895 | 3.24M | else |
896 | 3.24M | copy(orig, iter, back_inserter(optimisedItems)); |
897 | 3.39M | } |
898 | 98.0k | if (optimisedItems.size() < m_items.size()) |
899 | 37.4k | { |
900 | 37.4k | m_items = std::move(optimisedItems); |
901 | 37.4k | count++; |
902 | 37.4k | } |
903 | 98.0k | } |
904 | 198k | } |
905 | | |
906 | 71.6k | if (_settings.runConstantOptimiser) |
907 | 31.9k | ConstantOptimisationMethod::optimiseConstants( |
908 | 31.9k | isCreation(), |
909 | 31.9k | isCreation() ? 1 : _settings.expectedExecutionsPerDeployment, |
910 | 31.9k | m_evmVersion, |
911 | 31.9k | *this |
912 | 31.9k | ); |
913 | | |
914 | 71.6k | m_tagReplacements = std::move(tagReplacements); |
915 | 71.6k | return *m_tagReplacements; |
916 | 71.6k | } |
917 | | |
918 | | namespace |
919 | | { |
920 | | template<typename ValueT> |
921 | | void setBigEndian(bytes& _dest, size_t _offset, size_t _size, ValueT _value) |
922 | 3.00M | { |
923 | 3.00M | assertThrow(numberEncodingSize(_value) <= _size, AssemblyException, ""); |
924 | 3.00M | toBigEndian(_value, bytesRef(_dest.data() + _offset, _size)); |
925 | 3.00M | } |
926 | | |
927 | | template<typename ValueT> |
928 | | void appendBigEndian(bytes& _dest, size_t _size, ValueT _value) |
929 | 3.00M | { |
930 | 3.00M | _dest.resize(_dest.size() + _size); |
931 | 3.00M | setBigEndian(_dest, _dest.size() - _size, _size, _value); |
932 | 3.00M | } |
933 | | } |
934 | | |
935 | | LinkerObject const& Assembly::assemble() const |
936 | 155k | { |
937 | 155k | solRequire(!m_invalid, AssemblyException, "Attempted to assemble invalid Assembly object."); |
938 | | // Return the already assembled object, if present. |
939 | 155k | if (!m_assembledObject.bytecode.empty()) |
940 | 77.1k | return m_assembledObject; |
941 | | |
942 | | // Otherwise ensure the object is actually clear. |
943 | 78.0k | solRequire(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references."); |
944 | | |
945 | 78.0k | return assembleLegacy(); |
946 | 155k | } |
947 | | |
948 | | [[nodiscard]] bytes Assembly::assembleOperation(AssemblyItem const& _item) const |
949 | 11.1M | { |
950 | | // solidity::evmasm::Instructions underlying type is uint8_t |
951 | | // TODO: Change to std::to_underlying since C++23 |
952 | 11.1M | return {static_cast<uint8_t>(_item.instruction())}; |
953 | 11.1M | } |
954 | | |
955 | | [[nodiscard]] bytes Assembly::assemblePush(AssemblyItem const& _item) const |
956 | 3.74M | { |
957 | 3.74M | bytes ret; |
958 | 3.74M | unsigned pushValueSize = numberEncodingSize(_item.data()); |
959 | 3.74M | if (pushValueSize == 0 && !m_evmVersion.hasPush0()) |
960 | 237k | pushValueSize = 1; |
961 | | |
962 | | // solidity::evmasm::Instructions underlying type is uint8_t |
963 | | // TODO: Change to std::to_underlying since C++23 |
964 | 3.74M | ret.push_back(static_cast<uint8_t>(pushInstruction(pushValueSize))); |
965 | 3.74M | if (pushValueSize > 0) |
966 | 3.00M | appendBigEndian(ret, pushValueSize, _item.data()); |
967 | | |
968 | 3.74M | return ret; |
969 | 3.74M | } |
970 | | |
971 | | [[nodiscard]] std::pair<bytes, Assembly::LinkRef> Assembly::assemblePushLibraryAddress(AssemblyItem const& _item, size_t _pos) const |
972 | 1.40k | { |
973 | 1.40k | return { |
974 | | // solidity::evmasm::Instructions underlying type is uint8_t |
975 | | // TODO: Change to std::to_underlying since C++23 |
976 | 1.40k | bytes(1, static_cast<uint8_t>(Instruction::PUSH20)) + bytes(20), |
977 | 1.40k | {_pos + 1, m_libraries.at(_item.data())} |
978 | 1.40k | }; |
979 | 1.40k | } |
980 | | |
981 | | [[nodiscard]] bytes Assembly::assembleVerbatimBytecode(AssemblyItem const& item) const |
982 | 844 | { |
983 | 844 | return item.verbatimData(); |
984 | 844 | } |
985 | | |
986 | | [[nodiscard]] bytes Assembly::assemblePushDeployTimeAddress() const |
987 | 467 | { |
988 | | // solidity::evmasm::Instructions underlying type is uint8_t |
989 | | // TODO: Change to std::to_underlying since C++23 |
990 | 467 | return bytes(1, static_cast<uint8_t>(Instruction::PUSH20)) + bytes(20); |
991 | 467 | } |
992 | | |
993 | | [[nodiscard]] bytes Assembly::assembleTag(AssemblyItem const& _item, size_t _pos, bool _addJumpDest) const |
994 | 1.77M | { |
995 | 1.77M | solRequire(_item.data() != 0, AssemblyException, "Invalid tag position."); |
996 | 1.77M | solRequire(_item.splitForeignPushTag().first.empty(), AssemblyException, "Foreign tag."); |
997 | 1.77M | solRequire(_pos < 0xffffffffL, AssemblyException, "Tag too large."); |
998 | 1.77M | size_t tagId = static_cast<size_t>(_item.data()); |
999 | 1.77M | solRequire(m_tagPositionsInBytecode[tagId] == std::numeric_limits<size_t>::max(), AssemblyException, "Duplicate tag position."); |
1000 | 1.77M | m_tagPositionsInBytecode[tagId] = _pos; |
1001 | | |
1002 | | // solidity::evmasm::Instructions underlying type is uint8_t |
1003 | | // TODO: Change to std::to_underlying since C++23 |
1004 | 1.77M | return _addJumpDest ? bytes(1, static_cast<uint8_t>(Instruction::JUMPDEST)) : bytes(); |
1005 | 1.77M | } |
1006 | | |
1007 | | LinkerObject const& Assembly::assembleLegacy() const |
1008 | 78.0k | { |
1009 | 78.0k | solAssert(!m_invalid); |
1010 | | // Return the already assembled object, if present. |
1011 | 78.0k | if (!m_assembledObject.bytecode.empty()) |
1012 | 0 | return m_assembledObject; |
1013 | | // Otherwise ensure the object is actually clear. |
1014 | 78.0k | solAssert(m_assembledObject.linkReferences.empty()); |
1015 | | |
1016 | 78.0k | LinkerObject& ret = m_assembledObject; |
1017 | | |
1018 | 78.0k | size_t subTagSize = 1; |
1019 | 78.0k | std::map<u256, LinkerObject::ImmutableRefs> immutableReferencesBySub; |
1020 | 78.0k | for (auto const& sub: m_subs) |
1021 | 22.3k | { |
1022 | 22.3k | auto const& linkerObject = sub->assemble(); |
1023 | 22.3k | if (!linkerObject.immutableReferences.empty()) |
1024 | 240 | { |
1025 | 240 | assertThrow( |
1026 | 240 | immutableReferencesBySub.empty(), |
1027 | 240 | AssemblyException, |
1028 | 240 | "More than one sub-assembly references immutables." |
1029 | 240 | ); |
1030 | 240 | immutableReferencesBySub = linkerObject.immutableReferences; |
1031 | 240 | } |
1032 | 22.3k | for (size_t tagPos: sub->m_tagPositionsInBytecode) |
1033 | 1.49M | if (tagPos != std::numeric_limits<size_t>::max() && numberEncodingSize(tagPos) > subTagSize) |
1034 | 7.97k | subTagSize = numberEncodingSize(tagPos); |
1035 | 22.3k | } |
1036 | | |
1037 | 78.0k | bool setsImmutables = false; |
1038 | 78.0k | bool pushesImmutables = false; |
1039 | | |
1040 | 78.0k | for (auto const& item: m_items) |
1041 | 19.1M | if (item.type() == AssignImmutable) |
1042 | 519 | { |
1043 | 519 | item.setImmutableOccurrences(immutableReferencesBySub[item.data()].second.size()); |
1044 | 519 | setsImmutables = true; |
1045 | 519 | } |
1046 | 19.1M | else if (item.type() == PushImmutable) |
1047 | 411 | pushesImmutables = true; |
1048 | 78.0k | if (setsImmutables || pushesImmutables) |
1049 | 78.0k | assertThrow( |
1050 | 78.0k | setsImmutables != pushesImmutables, |
1051 | 78.0k | AssemblyException, |
1052 | 78.0k | "Cannot push and assign immutables in the same assembly subroutine." |
1053 | 78.0k | ); |
1054 | | |
1055 | 78.0k | unsigned bytesRequiredForCode = codeSize(static_cast<unsigned>(subTagSize)); |
1056 | 78.0k | m_tagPositionsInBytecode = std::vector<size_t>(m_usedTags, std::numeric_limits<size_t>::max()); |
1057 | 78.0k | unsigned bytesPerTag = numberEncodingSize(bytesRequiredForCode); |
1058 | | // Adjust bytesPerTag for references to sub assemblies. |
1059 | 78.0k | for (AssemblyItem const& item: m_items) |
1060 | 19.1M | if (item.type() == PushTag) |
1061 | 2.34M | { |
1062 | 2.34M | auto [subId, tagId] = item.splitForeignPushTag(); |
1063 | 2.34M | if (subId.empty()) |
1064 | 2.34M | continue; |
1065 | 98 | solAssert(subId.value < m_subs.size(), "Invalid sub id"); |
1066 | 98 | auto subTagPosition = m_subs[subId.asIndex()]->m_tagPositionsInBytecode.at(tagId); |
1067 | 98 | assertThrow(subTagPosition != std::numeric_limits<size_t>::max(), AssemblyException, "Reference to tag without position."); |
1068 | 98 | bytesPerTag = std::max(bytesPerTag, numberEncodingSize(subTagPosition)); |
1069 | 98 | } |
1070 | | |
1071 | 78.0k | unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + static_cast<unsigned>(m_auxiliaryData.size()); |
1072 | 78.0k | for (auto const& sub: m_subs) |
1073 | 22.3k | bytesRequiredIncludingData += static_cast<unsigned>(sub->assemble().bytecode.size()); |
1074 | | |
1075 | 78.0k | unsigned bytesPerDataRef = numberEncodingSize(bytesRequiredIncludingData); |
1076 | 78.0k | ret.bytecode.reserve(bytesRequiredIncludingData); |
1077 | | |
1078 | 78.0k | TagRefs tagRefs; |
1079 | 78.0k | DataRefs dataRefs; |
1080 | 78.0k | SubAssemblyRefs subRefs; |
1081 | 78.0k | ProgramSizeRefs sizeRefs; |
1082 | 78.0k | uint8_t tagPush = static_cast<uint8_t>(pushInstruction(bytesPerTag)); |
1083 | 78.0k | uint8_t dataRefPush = static_cast<uint8_t>(pushInstruction(bytesPerDataRef)); |
1084 | | |
1085 | 78.0k | LinkerObject::CodeSectionLocation codeSectionLocation; |
1086 | 78.0k | codeSectionLocation.instructionLocations.reserve(m_items.size()); |
1087 | 78.0k | codeSectionLocation.start = 0; |
1088 | 78.0k | for (auto const& [assemblyItemIndex, item]: m_items | ranges::views::enumerate) |
1089 | 19.1M | { |
1090 | | // collect instruction locations via side effects |
1091 | 19.1M | InstructionLocationEmitter instructionLocationEmitter(codeSectionLocation.instructionLocations, ret.bytecode, assemblyItemIndex); |
1092 | | // store position of the invalid jump destination |
1093 | 19.1M | if (item.type() != Tag && m_tagPositionsInBytecode[0] == std::numeric_limits<size_t>::max()) |
1094 | 76.3k | m_tagPositionsInBytecode[0] = ret.bytecode.size(); |
1095 | | |
1096 | 19.1M | switch (item.type()) |
1097 | 19.1M | { |
1098 | 11.1M | case Operation: |
1099 | 11.1M | ret.bytecode += assembleOperation(item); |
1100 | 11.1M | break; |
1101 | 3.74M | case Push: |
1102 | 3.74M | ret.bytecode += assemblePush(item); |
1103 | 3.74M | break; |
1104 | 2.34M | case PushTag: |
1105 | 2.34M | ret.bytecode.push_back(tagPush); |
1106 | 2.34M | tagRefs[ret.bytecode.size()] = item.splitForeignPushTag(); |
1107 | 2.34M | ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); |
1108 | 2.34M | break; |
1109 | 14.8k | case PushData: |
1110 | 14.8k | ret.bytecode.push_back(dataRefPush); |
1111 | 14.8k | dataRefs.insert(std::make_pair(h256(item.data()), ret.bytecode.size())); |
1112 | 14.8k | ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); |
1113 | 14.8k | break; |
1114 | 19.8k | case PushSub: |
1115 | 19.8k | solAssert(item.data() <= std::numeric_limits<SubAssemblyID::ValueType>::max()); |
1116 | 19.8k | ret.bytecode.push_back(dataRefPush); |
1117 | 19.8k | subRefs.emplace(SubAssemblyID{item.data()}, ret.bytecode.size()); |
1118 | 19.8k | ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); |
1119 | 19.8k | break; |
1120 | 21.6k | case PushSubSize: |
1121 | 21.6k | { |
1122 | 21.6k | solAssert(item.data() <= std::numeric_limits<SubAssemblyID::ValueType>::max()); |
1123 | 21.6k | auto s = subAssemblyById(SubAssemblyID{item.data()})->assemble().bytecode.size(); |
1124 | 21.6k | item.setPushedValue(u256(s)); |
1125 | 21.6k | unsigned b = std::max<unsigned>(1, numberEncodingSize(s)); |
1126 | 21.6k | ret.bytecode.push_back(static_cast<uint8_t>(pushInstruction(b))); |
1127 | 21.6k | ret.bytecode.resize(ret.bytecode.size() + b); |
1128 | 21.6k | bytesRef byr(&ret.bytecode.back() + 1 - b, b); |
1129 | 21.6k | toBigEndian(s, byr); |
1130 | 21.6k | break; |
1131 | 0 | } |
1132 | 950 | case PushProgramSize: |
1133 | 950 | ret.bytecode.push_back(dataRefPush); |
1134 | 950 | sizeRefs.push_back(static_cast<unsigned>(ret.bytecode.size())); |
1135 | 950 | ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); |
1136 | 950 | break; |
1137 | 1.40k | case PushLibraryAddress: |
1138 | 1.40k | { |
1139 | 1.40k | auto const [bytecode, linkRef] = assemblePushLibraryAddress(item, ret.bytecode.size()); |
1140 | 1.40k | ret.bytecode += bytecode; |
1141 | 1.40k | ret.linkReferences.insert(linkRef); |
1142 | 1.40k | break; |
1143 | 0 | } |
1144 | 411 | case PushImmutable: |
1145 | 411 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH32)); |
1146 | | // Maps keccak back to the "identifier" std::string of that immutable. |
1147 | 411 | ret.immutableReferences[item.data()].first = m_immutables.at(item.data()); |
1148 | | // Record the bytecode offset of the PUSH32 argument. |
1149 | 411 | ret.immutableReferences[item.data()].second.emplace_back(ret.bytecode.size()); |
1150 | | // Advance bytecode by 32 bytes (default initialized). |
1151 | 411 | ret.bytecode.resize(ret.bytecode.size() + 32); |
1152 | 411 | break; |
1153 | 844 | case VerbatimBytecode: |
1154 | 844 | ret.bytecode += assembleVerbatimBytecode(item); |
1155 | 844 | break; |
1156 | 519 | case AssignImmutable: |
1157 | 519 | { |
1158 | | // Expect 2 elements on stack (source, dest_base) |
1159 | 519 | auto const& offsets = immutableReferencesBySub[item.data()].second; |
1160 | 815 | for (size_t i = 0; i < offsets.size(); ++i) |
1161 | 296 | { |
1162 | 296 | if (i != offsets.size() - 1) |
1163 | 12 | { |
1164 | 12 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::DUP2)); |
1165 | | // This item type decomposes into multiple evm instructions, so we manually call emit() |
1166 | 12 | instructionLocationEmitter.emit(); |
1167 | 12 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::DUP2)); |
1168 | 12 | instructionLocationEmitter.emit(); |
1169 | 12 | } |
1170 | | // TODO: should we make use of the constant optimizer methods for pushing the offsets? |
1171 | 296 | bytes offsetBytes = toCompactBigEndian(u256(offsets[i])); |
1172 | 296 | ret.bytecode.push_back(static_cast<uint8_t>(pushInstruction(static_cast<unsigned>(offsetBytes.size())))); |
1173 | 296 | ret.bytecode += offsetBytes; |
1174 | 296 | instructionLocationEmitter.emit(); |
1175 | 296 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::ADD)); |
1176 | 296 | instructionLocationEmitter.emit(); |
1177 | 296 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::MSTORE)); |
1178 | | // No emit needed here, it's taken care of by the destructor of instructionLocationEmitter. |
1179 | 296 | } |
1180 | 519 | if (offsets.empty()) |
1181 | 235 | { |
1182 | 235 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::POP)); |
1183 | 235 | instructionLocationEmitter.emit(); |
1184 | 235 | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::POP)); |
1185 | | // no emit needed here, it's taken care of by the destructor of instructionLocationEmitter |
1186 | 235 | } |
1187 | 519 | immutableReferencesBySub.erase(item.data()); |
1188 | 519 | break; |
1189 | 0 | } |
1190 | 467 | case PushDeployTimeAddress: |
1191 | 467 | ret.bytecode += assemblePushDeployTimeAddress(); |
1192 | 467 | break; |
1193 | 1.77M | case Tag: |
1194 | 1.77M | ret.bytecode += assembleTag(item, ret.bytecode.size(), true); |
1195 | 1.77M | break; |
1196 | 0 | default: |
1197 | 0 | solAssert(false, "Unexpected opcode while assembling."); |
1198 | 19.1M | } |
1199 | 19.1M | } |
1200 | | |
1201 | 78.0k | codeSectionLocation.end = ret.bytecode.size(); |
1202 | | |
1203 | 78.0k | ret.codeSectionLocation = std::move(codeSectionLocation); |
1204 | | |
1205 | 78.0k | if (!immutableReferencesBySub.empty()) |
1206 | 0 | throw |
1207 | 0 | langutil::Error( |
1208 | 0 | 1284_error, |
1209 | 0 | langutil::Error::Type::CodeGenerationError, |
1210 | 0 | "Some immutables were read from but never assigned, possibly because of optimization." |
1211 | 0 | ); |
1212 | | |
1213 | 78.0k | if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty()) |
1214 | | // Append an INVALID here to help tests find miscompilation. |
1215 | 44.6k | ret.bytecode.push_back(static_cast<uint8_t>(Instruction::INVALID)); |
1216 | | |
1217 | 78.0k | std::map<LinkerObject, size_t> subAssemblyOffsets; |
1218 | 78.0k | for (auto const& [subIdPath, bytecodeOffset]: subRefs) |
1219 | 19.8k | { |
1220 | 19.8k | LinkerObject subObject = subAssemblyById(subIdPath)->assemble(); |
1221 | 19.8k | bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef); |
1222 | | |
1223 | | // In order for de-duplication to kick in, not only must the bytecode be identical, but |
1224 | | // link and immutables references as well. |
1225 | 19.8k | if (size_t* subAssemblyOffset = util::valueOrNullptr(subAssemblyOffsets, subObject)) |
1226 | 1.24k | toBigEndian(*subAssemblyOffset, r); |
1227 | 18.5k | else |
1228 | 18.5k | { |
1229 | 18.5k | toBigEndian(ret.bytecode.size(), r); |
1230 | 18.5k | subAssemblyOffsets[subObject] = ret.bytecode.size(); |
1231 | 18.5k | ret.bytecode += subObject.bytecode; |
1232 | 18.5k | } |
1233 | 19.8k | for (auto const& ref: subObject.linkReferences) |
1234 | 1.85k | ret.linkReferences[ref.first + subAssemblyOffsets[subObject]] = ref.second; |
1235 | 19.8k | } |
1236 | 78.0k | for (auto const& i: tagRefs) |
1237 | 2.34M | { |
1238 | 2.34M | auto [subId, tagId] = i.second; |
1239 | 2.34M | solAssert(subId.empty() || subId.value < m_subs.size(), "Invalid sub id"); |
1240 | 2.34M | std::vector<size_t> const& tagPositions = |
1241 | 2.34M | subId.empty() ? |
1242 | 2.34M | m_tagPositionsInBytecode : |
1243 | 2.34M | m_subs[subId.asIndex()]->m_tagPositionsInBytecode; |
1244 | 2.34M | assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); |
1245 | 2.34M | size_t pos = tagPositions[tagId]; |
1246 | 2.34M | assertThrow(pos != std::numeric_limits<size_t>::max(), AssemblyException, "Reference to tag without position."); |
1247 | 2.34M | assertThrow(numberEncodingSize(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space."); |
1248 | 2.34M | bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); |
1249 | 2.34M | toBigEndian(pos, r); |
1250 | 2.34M | } |
1251 | 78.0k | for (auto const& [name, tagInfo]: m_namedTags) |
1252 | 441k | { |
1253 | 441k | size_t position = m_tagPositionsInBytecode.at(tagInfo.id); |
1254 | 441k | std::optional<size_t> tagIndex; |
1255 | 441k | for (auto&& [index, item]: m_items | ranges::views::enumerate) |
1256 | 1.73G | if (item.type() == Tag && static_cast<size_t>(item.data()) == tagInfo.id) |
1257 | 392k | { |
1258 | 392k | tagIndex = index; |
1259 | 392k | break; |
1260 | 392k | } |
1261 | 441k | ret.functionDebugData[name] = { |
1262 | 441k | position == std::numeric_limits<size_t>::max() ? std::nullopt : std::optional<size_t>{position}, |
1263 | 441k | tagIndex, |
1264 | 441k | tagInfo.sourceID, |
1265 | 441k | tagInfo.params, |
1266 | 441k | tagInfo.returns |
1267 | 441k | }; |
1268 | 441k | } |
1269 | | |
1270 | 78.0k | for (auto const& dataItem: m_data) |
1271 | 9.74k | { |
1272 | 9.74k | auto references = dataRefs.equal_range(dataItem.first); |
1273 | 9.74k | if (references.first == references.second) |
1274 | 3.98k | continue; |
1275 | 20.6k | for (auto ref = references.first; ref != references.second; ++ref) |
1276 | 14.8k | { |
1277 | 14.8k | bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); |
1278 | 14.8k | toBigEndian(ret.bytecode.size(), r); |
1279 | 14.8k | } |
1280 | 5.75k | ret.bytecode += dataItem.second; |
1281 | 5.75k | } |
1282 | | |
1283 | 78.0k | ret.bytecode += m_auxiliaryData; |
1284 | | |
1285 | 78.0k | for (unsigned pos: sizeRefs) |
1286 | 950 | { |
1287 | 950 | bytesRef r(ret.bytecode.data() + pos, bytesPerDataRef); |
1288 | 950 | toBigEndian(ret.bytecode.size(), r); |
1289 | 950 | } |
1290 | 78.0k | return ret; |
1291 | 78.0k | } |
1292 | | |
1293 | | std::vector<SubAssemblyID> Assembly::decodeSubPath(SubAssemblyID _subObjectId) const |
1294 | 41.4k | { |
1295 | 41.4k | if (_subObjectId.value < m_subs.size()) |
1296 | 41.2k | return {_subObjectId}; |
1297 | | |
1298 | 281 | auto subIdPathIt = ranges::find_if( |
1299 | 281 | m_subPaths, |
1300 | 281 | [_subObjectId](auto const& subId) { return subId.second == _subObjectId; } |
1301 | 281 | ); |
1302 | | |
1303 | 281 | assertThrow(subIdPathIt != m_subPaths.end(), AssemblyException, ""); |
1304 | 281 | return subIdPathIt->first; |
1305 | 41.4k | } |
1306 | | |
1307 | | SubAssemblyID Assembly::encodeSubPath(std::vector<SubAssemblyID> const& _subPath) |
1308 | 13.3k | { |
1309 | 13.3k | assertThrow(!_subPath.empty(), AssemblyException, ""); |
1310 | 13.3k | if (_subPath.size() == 1) |
1311 | 13.2k | { |
1312 | 13.2k | solAssert(_subPath[0].value < m_subs.size()); |
1313 | 13.2k | return _subPath[0]; |
1314 | 13.2k | } |
1315 | | |
1316 | 144 | if (!m_subPaths.contains(_subPath)) |
1317 | 66 | { |
1318 | 66 | SubAssemblyID const objectId{std::numeric_limits<SubAssemblyID::ValueType>::max() - m_subPaths.size()}; |
1319 | 66 | solAssert(objectId.value >= m_subs.size()); |
1320 | 66 | m_subPaths[_subPath] = objectId; |
1321 | 66 | } |
1322 | | |
1323 | 144 | return m_subPaths[_subPath]; |
1324 | 13.3k | } |
1325 | | |
1326 | | Assembly const* Assembly::subAssemblyById(SubAssemblyID const _subId) const |
1327 | 41.4k | { |
1328 | 41.4k | std::vector<SubAssemblyID> subIDs = decodeSubPath(_subId); |
1329 | 41.4k | Assembly const* currentAssembly = this; |
1330 | 41.4k | for (auto const& subID: subIDs) |
1331 | 41.7k | { |
1332 | 41.7k | currentAssembly = currentAssembly->m_subs.at(subID.asIndex()).get(); |
1333 | 41.7k | assertThrow(currentAssembly, AssemblyException, ""); |
1334 | 41.7k | } |
1335 | | |
1336 | 41.4k | assertThrow(currentAssembly != this, AssemblyException, ""); |
1337 | 41.4k | return currentAssembly; |
1338 | 41.4k | } |
1339 | | |
1340 | | Assembly::OptimiserSettings Assembly::OptimiserSettings::translateSettings(frontend::OptimiserSettings const& _settings) |
1341 | 50.4k | { |
1342 | | // Constructing it this way so that we notice changes in the fields. |
1343 | 50.4k | OptimiserSettings asmSettings{false, false, false, false, false, false, 0}; |
1344 | 50.4k | asmSettings.runInliner = _settings.runInliner; |
1345 | 50.4k | asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; |
1346 | 50.4k | asmSettings.runPeephole = _settings.runPeephole; |
1347 | 50.4k | asmSettings.runDeduplicate = _settings.runDeduplicate; |
1348 | 50.4k | asmSettings.runCSE = _settings.runCSE; |
1349 | 50.4k | asmSettings.runConstantOptimiser = _settings.runConstantOptimiser; |
1350 | 50.4k | asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment; |
1351 | 50.4k | return asmSettings; |
1352 | 50.4k | } |