FE 0.15.0
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
sym.h
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <concepts>
5
6#include <array>
7#include <bit>
8#include <iosfwd>
9#include <optional>
10#include <string>
11
12#include <ankerl/unordered_dense.h>
13
14#include "fe/arena.h"
15#include "fe/hash.h"
16
17namespace fe {
18
19/// A Sym%bol just wraps a pointer to Sym::String, so pass Sym itself around as value.
20/// Retrieving a `std::string_view` (via Sym::view) is basically free.
21/// You can also obtain a `std::string` (via Sym::str), but this involves a copy.
22/// The characters are *not* null-terminated.
23/// With the exception of the empty string, you should only create Sym%bols via SymPool::sym.
24/// This in turn will toss all Sym%bols into a big hash set.
25/// This makes Sym::operator== and Sym::operator!= an O(1) operation.
26/// The empty string is internally handled as `nullptr`.
27/// Thus, you can create a Sym%bol representing an empty string without having access to the SymPool.
28/// @note The empty `std::string`/`std::string_view` and `nullptr` are all identified as Sym::Sym().
29/// @warning Big endian version has not been tested.
30class Sym {
31public:
32 static constexpr size_t Short_String_Bytes = sizeof(uintptr_t);
33 static constexpr size_t Short_String_Mask = Short_String_Bytes - 1;
34
35 struct String {
36 constexpr String() noexcept = default;
37 constexpr String(size_t size) noexcept
38 : size(size) {}
39
40 size_t size = 0;
41 char chars[]; // This is actually a C-only feature, but all C++ compilers support that anyway.
42
43 struct Equal {
44 constexpr bool operator()(const String* s1, const String* s2) const noexcept {
45 bool res = s1->size == s2->size;
46 for (size_t i = 0, e = s1->size; res && i != e; ++i)
47 res &= s1->chars[i] == s2->chars[i];
48 return res;
49 }
50 };
51
52 /// Hashes the characters, not the pointer - String::Equal compares them, and the two have to agree.
53 struct Hash {
54 using is_avalanching = void;
55
56 size_t operator()(const String* s) const noexcept { return StrHash()(std::string_view(s->chars, s->size)); }
57 };
58 };
59
60 static_assert(sizeof(String) == sizeof(size_t), "String.chars should be 0");
61
62private:
63 constexpr Sym(uintptr_t ptr) noexcept
64 : ptr_(ptr) {}
65
66public:
67 constexpr Sym() noexcept = default;
68
69 /// @name Getters
70 ///@{
71 [[nodiscard]] constexpr bool empty() const noexcept { return ptr_ == 0; }
72 [[nodiscard]] constexpr size_t size() const noexcept {
73 if (empty()) return 0;
74 if (auto size = ptr_ & Short_String_Mask) return size;
75 return ((const String*)ptr_)->size;
76 }
77 [[nodiscard]] constexpr uintptr_t raw() const noexcept { return ptr_; }
78 ///@}
79
80 /// @name Access
81 ///@{
82 constexpr char operator[](size_t i) const noexcept {
83 assert(i < size());
84 return view()[i];
85 }
86 constexpr char front() const noexcept { return (*this)[0]; }
87 constexpr char back() const noexcept { return (*this)[size() - 1]; }
88 ///@}
89
90 /// @name Iterators
91 ///@{
92 constexpr auto begin() const noexcept { return view().data(); }
93 constexpr auto end() const noexcept { return begin() + size(); }
94 constexpr auto cbegin() const noexcept { return begin(); }
95 constexpr auto cend() const noexcept { return end(); }
96 constexpr auto rbegin() const noexcept { return std::reverse_iterator(end()); }
97 constexpr auto rend() const noexcept { return std::reverse_iterator(begin()); }
98 constexpr auto crbegin() const noexcept { return rbegin(); }
99 constexpr auto crend() const noexcept { return rend(); }
100 ///@}
101
102 /// @name Comparison: Sym w/ Sym
103 ///@{
104 friend constexpr auto operator<=>(Sym s1, Sym s2) noexcept { return s1.view() <=> s2.view(); }
105 friend constexpr bool operator==(Sym s1, Sym s2) noexcept { return s1.ptr_ == s2.ptr_; }
106 ///@}
107
108 /// @name Comparison: Sym w/ char
109 ///@{
110 friend constexpr std::strong_ordering operator<=>(Sym s, char c) noexcept { return cmp<false>(s, c); }
111 friend constexpr std::strong_ordering operator<=>(char c, Sym s) noexcept { return cmp<true>(s, c); }
112 friend constexpr bool operator==(Sym s, char c) noexcept { return (s.size() == 1) && (s[0] == c); }
113 friend constexpr bool operator==(char c, Sym s) noexcept { return (s.size() == 1) && (s[0] == c); }
114 ///@}
115
116 /// @name Comparison: Sym w/ convertible to std::string_view
117 ///@{
118 friend constexpr auto operator<=>(Sym lhs, const std::convertible_to<std::string_view> auto& rhs) noexcept {
119 return lhs.view() <=> std::string_view(rhs);
120 }
121 friend constexpr auto operator<=>(const std::convertible_to<std::string_view> auto& lhs, Sym rhs) noexcept {
122 return std::string_view(lhs) <=> rhs.view();
123 }
124
125 friend constexpr bool operator==(Sym lhs, const std::convertible_to<std::string_view> auto& rhs) noexcept {
126 return lhs.view() == std::string_view(rhs);
127 }
128
129 friend constexpr bool operator==(const std::convertible_to<std::string_view> auto& lhs, Sym rhs) noexcept {
130 return std::string_view(lhs) == rhs.view();
131 }
132 ///@}
133
134 /// @name Conversions
135 ///@{
136 /// @warning For a *short* Sym%bol (size < Sym::Short_String_Bytes) the characters are stored inline within this
137 /// object's `ptr_`, so the returned view points into *this* Sym%bol and is only valid as long as it lives - it
138 /// dangles for a temporary (e.g. `pool.sym("ab").view()`). Use Sym::str if the string must outlive the Sym%bol.
139 [[nodiscard]] constexpr std::string_view view() const noexcept {
140 if (empty()) return {std::bit_cast<const char*>(&ptr_), 0};
141 // Little endian: 2 a b ? register: ?ba2
142 // Big endian: a b ? 2 register: ab?2
143 uintptr_t offset = std::endian::native == std::endian::little ? 1 : 0;
144 if (auto size = ptr_ & Short_String_Mask) return {std::bit_cast<const char*>(&ptr_) + offset, size};
145 auto S = std::bit_cast<const String*>(ptr_);
146 return std::string_view(S->chars, S->size);
147 }
148 constexpr operator std::string_view() const noexcept { return view(); }
149 constexpr std::string_view operator*() const noexcept { return view(); }
150 // Unfortunately, this doesn't work:
151 // std::string_view operator->() const { return view(); }
152
153 constexpr std::string str() const { return std::string(view()); } ///< This involves a copy.
154 constexpr explicit operator std::string() const { return str(); } ///< `explicit` as this involves a copy.
155 constexpr explicit operator bool() const noexcept { return ptr_; } ///< Is not empty?
156 ///@}
157
158 friend std::ostream& operator<<(std::ostream& os, Sym sym);
159
160 /// @name Hash/Eq for hash tables.
161 /// Both work on the interned pointer, so they are O(1).
162 /// @note There is deliberately *no* heterogeneous (`std::string_view`) lookup:
163 /// a content-based hash would be inconsistent with the pointer-based one.
164 /// Intern via SymPool::sym first, then look up with the resulting Sym.
165 ///@{
166 struct Hash {
167 using is_avalanching = void;
168
169 size_t operator()(Sym s) const noexcept { return ankerl::unordered_dense::hash<uintptr_t>()(s.ptr_); }
170 };
171
172 struct Eq {
173 constexpr bool operator()(Sym a, Sym b) const noexcept { return a.ptr_ == b.ptr_; }
174 };
175 ///@}
176
177private:
178 template<bool Rev>
179 static constexpr std::strong_ordering cmp(Sym s, char c) noexcept {
180 const auto n = s.size();
181 if (n == 0) return Rev ? std::strong_ordering::greater : std::strong_ordering::less;
182
183 auto cmp = s[0] <=> c;
184 if (cmp != 0) return cmp;
185
186 return (n == 1) ? std::strong_ordering::equal
187 : (Rev ? std::strong_ordering::less : std::strong_ordering::greater);
188 }
189
190 // Little endian: 2 a b ? register: ?ba2
191 // Big endian: a b ? 2 register: ab?2
192 uintptr_t ptr_ = 0;
193
194 friend class SymPool;
195};
196
197#ifndef DOXYGEN
198} // namespace fe
199
200template<>
201struct std::hash<fe::Sym> {
202 size_t operator()(fe::Sym sym) const noexcept { return fe::Sym::Hash()(sym); }
203};
204
205namespace fe {
206#endif
207
208/// @name SymMap/SymSet
209/// Set/Map is keyed by pointer - which is hashed in SymPool.
210///@{
211///
212template<class V>
213using SymMap = ankerl::unordered_dense::map<Sym, V, Sym::Hash, Sym::Eq>;
214using SymSet = ankerl::unordered_dense::set<Sym, Sym::Hash, Sym::Eq>;
215///@}
216
217/// A fixed-capacity Sym%bol -> @p V map for a *closed* set of @p Size entries: filled once, then only
218/// looked up - a Lexer's reserved words, say.
219/// Prefer this over SymMap for that use: both hash and compare the same interned pointer, but SymMap
220/// pays a full finalizer and a SwissTable group probe where this pays one multiply and one probe of
221/// a table whose capacity is a compile-time constant.
222/// @note @p V has to be default-constructible; an absent key yields no @p V at all - see SymTab::find.
223/// @warning Insert-only: there is no erase, and SymTab::emplace asserts on a key already present.
224template<class V, size_t Size>
225class SymTab {
226public:
227 /// Twice @p Size, rounded up to a power of two, so the load factor stays below `1/2`.
228 static constexpr size_t Capacity = std::bit_ceil(2 * Size);
229
230 /// @name Access
231 ///@{
232 void emplace(Sym sym, V v) {
233 assert(!sym.empty() && "the empty Sym%bol marks a free slot");
234 for (auto i = idx(sym);; i = (i + 1) & Mask) {
235 assert(slots_[i].sym != sym && "already present");
236 if (slots_[i].sym.empty()) {
237 slots_[i] = {sym, std::move(v)};
238 return;
239 }
240 }
241 }
242
243 /// Yields nothing if @p sym is not present - the empty Sym%bol never is.
244 /// @note The free-slot test comes first: the empty Sym%bol *is* a free slot's key.
245 std::optional<V> find(Sym sym) const {
246 for (auto i = idx(sym);; i = (i + 1) & Mask) {
247 if (slots_[i].sym.empty()) return {};
248 if (slots_[i].sym == sym) return slots_[i].v;
249 }
250 }
251
252 bool contains(Sym sym) const { return (bool)find(sym); }
253 ///@}
254
255private:
256 static constexpr size_t Mask = Capacity - 1;
257 static constexpr size_t Shift = 64 - std::bit_width(Mask);
258 static constexpr uint64_t Magic = 0x9E3779B97F4A7C15ull; ///< `2^64/phi` - Fibonacci hashing.
259
260 /// Takes the *high* bits of the product: a long Sym%bol is an 8-byte aligned pointer and a short
261 /// one's low byte is a size of `1 .. Sym::Short_String_Bytes-1`, so the low bits cluster.
262 static size_t idx(Sym sym) { return size_t((uint64_t(sym.raw()) * Magic) >> Shift); }
263
264 struct Slot {
265 Sym sym;
266 V v = {};
267 };
268
269 std::array<Slot, Capacity> slots_ = {};
270};
271
272/// Hash set where all strings - wrapped in Sym%bol - live in.
273/// You can access the SymPool from Driver.
274class SymPool {
275public:
277
278 /// @name Constructor & Destruction
279 ///@{
280 SymPool(const SymPool&) = delete;
281 SymPool() noexcept {}
282 SymPool(SymPool&& other) noexcept
283 : SymPool() {
284 swap(*this, other);
285 }
287 ///@}
288
289 /// @name sym
290 ///@{
291 Sym sym(std::string_view s);
292 Sym sym(const std::string& s) { return sym((std::string_view)s); }
293 /// @p s is a null-terminated C-string.
294 Sym sym(const char* s) { return s == nullptr ? Sym() : sym(std::string_view(s)); }
295 // TODO we can try to fit s in current page and hence eliminate the explicit use of strlen
296 ///@}
297
298 friend void swap(SymPool& p1, SymPool& p2) noexcept {
299 using std::swap;
300 // clang-format off
301 swap(p1.strings_, p2.strings_);
302 swap(p1.pool_, p2.pool_ );
303 // clang-format on
304 }
305
306private:
307 Arena strings_;
308 ankerl::unordered_dense::set<const String*, String::Hash, String::Equal> pool_;
309};
310
311static_assert(std::is_trivially_copyable_v<Sym>);
312static_assert(sizeof(uintptr_t) == sizeof(void*), "uintptr_t must match pointer size");
313static_assert(std::has_unique_object_representations_v<uintptr_t>);
314static_assert(std::endian::native == std::endian::little || std::endian::native == std::endian::big,
315 "mixed endianness not supported");
316
317} // namespace fe
An arena pre-allocates so-called pages of size Arena::page_size_.
Definition arena.h:25
Sym sym(const char *s)
s is a null-terminated C-string.
Definition sym.h:294
Sym::String String
Definition sym.h:276
Sym sym(std::string_view s)
SymPool & operator=(SymPool)=delete
SymPool(const SymPool &)=delete
Sym sym(const std::string &s)
Definition sym.h:292
SymPool(SymPool &&other) noexcept
Definition sym.h:282
SymPool() noexcept
Definition sym.h:281
friend void swap(SymPool &p1, SymPool &p2) noexcept
Definition sym.h:298
A fixed-capacity Symbol -> V map for a closed set of Size entries: filled once, then only looked up -...
Definition sym.h:225
static constexpr size_t Capacity
Twice Size, rounded up to a power of two, so the load factor stays below 1/2.
Definition sym.h:228
void emplace(Sym sym, V v)
Definition sym.h:232
bool contains(Sym sym) const
Definition sym.h:252
std::optional< V > find(Sym sym) const
Yields nothing if sym is not present - the empty Symbol never is.
Definition sym.h:245
A Symbol just wraps a pointer to Sym::String, so pass Sym itself around as value.
Definition sym.h:30
constexpr uintptr_t raw() const noexcept
Definition sym.h:77
friend constexpr std::strong_ordering operator<=>(char c, Sym s) noexcept
Definition sym.h:111
friend constexpr bool operator==(Sym lhs, const std::convertible_to< std::string_view > auto &rhs) noexcept
Definition sym.h:125
friend constexpr auto operator<=>(Sym s1, Sym s2) noexcept
Definition sym.h:104
constexpr auto rend() const noexcept
Definition sym.h:97
constexpr auto begin() const noexcept
Definition sym.h:92
constexpr char front() const noexcept
Definition sym.h:86
constexpr std::string_view operator*() const noexcept
Definition sym.h:149
constexpr bool empty() const noexcept
Definition sym.h:71
static constexpr size_t Short_String_Mask
Definition sym.h:33
static constexpr size_t Short_String_Bytes
Definition sym.h:32
constexpr auto cend() const noexcept
Definition sym.h:95
friend constexpr bool operator==(char c, Sym s) noexcept
Definition sym.h:113
constexpr size_t size() const noexcept
Definition sym.h:72
constexpr Sym() noexcept=default
constexpr char back() const noexcept
Definition sym.h:87
friend constexpr auto operator<=>(const std::convertible_to< std::string_view > auto &lhs, Sym rhs) noexcept
Definition sym.h:121
friend constexpr bool operator==(Sym s1, Sym s2) noexcept
Definition sym.h:105
friend class SymPool
Definition sym.h:194
constexpr auto crbegin() const noexcept
Definition sym.h:98
constexpr char operator[](size_t i) const noexcept
Definition sym.h:82
friend constexpr bool operator==(Sym s, char c) noexcept
Definition sym.h:112
constexpr auto crend() const noexcept
Definition sym.h:99
friend std::ostream & operator<<(std::ostream &os, Sym sym)
constexpr auto rbegin() const noexcept
Definition sym.h:96
friend constexpr bool operator==(const std::convertible_to< std::string_view > auto &lhs, Sym rhs) noexcept
Definition sym.h:129
constexpr std::string str() const
This involves a copy.
Definition sym.h:153
constexpr auto cbegin() const noexcept
Definition sym.h:94
friend constexpr auto operator<=>(Sym lhs, const std::convertible_to< std::string_view > auto &rhs) noexcept
Definition sym.h:118
constexpr auto end() const noexcept
Definition sym.h:93
constexpr std::string_view view() const noexcept
Definition sym.h:139
friend constexpr std::strong_ordering operator<=>(Sym s, char c) noexcept
Definition sym.h:110
Definition algo.h:17
ankerl::unordered_dense::set< Sym, Sym::Hash, Sym::Eq > SymSet
Definition sym.h:214
ankerl::unordered_dense::map< Sym, V, Sym::Hash, Sym::Eq > SymMap
Definition sym.h:213
Hashes the characters of a string; transparent, so std::string keys may be looked up by std::string_v...
Definition hash.h:46
constexpr bool operator()(Sym a, Sym b) const noexcept
Definition sym.h:173
size_t operator()(Sym s) const noexcept
Definition sym.h:169
void is_avalanching
Definition sym.h:167
constexpr bool operator()(const String *s1, const String *s2) const noexcept
Definition sym.h:44
Hashes the characters, not the pointer - String::Equal compares them, and the two have to agree.
Definition sym.h:53
void is_avalanching
Definition sym.h:54
size_t operator()(const String *s) const noexcept
Definition sym.h:56
constexpr String() noexcept=default
char chars[]
Definition sym.h:41
size_t size
Definition sym.h:40