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