/src/solidity/libevmasm/Assembly.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | |
19 | | #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 | | |
27 | | #include <liblangutil/DebugInfoSelection.h> |
28 | | #include <liblangutil/EVMVersion.h> |
29 | | |
30 | | #include <libsolutil/Common.h> |
31 | | #include <libsolutil/Assertions.h> |
32 | | #include <libsolutil/Keccak256.h> |
33 | | |
34 | | #include <libsolidity/interface/OptimiserSettings.h> |
35 | | |
36 | | #include <json/json.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 | | public: |
52 | 0 | Assembly(bool _creation, std::string _name): m_creation(_creation), m_name(std::move(_name)) { } |
53 | | |
54 | 0 | AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } |
55 | 0 | AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } |
56 | | /// Returns a tag identified by the given name. Creates it if it does not yet exist. |
57 | | AssemblyItem namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID); |
58 | 0 | AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } |
59 | 0 | bytes const& data(util::h256 const& _i) const { return m_data.at(_i); } |
60 | 0 | AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } |
61 | 0 | Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } |
62 | 0 | Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } |
63 | 0 | size_t numSubs() const { return m_subs.size(); } |
64 | 0 | AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } |
65 | | AssemblyItem newPushLibraryAddress(std::string const& _identifier); |
66 | | AssemblyItem newPushImmutable(std::string const& _identifier); |
67 | | AssemblyItem newImmutableAssignment(std::string const& _identifier); |
68 | | |
69 | | AssemblyItem const& append(AssemblyItem _i); |
70 | 0 | AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } |
71 | | |
72 | 0 | template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; } Unexecuted instantiation: solidity::evmasm::Assembly& solidity::evmasm::Assembly::operator<< <solidity::evmasm::AssemblyItem>(solidity::evmasm::AssemblyItem const&) Unexecuted instantiation: solidity::evmasm::Assembly& solidity::evmasm::Assembly::operator<< <boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256u, 256u, (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<256u, 256u, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0> const&) |
73 | | |
74 | | /// Pushes the final size of the current assembly itself. Use this when the code is modified |
75 | | /// after compilation and CODESIZE is not an option. |
76 | 0 | void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } |
77 | 0 | void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } |
78 | 0 | void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); } |
79 | 0 | void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); } |
80 | | |
81 | | void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables) |
82 | 0 | { |
83 | 0 | append(AssemblyItem(std::move(_data), _arguments, _returnVariables)); |
84 | 0 | } |
85 | | |
86 | 0 | AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } |
87 | 0 | AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } |
88 | 0 | AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } |
89 | 0 | AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; } |
90 | | |
91 | | /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) |
92 | | /// on the stack. @returns the pushsub assembly item. |
93 | 0 | AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; } |
94 | 0 | void pushSubroutineSize(size_t _subRoutine) { append(newPushSubSize(_subRoutine)); } |
95 | | /// Pushes the offset of the subroutine. |
96 | 0 | void pushSubroutineOffset(size_t _subRoutine) { append(AssemblyItem(PushSub, _subRoutine)); } |
97 | | |
98 | | /// Appends @a _data literally to the very end of the bytecode. |
99 | 0 | void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; } |
100 | | |
101 | | /// Returns the assembly items. |
102 | 0 | AssemblyItems const& items() const { return m_items; } |
103 | | |
104 | | /// Returns the mutable assembly items. Use with care! |
105 | 0 | AssemblyItems& items() { return m_items; } |
106 | | |
107 | 0 | int deposit() const { return m_deposit; } |
108 | 0 | void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } |
109 | 0 | void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } |
110 | 0 | std::string const& name() const { return m_name; } |
111 | | |
112 | | /// Changes the source location used for each appended item. |
113 | 0 | void setSourceLocation(langutil::SourceLocation const& _location) { m_currentSourceLocation = _location; } |
114 | 0 | langutil::SourceLocation const& currentSourceLocation() const { return m_currentSourceLocation; } |
115 | | |
116 | | /// Assembles the assembly into bytecode. The assembly should not be modified after this call, since the assembled version is cached. |
117 | | LinkerObject const& assemble() const; |
118 | | |
119 | | struct OptimiserSettings |
120 | | { |
121 | | bool runInliner = false; |
122 | | bool runJumpdestRemover = false; |
123 | | bool runPeephole = false; |
124 | | bool runDeduplicate = false; |
125 | | bool runCSE = false; |
126 | | bool runConstantOptimiser = false; |
127 | | langutil::EVMVersion evmVersion; |
128 | | /// This specifies an estimate on how often each opcode in this assembly will be executed, |
129 | | /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. |
130 | | size_t expectedExecutionsPerDeployment = frontend::OptimiserSettings{}.expectedExecutionsPerDeployment; |
131 | | }; |
132 | | |
133 | | /// Modify and return the current assembly such that creation and execution gas usage |
134 | | /// is optimised according to the settings in @a _settings. |
135 | | Assembly& optimise(OptimiserSettings const& _settings); |
136 | | |
137 | | /// Create a text representation of the assembly. |
138 | | std::string assemblyString( |
139 | | langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(), |
140 | | StringMap const& _sourceCodes = StringMap() |
141 | | ) const; |
142 | | void assemblyStream( |
143 | | std::ostream& _out, |
144 | | langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(), |
145 | | std::string const& _prefix = "", |
146 | | StringMap const& _sourceCodes = StringMap() |
147 | | ) const; |
148 | | |
149 | | /// Create a JSON representation of the assembly. |
150 | | Json::Value assemblyJSON( |
151 | | std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>(), |
152 | | bool _includeSourceList = true |
153 | | ) const; |
154 | | |
155 | | /// Mark this assembly as invalid. Calling ``assemble`` on it will throw. |
156 | 0 | void markAsInvalid() { m_invalid = true; } |
157 | | |
158 | | std::vector<size_t> decodeSubPath(size_t _subObjectId) const; |
159 | | size_t encodeSubPath(std::vector<size_t> const& _subPath); |
160 | | |
161 | 0 | bool isCreation() const { return m_creation; } |
162 | | |
163 | | protected: |
164 | | /// Does the same operations as @a optimise, but should only be applied to a sub and |
165 | | /// returns the replaced tags. Also takes an argument containing the tags of this assembly |
166 | | /// that are referenced in a super-assembly. |
167 | | std::map<u256, u256> const& optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside); |
168 | | |
169 | | unsigned codeSize(unsigned subTagSize) const; |
170 | | |
171 | | private: |
172 | | bool m_invalid = false; |
173 | | |
174 | | Assembly const* subAssemblyById(size_t _subId) const; |
175 | | |
176 | | protected: |
177 | | /// 0 is reserved for exception |
178 | | unsigned m_usedTags = 1; |
179 | | |
180 | | struct NamedTagInfo |
181 | | { |
182 | | size_t id; |
183 | | std::optional<size_t> sourceID; |
184 | | size_t params; |
185 | | size_t returns; |
186 | | }; |
187 | | |
188 | | std::map<std::string, NamedTagInfo> m_namedTags; |
189 | | AssemblyItems m_items; |
190 | | std::map<util::h256, bytes> m_data; |
191 | | /// Data that is appended to the very end of the contract. |
192 | | bytes m_auxiliaryData; |
193 | | std::vector<std::shared_ptr<Assembly>> m_subs; |
194 | | std::map<util::h256, std::string> m_strings; |
195 | | std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked. |
196 | | std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables. |
197 | | |
198 | | /// Map from a vector representing a path to a particular sub assembly to sub assembly id. |
199 | | /// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value). |
200 | | std::map<std::vector<size_t>, size_t> m_subPaths; |
201 | | |
202 | | /// Contains the tag replacements relevant for super-assemblies. |
203 | | /// If set, it means the optimizer has run and we will not run it again. |
204 | | std::optional<std::map<u256, u256>> m_tagReplacements; |
205 | | |
206 | | mutable LinkerObject m_assembledObject; |
207 | | mutable std::vector<size_t> m_tagPositionsInBytecode; |
208 | | |
209 | | int m_deposit = 0; |
210 | | /// True, if the assembly contains contract creation code. |
211 | | bool const m_creation = false; |
212 | | /// Internal name of the assembly object, only used with the Yul backend |
213 | | /// currently |
214 | | std::string m_name; |
215 | | |
216 | | langutil::SourceLocation m_currentSourceLocation; |
217 | | |
218 | | public: |
219 | | size_t m_currentModifierDepth = 0; |
220 | | }; |
221 | | |
222 | | inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) |
223 | 0 | { |
224 | 0 | _a.assemblyStream(_out); |
225 | 0 | return _out; |
226 | 0 | } |
227 | | |
228 | | } |