Coverage Report

Created: 2026-07-13 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}