/src/solidity/libyul/backends/evm/EVMDialect.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 | | /** |
19 | | * Yul dialects for EVM. |
20 | | */ |
21 | | |
22 | | #include <libyul/backends/evm/EVMDialect.h> |
23 | | |
24 | | #include <libevmasm/AssemblyItem.h> |
25 | | #include <libevmasm/Instruction.h> |
26 | | #include <libevmasm/SemanticInformation.h> |
27 | | |
28 | | #include <libyul/AST.h> |
29 | | #include <libyul/Exceptions.h> |
30 | | #include <libyul/Object.h> |
31 | | #include <libyul/Utilities.h> |
32 | | |
33 | | #include <libsolutil/StringUtils.h> |
34 | | |
35 | | #include <range/v3/view/enumerate.hpp> |
36 | | #include <range/v3/view/map.hpp> |
37 | | #include <range/v3/to_container.hpp> |
38 | | |
39 | | #include <regex> |
40 | | #include <utility> |
41 | | #include <vector> |
42 | | |
43 | | using namespace std::string_literals; |
44 | | using namespace solidity; |
45 | | using namespace solidity::yul; |
46 | | using namespace solidity::util; |
47 | | |
48 | | namespace |
49 | | { |
50 | | |
51 | | size_t constexpr toContinuousVerbatimIndex(size_t _arguments, size_t _returnVariables) |
52 | 8.13k | { |
53 | 8.13k | return _arguments + _returnVariables * EVMDialect::verbatimMaxInputSlots; |
54 | 8.13k | } |
55 | | |
56 | | std::tuple<size_t, size_t> constexpr verbatimIndexToArgsAndRets(size_t _index) |
57 | 0 | { |
58 | 0 | size_t const numRets = _index / EVMDialect::verbatimMaxInputSlots; |
59 | 0 | return std::make_tuple(_index - numRets * EVMDialect::verbatimMaxInputSlots, numRets); |
60 | 0 | } |
61 | | |
62 | | bool isLowLevelStackManipulationInstruction(evmasm::Instruction const& _instruction) |
63 | 31.4M | { |
64 | 31.4M | return |
65 | 31.4M | evmasm::SemanticInformation::isSwapInstruction(_instruction) || |
66 | 31.4M | evmasm::SemanticInformation::isDupInstruction(_instruction) || |
67 | 31.4M | isPushInstruction(_instruction); |
68 | 31.4M | } |
69 | | |
70 | | bool isLowLevelControlFlowInstruction(evmasm::Instruction const& _instruction) |
71 | 32.2M | { |
72 | 32.2M | switch (_instruction) |
73 | 32.2M | { |
74 | 271k | case evmasm::Instruction::JUMP: |
75 | 542k | case evmasm::Instruction::JUMPI: |
76 | 813k | case evmasm::Instruction::JUMPDEST: |
77 | 813k | return true; |
78 | 31.4M | default: |
79 | 31.4M | return false; |
80 | 32.2M | } |
81 | 32.2M | } |
82 | | |
83 | | std::set<std::string, std::less<>> createReservedIdentifiers(langutil::EVMVersion _evmVersion) |
84 | 271k | { |
85 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name |
86 | | // basefee for VMs before london. |
87 | 271k | auto baseFeeException = [&](evmasm::Instruction _instr) -> bool |
88 | 40.9M | { |
89 | 40.9M | return _instr == evmasm::Instruction::BASEFEE && _evmVersion < langutil::EVMVersion::london(); |
90 | 40.9M | }; |
91 | | |
92 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name |
93 | | // blobbasefee for VMs before cancun. |
94 | 271k | auto blobBaseFeeException = [&](evmasm::Instruction _instr) -> bool |
95 | 40.6M | { |
96 | 40.6M | return _instr == evmasm::Instruction::BLOBBASEFEE && _evmVersion < langutil::EVMVersion::cancun(); |
97 | 40.6M | }; |
98 | | |
99 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name |
100 | | // mcopy for VMs before london. |
101 | 271k | auto mcopyException = [&](evmasm::Instruction _instr) -> bool |
102 | 40.4M | { |
103 | 40.4M | return _instr == evmasm::Instruction::MCOPY && _evmVersion < langutil::EVMVersion::cancun(); |
104 | 40.4M | }; |
105 | | |
106 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name |
107 | | // prevrandao for VMs before paris. |
108 | 271k | auto prevRandaoException = [&](std::string const& _instrName) -> bool |
109 | 40.8M | { |
110 | | // Using string comparison as the opcode is the same as for "difficulty" |
111 | 40.8M | return _instrName == "prevrandao" && _evmVersion < langutil::EVMVersion::paris(); |
112 | 40.8M | }; |
113 | | |
114 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name |
115 | | // blobhash for VMs before cancun. |
116 | 271k | auto blobHashException = [&](evmasm::Instruction _instr) -> bool |
117 | 40.7M | { |
118 | 40.7M | return _instr == evmasm::Instruction::BLOBHASH && _evmVersion < langutil::EVMVersion::cancun(); |
119 | 40.7M | }; |
120 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the names |
121 | | // tstore or tload for VMs before cancun. |
122 | 271k | auto transientStorageException = [&](evmasm::Instruction _instr) -> bool |
123 | 40.3M | { |
124 | 40.3M | return |
125 | 40.3M | _evmVersion < langutil::EVMVersion::cancun() && |
126 | 18.7M | (_instr == evmasm::Instruction::TSTORE || _instr == evmasm::Instruction::TLOAD); |
127 | 40.3M | }; |
128 | | // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name |
129 | | // clz for VMs before osaka. |
130 | 271k | auto clzException = [&](evmasm::Instruction _instr) -> bool |
131 | 40.1M | { |
132 | 40.1M | return _instr == evmasm::Instruction::CLZ && !_evmVersion.hasCLZ(); |
133 | 40.1M | }; |
134 | | |
135 | 271k | std::set<std::string, std::less<>> reserved; |
136 | 271k | for (auto const& instr: evmasm::c_instructions) |
137 | 40.9M | { |
138 | 40.9M | std::string name = toLower(instr.first); |
139 | 40.9M | if ( |
140 | 40.9M | !baseFeeException(instr.second) && |
141 | 40.8M | !prevRandaoException(name) && |
142 | 40.7M | !blobHashException(instr.second) && |
143 | 40.6M | !blobBaseFeeException(instr.second) && |
144 | 40.4M | !mcopyException(instr.second) && |
145 | 40.3M | !transientStorageException(instr.second) && |
146 | 40.1M | !clzException(instr.second) |
147 | 40.9M | ) |
148 | 39.9M | reserved.emplace(name); |
149 | 40.9M | } |
150 | 271k | reserved += std::vector<std::string>{ |
151 | 271k | "linkersymbol", |
152 | 271k | "datasize", |
153 | 271k | "dataoffset", |
154 | 271k | "datacopy", |
155 | 271k | "setimmutable", |
156 | 271k | "loadimmutable", |
157 | 271k | }; |
158 | | |
159 | 271k | return reserved; |
160 | 271k | } |
161 | | |
162 | | std::vector<BuiltinFunctionForEVM const*> createDialectBuiltins( |
163 | | std::vector<std::tuple<EVMBuiltins::Scopes, BuiltinFunctionForEVM>> const& _allBuiltins, |
164 | | langutil::EVMVersion const _evmVersion, |
165 | | bool const _objectAccess |
166 | | ) |
167 | 271k | { |
168 | 271k | std::vector<BuiltinFunctionForEVM const*> builtins; |
169 | 271k | builtins.reserve(_allBuiltins.size()); |
170 | | |
171 | 271k | for (auto const& [scopes, builtin]: _allBuiltins) |
172 | 34.1M | { |
173 | 34.1M | bool builtinShouldBeAdded = true; |
174 | 34.1M | if (scopes.instruction()) |
175 | 32.2M | { |
176 | 32.2M | if (scopes.replaced()) |
177 | 0 | builtinShouldBeAdded = false; |
178 | 32.2M | else |
179 | 32.2M | { |
180 | | // Exclude prevrandao as builtin for VMs before paris and difficulty for VMs after paris. |
181 | 32.2M | auto prevRandaoException = [&](std::string_view const _instrName) -> bool |
182 | 32.2M | { |
183 | 20.7M | return (_instrName == "prevrandao" && _evmVersion < langutil::EVMVersion::paris()) || (_instrName == "difficulty" && _evmVersion >= langutil::EVMVersion::paris()); |
184 | 20.7M | }; |
185 | | |
186 | 32.2M | yulAssert(builtin.instruction); |
187 | 32.2M | auto const& _opcode = *builtin.instruction; |
188 | 32.2M | builtinShouldBeAdded = |
189 | 32.2M | !isLowLevelControlFlowInstruction(_opcode) && |
190 | 31.4M | !isLowLevelStackManipulationInstruction(_opcode) && |
191 | 22.5M | _evmVersion.hasOpcode(_opcode) && |
192 | 20.7M | !prevRandaoException(builtin.name); |
193 | 32.2M | } |
194 | 32.2M | } |
195 | | |
196 | 34.1M | builtinShouldBeAdded &= !scopes.requiresObjectAccess() || _objectAccess; |
197 | | |
198 | 34.1M | if (builtinShouldBeAdded) |
199 | 22.3M | builtins.emplace_back(&builtin); |
200 | 11.8M | else |
201 | 11.8M | builtins.emplace_back(nullptr); |
202 | 34.1M | } |
203 | | |
204 | 271k | return builtins; |
205 | 271k | } |
206 | | |
207 | | std::regex const& verbatimPattern() |
208 | 9.30k | { |
209 | 9.30k | std::regex static const pattern{"([1-9]?[0-9])i_([1-9]?[0-9])o"}; |
210 | 9.30k | return pattern; |
211 | 9.30k | } |
212 | | |
213 | | } |
214 | | |
215 | | EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess): |
216 | 271k | m_objectAccess(_objectAccess), |
217 | 271k | m_evmVersion(_evmVersion), |
218 | 271k | m_functions(createDialectBuiltins(allBuiltins().functions(), _evmVersion, _objectAccess)), |
219 | 271k | m_reserved(createReservedIdentifiers(_evmVersion)) |
220 | 271k | { |
221 | 271k | for (auto const& [index, maybeBuiltin]: m_functions | ranges::views::enumerate) |
222 | 34.1M | if (maybeBuiltin) |
223 | | // ids are offset by the maximum number of verbatim functions |
224 | 22.3M | m_builtinFunctionsByName[maybeBuiltin->name] = BuiltinHandle{index + verbatimIDOffset}; |
225 | | |
226 | 271k | m_discardFunction = EVMDialect::findBuiltin("pop"); |
227 | 271k | m_equalityFunction = EVMDialect::findBuiltin("eq"); |
228 | 271k | m_booleanNegationFunction = EVMDialect::findBuiltin("iszero"); |
229 | 271k | m_memoryStoreFunction = EVMDialect::findBuiltin("mstore"); |
230 | 271k | m_memoryLoadFunction = EVMDialect::findBuiltin("mload"); |
231 | 271k | m_storageStoreFunction = EVMDialect::findBuiltin("sstore"); |
232 | 271k | m_storageLoadFunction = EVMDialect::findBuiltin("sload"); |
233 | 271k | m_hashFunction = EVMDialect::findBuiltin("keccak256"); |
234 | | |
235 | 271k | m_auxiliaryBuiltinHandles.add = EVMDialect::findBuiltin("add"); |
236 | 271k | m_auxiliaryBuiltinHandles.exp = EVMDialect::findBuiltin("exp"); |
237 | 271k | m_auxiliaryBuiltinHandles.mul = EVMDialect::findBuiltin("mul"); |
238 | 271k | m_auxiliaryBuiltinHandles.not_ = EVMDialect::findBuiltin("not"); |
239 | 271k | m_auxiliaryBuiltinHandles.shl = EVMDialect::findBuiltin("shl"); |
240 | 271k | m_auxiliaryBuiltinHandles.sub = EVMDialect::findBuiltin("sub"); |
241 | 271k | } |
242 | | |
243 | | std::optional<BuiltinHandle> EVMDialect::findBuiltin(std::string_view _name) const |
244 | 46.7M | { |
245 | 46.7M | if (m_objectAccess && _name.substr(0, "verbatim_"s.size()) == "verbatim_") |
246 | 9.30k | { |
247 | 9.30k | std::smatch match; |
248 | 9.30k | std::string name(_name.substr("verbatim_"s.size())); |
249 | 9.30k | if (regex_match(name, match, verbatimPattern())) |
250 | 8.13k | return verbatimFunction(stoul(match[1]), stoul(match[2])); |
251 | 9.30k | } |
252 | | |
253 | 46.7M | if ( |
254 | 46.7M | auto it = m_builtinFunctionsByName.find(_name); |
255 | 46.7M | it != m_builtinFunctionsByName.end() |
256 | 46.7M | ) |
257 | 35.6M | return it->second; |
258 | | |
259 | 11.0M | return std::nullopt; |
260 | 46.7M | } |
261 | | |
262 | | BuiltinFunctionForEVM const& EVMDialect::builtin(BuiltinHandle const& _handle) const |
263 | 753M | { |
264 | 753M | if (isVerbatimHandle(_handle)) |
265 | 104k | { |
266 | 104k | yulAssert(_handle.id < verbatimIDOffset); |
267 | 104k | auto const& verbatimFunctionPtr = m_verbatimFunctions[_handle.id]; |
268 | 104k | yulAssert(verbatimFunctionPtr); |
269 | 104k | return *verbatimFunctionPtr; |
270 | 104k | } |
271 | | |
272 | 753M | yulAssert(_handle.id - verbatimIDOffset < m_functions.size()); |
273 | 753M | auto const* maybeBuiltin = m_functions[_handle.id - verbatimIDOffset]; |
274 | 753M | yulAssert(maybeBuiltin); |
275 | 753M | return *maybeBuiltin; |
276 | 753M | } |
277 | | |
278 | | bool EVMDialect::reservedIdentifier(std::string_view _name) const |
279 | 93.8M | { |
280 | 93.8M | if (m_objectAccess) |
281 | 90.6M | if (_name.substr(0, "verbatim"s.size()) == "verbatim") |
282 | 135 | return true; |
283 | 93.8M | return m_reserved.contains(_name); |
284 | 93.8M | } |
285 | | |
286 | | EVMDialect const& EVMDialect::strictAssemblyForEVM(langutil::EVMVersion _evmVersion) |
287 | 114k | { |
288 | 114k | static std::map<langutil::EVMVersion, std::unique_ptr<EVMDialect const>> dialects; |
289 | 114k | static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }}; |
290 | 114k | if (!dialects[_evmVersion]) |
291 | 35 | dialects[_evmVersion] = std::make_unique<EVMDialect>(_evmVersion, false); |
292 | 114k | return *dialects[_evmVersion]; |
293 | 114k | } |
294 | | |
295 | | EVMDialect const& EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion _evmVersion) |
296 | 18.3M | { |
297 | 18.3M | static std::map<langutil::EVMVersion, std::unique_ptr<EVMDialect const>> dialects; |
298 | 18.3M | static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }}; |
299 | 18.3M | if (!dialects[_evmVersion]) |
300 | 179k | dialects[_evmVersion] = std::make_unique<EVMDialect>(_evmVersion, true); |
301 | 18.3M | return *dialects[_evmVersion]; |
302 | 18.3M | } |
303 | | |
304 | | std::set<std::string_view> EVMDialect::builtinFunctionNames() const |
305 | 0 | { |
306 | 0 | return ranges::views::keys(m_builtinFunctionsByName) | ranges::to<std::set>; |
307 | 0 | } |
308 | | |
309 | | BuiltinFunctionForEVM EVMDialect::createVerbatimFunctionFromHandle(BuiltinHandle const& _handle) |
310 | 0 | { |
311 | 0 | return std::apply(EVMBuiltins::createVerbatimFunction, verbatimIndexToArgsAndRets(_handle.id)); |
312 | 0 | } |
313 | | |
314 | | BuiltinHandle EVMDialect::verbatimFunction(size_t _arguments, size_t _returnVariables) const |
315 | 8.13k | { |
316 | 8.13k | yulAssert(_arguments <= verbatimMaxInputSlots); |
317 | 8.13k | yulAssert(_returnVariables <= verbatimMaxOutputSlots); |
318 | | |
319 | 8.13k | auto const verbatimIndex = toContinuousVerbatimIndex(_arguments, _returnVariables); |
320 | 8.13k | yulAssert(verbatimIndex < verbatimIDOffset); |
321 | | |
322 | 8.13k | if ( |
323 | 8.13k | auto& verbatimFunctionPtr = m_verbatimFunctions[verbatimIndex]; |
324 | 8.13k | !verbatimFunctionPtr |
325 | 8.13k | ) |
326 | 1.66k | verbatimFunctionPtr = std::make_unique<BuiltinFunctionForEVM>(EVMBuiltins::createVerbatimFunction(_arguments, _returnVariables)); |
327 | | |
328 | 8.13k | return {verbatimIndex}; |
329 | 8.13k | } |
330 | | |
331 | | EVMBuiltins const& EVMDialect::allBuiltins() |
332 | 271k | { |
333 | 271k | static EVMBuiltins const builtins; |
334 | 271k | return builtins; |
335 | 271k | } |