/src/solidity/libyul/YulString.h
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 | | * String abstraction that avoids copies. |
20 | | */ |
21 | | |
22 | | #pragma once |
23 | | |
24 | | #include <libsolutil/UnorderedContainers.h> |
25 | | |
26 | | #include <fmt/format.h> |
27 | | |
28 | | #include <deque> |
29 | | #include <memory> |
30 | | #include <vector> |
31 | | #include <string> |
32 | | #include <string_view> |
33 | | #include <functional> |
34 | | |
35 | | namespace solidity::yul |
36 | | { |
37 | | |
38 | | /// Repository for YulStrings. |
39 | | /// Owns the string data for all YulStrings, which can be referenced by a Handle. |
40 | | /// A Handle consists of an ID (that depends on the insertion order of YulStrings and is potentially |
41 | | /// non-deterministic) and a deterministic string hash. |
42 | | class YulStringRepository |
43 | | { |
44 | | public: |
45 | | struct Handle |
46 | | { |
47 | | size_t id; |
48 | | std::uint64_t hash; |
49 | | }; |
50 | | |
51 | | static YulStringRepository& instance() |
52 | 280M | { |
53 | 280M | static YulStringRepository inst; |
54 | 280M | return inst; |
55 | 280M | } |
56 | | |
57 | | Handle stringToHandle(std::string_view const _string) |
58 | 81.3M | { |
59 | 81.3M | if (_string.empty()) |
60 | 2.03M | return { 0, emptyHash() }; |
61 | 79.3M | std::uint64_t h = hash(_string); |
62 | 79.3M | auto range = m_hashToID.equal_range(h); |
63 | 79.3M | for (auto it = range.first; it != range.second; ++it) |
64 | 35.2M | if (m_strings[it->second] == _string) |
65 | 35.2M | return Handle{it->second, h}; |
66 | 44.0M | m_strings.emplace_back(_string); |
67 | 44.0M | size_t id = m_strings.size() - 1; |
68 | 44.0M | m_hashToID.emplace_hint(range.second, std::make_pair(h, id)); |
69 | | |
70 | 44.0M | return Handle{id, h}; |
71 | 79.3M | } |
72 | 198M | std::string const& idToString(size_t _id) const { return m_strings.at(_id); } |
73 | | |
74 | | static std::uint64_t hash(std::string_view const v) |
75 | 79.3M | { |
76 | | // FNV hash - can be replaced by a better one, e.g. xxhash64 |
77 | 79.3M | std::uint64_t hash = emptyHash(); |
78 | 79.3M | for (char c: v) |
79 | 859M | { |
80 | 859M | hash *= 1099511628211u; |
81 | 859M | hash ^= static_cast<uint64_t>(c); |
82 | 859M | } |
83 | | |
84 | 79.3M | return hash; |
85 | 79.3M | } |
86 | 172M | static constexpr std::uint64_t emptyHash() { return 14695981039346656037u; } |
87 | | /// Clear the repository. |
88 | | /// Use with care - there cannot be any dangling YulString references. |
89 | | /// If references need to be cleared manually, register the callback via |
90 | | /// resetCallback. |
91 | | static void reset() |
92 | 137k | { |
93 | 137k | for (auto const& cb: resetCallbacks()) |
94 | 137k | cb(); |
95 | 137k | instance() = YulStringRepository{}; |
96 | 137k | } |
97 | | /// Struct that registers a reset callback as a side-effect of its construction. |
98 | | /// Useful as static local variable to register a reset callback once. |
99 | | struct ResetCallback |
100 | | { |
101 | | ResetCallback(std::function<void()> _fun) |
102 | 17 | { |
103 | 17 | YulStringRepository::resetCallbacks().emplace_back(std::move(_fun)); |
104 | 17 | } |
105 | | }; |
106 | | |
107 | | private: |
108 | 137k | YulStringRepository() = default; |
109 | | YulStringRepository(YulStringRepository const&) = delete; |
110 | | YulStringRepository(YulStringRepository&&) = default; |
111 | | YulStringRepository& operator=(YulStringRepository const& _rhs) = delete; |
112 | 137k | YulStringRepository& operator=(YulStringRepository&& _rhs) = default; |
113 | | |
114 | | static std::vector<std::function<void()>>& resetCallbacks() |
115 | 137k | { |
116 | 137k | static std::vector<std::function<void()>> callbacks; |
117 | 137k | return callbacks; |
118 | 137k | } |
119 | | |
120 | | std::deque<std::string> m_strings = {""}; |
121 | | util::unordered_multimap<std::uint64_t, size_t> m_hashToID = {{emptyHash(), 0}}; |
122 | | }; |
123 | | |
124 | | /// Wrapper around handles into the YulString repository. |
125 | | /// Equality of two YulStrings is determined by comparing their ID. |
126 | | /// The <-operator depends on the string hash and is not consistent |
127 | | /// with string comparisons (however, it is still deterministic). |
128 | | class YulString |
129 | | { |
130 | | public: |
131 | 90.8M | YulString() = default; |
132 | 81.3M | explicit YulString(std::string_view const _s): m_handle(YulStringRepository::instance().stringToHandle(_s)) {} |
133 | | YulString(YulString const&) = default; |
134 | | YulString(YulString&&) = default; |
135 | | YulString& operator=(YulString const&) = default; |
136 | | YulString& operator=(YulString&&) = default; |
137 | | |
138 | | /// This is not consistent with the string ordering! |
139 | | /// First compares the string hashes. If they are equal |
140 | | /// it checks for identical IDs (only identical strings have |
141 | | /// identical IDs and identical strings do not compare as "less"). |
142 | | /// If the hashes are identical and the strings are distinct, it |
143 | | /// falls back to string comparison. |
144 | | std::strong_ordering operator<=>(YulString const& _other) const |
145 | 13.3G | { |
146 | 13.3G | if (auto const order = m_handle.hash <=> _other.m_handle.hash; order != std::strong_ordering::equal) |
147 | 11.4G | return order; |
148 | 1.96G | if (m_handle.id == _other.m_handle.id) |
149 | 1.96G | return std::strong_ordering::equal; |
150 | 0 | return str().compare(_other.str()) <=> 0; |
151 | 1.96G | } |
152 | | /// Equality is determined based on the string ID. |
153 | 2.58G | bool operator==(YulString const& _other) const { return m_handle.id == _other.m_handle.id; } |
154 | | |
155 | 98.3M | bool empty() const { return m_handle.id == 0; } |
156 | | std::string const& str() const |
157 | 198M | { |
158 | 198M | return YulStringRepository::instance().idToString(m_handle.id); |
159 | 198M | } |
160 | | |
161 | 3.46G | uint64_t hash() const { return m_handle.hash; } |
162 | | |
163 | | private: |
164 | | /// Handle of the string. Assumes that the empty string has ID zero. |
165 | | YulStringRepository::Handle m_handle{ 0, YulStringRepository::emptyHash() }; |
166 | | }; |
167 | | |
168 | | inline YulString operator""_yulname(char const* _string, std::size_t _size) |
169 | 707k | { |
170 | 707k | return YulString(std::string_view(_string, _size)); |
171 | 707k | } |
172 | | |
173 | | } |
174 | | |
175 | | namespace std |
176 | | { |
177 | | template<> struct hash<solidity::yul::YulString> |
178 | | { |
179 | | size_t operator()(solidity::yul::YulString const& x) const |
180 | 3.27G | { |
181 | 3.27G | return static_cast<size_t>(x.hash()); |
182 | 3.27G | } |
183 | | }; |
184 | | } |