/src/solidity/libevmasm/Assembly.h
Line | Count | Source |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | |
19 | | #pragma once |
20 | | |
21 | | #include <libevmasm/Instruction.h> |
22 | | #include <liblangutil/SourceLocation.h> |
23 | | #include <libevmasm/AssemblyItem.h> |
24 | | #include <libevmasm/LinkerObject.h> |
25 | | #include <libevmasm/Exceptions.h> |
26 | | #include <libevmasm/SubAssemblyID.h> |
27 | | |
28 | | #include <liblangutil/DebugInfoSelection.h> |
29 | | #include <liblangutil/EVMVersion.h> |
30 | | |
31 | | #include <libsolutil/Common.h> |
32 | | #include <libsolutil/Assertions.h> |
33 | | #include <libsolutil/Keccak256.h> |
34 | | #include <libsolutil/JSON.h> |
35 | | |
36 | | #include <libsolidity/interface/OptimiserSettings.h> |
37 | | |
38 | | #include <iostream> |
39 | | #include <sstream> |
40 | | #include <memory> |
41 | | #include <map> |
42 | | #include <utility> |
43 | | |
44 | | namespace solidity::evmasm |
45 | | { |
46 | | |
47 | | using AssemblyPointer = std::shared_ptr<Assembly>; |
48 | | |
49 | | class Assembly |
50 | | { |
51 | | using TagRefs = std::map<size_t, std::pair<SubAssemblyID, size_t>>; |
52 | | using DataRefs = std::multimap<util::h256, unsigned>; |
53 | | using SubAssemblyRefs = std::multimap<SubAssemblyID, size_t>; |
54 | | using ProgramSizeRefs = std::vector<unsigned>; |
55 | | using LinkRef = std::pair<size_t, std::string>; |
56 | | |
57 | | public: |
58 | | Assembly(langutil::EVMVersion _evmVersion, bool _creation, std::string _name): |
59 | 74.4k | m_evmVersion(_evmVersion), |
60 | 74.4k | m_creation(_creation), |
61 | 74.4k | m_name(std::move(_name)) |
62 | 74.4k | {} |
63 | | |
64 | 1.78M | AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } |
65 | 398k | AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } |
66 | | |
67 | | /// Returns a tag identified by the given name. Creates it if it does not yet exist. |
68 | | AssemblyItem namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID); |
69 | 16.0k | AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } |
70 | 333 | bytes const& data(util::h256 const& _i) const { return m_data.at(_i); } |
71 | 21.8k | AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } |
72 | 0 | Assembly const& sub(SubAssemblyID const _sub) const { return *m_subs.at(_sub.asIndex()); } |
73 | 9.77k | Assembly& sub(SubAssemblyID const _sub) { return *m_subs.at(_sub.asIndex()); } |
74 | 37.9k | size_t numSubs() const { return m_subs.size(); } |
75 | 21.7k | AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } |
76 | | AssemblyItem newPushLibraryAddress(std::string const& _identifier); |
77 | | AssemblyItem newPushImmutable(std::string const& _identifier); |
78 | | AssemblyItem newImmutableAssignment(std::string const& _identifier); |
79 | | |
80 | | AssemblyItem const& append(AssemblyItem _i); |
81 | 11.6k | AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } |
82 | | |
83 | 262k | template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }solidity::evmasm::Assembly& solidity::evmasm::Assembly::operator<< <solidity::evmasm::AssemblyItem>(solidity::evmasm::AssemblyItem const&) Line | Count | Source | 83 | 262k | template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; } |
solidity::evmasm::Assembly& solidity::evmasm::Assembly::operator<< <boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256ul, 256ul, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0> >(boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256ul, 256ul, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0> const&) Line | Count | Source | 83 | 333 | template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; } |
|
84 | | |
85 | | /// Pushes the final size of the current assembly itself. Use this when the code is modified |
86 | | /// after compilation and CODESIZE is not an option. |
87 | 949 | void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } |
88 | 1.67k | void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } |
89 | 283 | void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); } |
90 | 490 | void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); } |
91 | | |
92 | | void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables) |
93 | 1.22k | { |
94 | 1.22k | append(AssemblyItem(std::move(_data), _arguments, _returnVariables)); |
95 | 1.22k | } |
96 | | |
97 | 13.9k | AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } |
98 | 174k | AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } |
99 | 0 | AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } |
100 | 35.6k | AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; } |
101 | | |
102 | | /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) |
103 | | /// on the stack. @returns the pushsub assembly item. |
104 | 189 | AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; } |
105 | 21.5k | void pushSubroutineSize(SubAssemblyID _subRoutine) { append(newPushSubSize(_subRoutine.value)); } |
106 | | /// Pushes the offset of the subroutine. |
107 | 18.8k | void pushSubroutineOffset(SubAssemblyID _subRoutine) { append(AssemblyItem(PushSub, _subRoutine.value)); } |
108 | | |
109 | | /// Appends @a _data literally to the very end of the bytecode. |
110 | 18.1k | void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; } |
111 | | |
112 | 27.4M | int deposit() const { return m_deposit; } |
113 | 1.37M | void adjustDeposit(int _adjustment) { m_deposit += _adjustment; solAssert(m_deposit >= 0); } |
114 | 1.63M | void setDeposit(int _deposit) { m_deposit = _deposit; solAssert(m_deposit >= 0); } |
115 | 29.8k | std::string const& name() const { return m_name; } |
116 | | |
117 | | /// Changes the source location used for each appended item. |
118 | 18.3M | void setSourceLocation(langutil::SourceLocation const& _location) { m_currentSourceLocation = _location; } |
119 | 49.1k | langutil::SourceLocation const& currentSourceLocation() const { return m_currentSourceLocation; } |
120 | 4.07M | langutil::EVMVersion const& evmVersion() const { return m_evmVersion; } |
121 | | |
122 | | /// Assembles the assembly into bytecode. The assembly should not be modified after this call, since the assembled version is cached. |
123 | | LinkerObject const& assemble() const; |
124 | | |
125 | | struct OptimiserSettings |
126 | | { |
127 | | bool runInliner = false; |
128 | | bool runJumpdestRemover = false; |
129 | | bool runPeephole = false; |
130 | | bool runDeduplicate = false; |
131 | | bool runCSE = false; |
132 | | bool runConstantOptimiser = false; |
133 | | |
134 | | using ExecutionCount = frontend::OptimiserSettings::ExecutionCount; |
135 | | /// This specifies an estimate on how often each opcode in this assembly will be executed, |
136 | | /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. |
137 | | ExecutionCount expectedExecutionsPerDeployment = frontend::OptimiserSettings{}.expectedExecutionsPerDeployment; |
138 | | |
139 | | static OptimiserSettings translateSettings(frontend::OptimiserSettings const& _settings); |
140 | | }; |
141 | | |
142 | | /// Modify and return the current assembly such that creation and execution gas usage |
143 | | /// is optimised according to the settings in @a _settings. |
144 | | Assembly& optimise(OptimiserSettings const& _settings); |
145 | | |
146 | | /// Create a text representation of the assembly. |
147 | | std::string assemblyString( |
148 | | langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(), |
149 | | StringMap const& _sourceCodes = StringMap() |
150 | | ) const; |
151 | | void assemblyStream( |
152 | | std::ostream& _out, |
153 | | langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(), |
154 | | std::string const& _prefix = "", |
155 | | StringMap const& _sourceCodes = StringMap() |
156 | | ) const; |
157 | | |
158 | | /// Create a JSON representation of the assembly. |
159 | | Json assemblyJSON(std::map<std::string, unsigned> const& _sourceIndices, bool _includeSourceList = true) const; |
160 | | |
161 | | /// Constructs an @a Assembly from the serialized JSON representation. |
162 | | /// @param _json JSON object containing assembly in the format produced by assemblyJSON(). |
163 | | /// @param _sourceList List of source files the assembly was built from. When the JSON represents |
164 | | /// the root assembly, the function will read it from the 'sourceList' field and the parameter |
165 | | /// must be empty. It is only used to pass the list down to recursive calls. |
166 | | /// @param _level Nesting level of the current assembly in the assembly tree. The root is |
167 | | /// at level 0 and the value increases down the tree. Necessary to distinguish between creation |
168 | | /// and deployed objects. |
169 | | /// @returns Created @a Assembly and the source list read from the 'sourceList' field of the root |
170 | | /// assembly or an empty list (in recursive calls). |
171 | | static std::pair<std::shared_ptr<Assembly>, std::vector<std::string>> fromJSON( |
172 | | Json const& _json, |
173 | | std::vector<std::string> const& _sourceList = {}, |
174 | | size_t _level = 0 |
175 | | ); |
176 | | |
177 | | /// Mark this assembly as invalid. Calling ``assemble`` on it will throw. |
178 | 20.4k | void markAsInvalid() { m_invalid = true; } |
179 | | |
180 | | std::vector<SubAssemblyID> decodeSubPath(SubAssemblyID _subObjectId) const; |
181 | | SubAssemblyID encodeSubPath(std::vector<SubAssemblyID> const& _subPath); |
182 | | |
183 | 189k | bool isCreation() const { return m_creation; } |
184 | | |
185 | 67.0k | AssemblyItems& items() { return m_items; } |
186 | 25.4k | AssemblyItems const& items() const { return m_items; } |
187 | | |
188 | | protected: |
189 | | /// Does the same operations as @a optimise, but should only be applied to a sub and |
190 | | /// returns the replaced tags. Also takes an argument containing the tags of this assembly |
191 | | /// that are referenced in a super-assembly. |
192 | | std::map<u256, u256> const& optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside); |
193 | | |
194 | | /// Calculates approximate size of "pure" code without data. |
195 | | unsigned codeSize(unsigned subTagSize) const; |
196 | | |
197 | | /// Add all assembly items from given JSON array. This function imports the items by iterating through |
198 | | /// the code array. This method only works on clean Assembly objects that don't have any items defined yet. |
199 | | /// @param _json JSON array that contains assembly items (e.g. json['.code']) |
200 | | /// @param _sourceList List of source names. |
201 | | void importAssemblyItemsFromJSON(Json const& _code, std::vector<std::string> const& _sourceList); |
202 | | |
203 | | /// Creates an AssemblyItem from a given JSON representation. |
204 | | /// @param _json JSON object that consists a single assembly item |
205 | | /// @param _sourceList List of source names. |
206 | | /// @returns AssemblyItem of _json argument. |
207 | | AssemblyItem createAssemblyItemFromJSON(Json const& _json, std::vector<std::string> const& _sourceList); |
208 | | |
209 | | private: |
210 | | bool m_invalid = false; |
211 | | |
212 | | Assembly const* subAssemblyById(SubAssemblyID _subId) const; |
213 | | |
214 | | void encodeAllPossibleSubPathsInAssemblyTree(std::vector<SubAssemblyID> _pathFromRoot = {}, std::vector<Assembly*> _assembliesOnPath = {}); |
215 | | |
216 | | std::shared_ptr<std::string const> sharedSourceName(std::string const& _name) const; |
217 | | |
218 | | LinkerObject const& assembleLegacy() const; |
219 | | |
220 | | /// Assemble bytecode for AssemblyItem type. |
221 | | [[nodiscard]] bytes assembleOperation(AssemblyItem const& _item) const; |
222 | | [[nodiscard]] bytes assemblePush(AssemblyItem const& _item) const; |
223 | | [[nodiscard]] std::pair<bytes, Assembly::LinkRef> assemblePushLibraryAddress(AssemblyItem const& _item, size_t _pos) const; |
224 | | [[nodiscard]] bytes assembleVerbatimBytecode(AssemblyItem const& item) const; |
225 | | [[nodiscard]] bytes assemblePushDeployTimeAddress() const; |
226 | | [[nodiscard]] bytes assembleTag(AssemblyItem const& _item, size_t _pos, bool _addJumpDest) const; |
227 | | |
228 | | protected: |
229 | | /// 0 is reserved for exception |
230 | | unsigned m_usedTags = 1; |
231 | | |
232 | | struct NamedTagInfo |
233 | | { |
234 | | size_t id; |
235 | | std::optional<size_t> sourceID; |
236 | | size_t params; |
237 | | size_t returns; |
238 | | }; |
239 | | |
240 | | std::map<std::string, NamedTagInfo> m_namedTags; |
241 | | std::map<util::h256, bytes> m_data; |
242 | | /// Data that is appended to the very end of the contract. |
243 | | bytes m_auxiliaryData; |
244 | | std::vector<std::shared_ptr<Assembly>> m_subs; |
245 | | AssemblyItems m_items; |
246 | | std::map<util::h256, std::string> m_strings; |
247 | | std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked. |
248 | | std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables. |
249 | | |
250 | | /// Map from a vector representing a path to a particular sub assembly to sub assembly id. |
251 | | /// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value). |
252 | | /// Nested/indirect SubAssemblyIDs are assigned negative unsigned 64 bit integers, i.e., the `i`th indirect |
253 | | /// index corresponds to `std::numeric_limits<std::uint64_t>::max() - i` in contrast to direct indices which occupy |
254 | | /// the non-negative half of the index space. |
255 | | std::map<std::vector<SubAssemblyID>, SubAssemblyID> m_subPaths; |
256 | | |
257 | | /// Contains the tag replacements relevant for super-assemblies. |
258 | | /// If set, it means the optimizer has run and we will not run it again. |
259 | | std::optional<std::map<u256, u256>> m_tagReplacements; |
260 | | |
261 | | mutable LinkerObject m_assembledObject; |
262 | | mutable std::vector<size_t> m_tagPositionsInBytecode; |
263 | | |
264 | | langutil::EVMVersion m_evmVersion; |
265 | | |
266 | | int m_deposit = 0; |
267 | | /// True, if the assembly contains contract creation code. |
268 | | bool const m_creation = false; |
269 | | /// Internal name of the assembly object, only used with the Yul backend |
270 | | /// currently |
271 | | std::string m_name; |
272 | | langutil::SourceLocation m_currentSourceLocation; |
273 | | |
274 | | // FIXME: This being static means that the strings won't be freed when they're no longer needed |
275 | | static std::map<std::string, std::shared_ptr<std::string const>> s_sharedSourceNames; |
276 | | |
277 | | public: |
278 | | size_t m_currentModifierDepth = 0; |
279 | | }; |
280 | | |
281 | | inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) |
282 | 0 | { |
283 | 0 | _a.assemblyStream(_out); |
284 | 0 | return _out; |
285 | 0 | } |
286 | | |
287 | | } |