Coverage Report

Created: 2026-09-14 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libevmasm/BlockDeduplicator.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
 * @file BlockDeduplicator.cpp
20
 * @author Christian <c@ethdev.com>
21
 * @date 2015
22
 * Unifies basic blocks that share content.
23
 */
24
25
#include <libevmasm/BlockDeduplicator.h>
26
27
#include <libevmasm/AssemblyItem.h>
28
#include <libevmasm/SemanticInformation.h>
29
30
#include <boost/container_hash/hash.hpp>
31
32
#include <range/v3/algorithm/any_of.hpp>
33
#include <range/v3/algorithm/equal.hpp>
34
35
#include <unordered_set>
36
37
using namespace solidity;
38
using namespace solidity::evmasm;
39
40
41
bool BlockDeduplicator::deduplicate()
42
98.0k
{
43
  // Group basic blocks by a content hash and dedup within each bucket.
44
  // The hash and equality both walk a BlockIterator that ignores tags and stops at
45
  // opcodes that terminate control flow, replacing the block's own self-push by a
46
  // virtual tag so that recursive loops match.
47
48
  // Virtual tag that signifies "the current block" and which is used to optimize loops.
49
  // We abort if this virtual tag actually exists.
50
98.0k
  AssemblyItem const pushSelf{PushTag, u256(-4)};
51
98.0k
  {
52
98.0k
    AssemblyItem const selfTag = pushSelf.tag();
53
20.4M
    if (ranges::any_of(m_items, [&](AssemblyItem const& _item) { return _item == selfTag || _item == pushSelf; }))
54
0
      return false;
55
98.0k
  }
56
57
98.0k
  BlockIterator const end{m_items.end(), m_items.end()};
58
59
  // yields a block iterator into the body of a block (skips `Tag` typed assembly items at `_blockBegin`)
60
98.0k
  auto const blockBodyBegin = [&](std::size_t const _blockBegin, AssemblyItem const& _selfTagPush)
61
2.28M
  {
62
2.28M
    BlockIterator it{
63
2.28M
      m_items.begin() + static_cast<BlockIterator::difference_type>(_blockBegin),
64
2.28M
      m_items.end(),
65
2.28M
      &_selfTagPush,
66
2.28M
      &pushSelf
67
2.28M
    };
68
2.28M
    if (it != end && (*it).type() == Tag)
69
2.28M
      ++it;
70
2.28M
    return it;
71
2.28M
  };
72
73
98.0k
  auto const hashBlockAt = [&](std::size_t const _i)
74
1.84M
  {
75
1.84M
    return boost::hash_range(blockBodyBegin(_i, m_items[_i].pushTag()), end);
76
1.84M
  };
77
98.0k
  auto const blocksAtEqual = [&](std::size_t const _i, std::size_t const _j)
78
217k
  {
79
217k
    return ranges::equal(
80
217k
      blockBodyBegin(_i, m_items[_i].pushTag()), end,
81
217k
      blockBodyBegin(_j, m_items[_j].pushTag()), end
82
217k
    );
83
217k
  };
84
85
98.0k
  std::size_t iterations = 0;
86
98.0k
  for (; ; ++iterations)
87
108k
  {
88
108k
    std::unordered_set<std::size_t, decltype(hashBlockAt), decltype(blocksAtEqual)> seen(0, hashBlockAt, blocksAtEqual);
89
26.2M
    for (std::size_t i = 0; i < m_items.size(); ++i)
90
26.1M
    {
91
26.1M
      if (m_items[i].type() != Tag)
92
24.2M
        continue;
93
1.84M
      auto const [it, inserted] = seen.insert(i);
94
1.84M
      if (!inserted)
95
217k
        m_replacedTags[m_items[i].data()] = m_items[*it].data();
96
1.84M
    }
97
98
108k
    if (!applyTagReplacement(m_items, m_replacedTags))
99
98.0k
      break;
100
108k
  }
101
98.0k
  return iterations > 0;
102
98.0k
}
103
104
bool BlockDeduplicator::applyTagReplacement(
105
  AssemblyItems& _items,
106
  std::map<u256, u256> const& _replacements,
107
  SubAssemblyID _subId
108
)
109
129k
{
110
129k
  bool changed = false;
111
129k
  for (AssemblyItem& item: _items)
112
27.5M
    if (item.type() == PushTag)
113
2.62M
    {
114
2.62M
      SubAssemblyID subId;
115
2.62M
      size_t tagId;
116
2.62M
      std::tie(subId, tagId) = item.splitForeignPushTag();
117
2.62M
      if (subId != _subId)
118
173k
        continue;
119
2.45M
      auto it = _replacements.find(tagId);
120
      // Recursively look for the element replaced by tagId
121
2.55M
      for (auto _it = it; _it != _replacements.end(); _it = _replacements.find(_it->second))
122
107k
        it = _it;
123
124
2.45M
      if (it != _replacements.end())
125
107k
      {
126
107k
        changed = true;
127
107k
        item.setPushTagSubIdAndTag(subId, static_cast<size_t>(it->second));
128
107k
      }
129
2.45M
    }
130
129k
  return changed;
131
129k
}
132
133
BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++()
134
34.2M
{
135
34.2M
  if (it == end)
136
0
    return *this;
137
34.2M
  if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI})
138
2.26M
    it = end;
139
31.9M
  else
140
31.9M
  {
141
31.9M
    ++it;
142
32.7M
    while (it != end && it->type() == Tag)
143
783k
      ++it;
144
31.9M
  }
145
34.2M
  return *this;
146
34.2M
}
147
148
AssemblyItem const& BlockDeduplicator::BlockIterator::operator*() const
149
34.2M
{
150
34.2M
  if (replaceItem && replaceWith && *it == *replaceItem)
151
37.0k
    return *replaceWith;
152
34.1M
  else
153
34.1M
    return *it;
154
34.2M
}