Coverage Report

Created: 2026-09-14 06:38

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