mutable
A Database System for Research and Fast Prototyping
Loading...
Searching...
No Matches
WasmAlgo.cpp
Go to the documentation of this file.
2
4#include <numeric>
5
6
7using namespace m;
8using namespace m::wasm;
9
10
11namespace {
12
13namespace options {
14
16bool insist_no_rehashing = false;
18bool special_case_quicksort_two_elements = true;
21bool partition_hard_boundary = false;
22
23}
24
25__attribute__((constructor(201)))
26static void add_wasm_algo_args()
27{
28 Catalog &C = Catalog::Get();
29
30 /*----- Command-line arguments -----*/
31 C.arg_parser().add<bool>(
32 /* group= */ "Wasm",
33 /* short= */ nullptr,
34 /* long= */ "--insist-no-rehashing",
35 /* description= */ "insist that no rehashing occurs",
36 /* callback= */ [](bool){ options::insist_no_rehashing = true; }
37 );
38 C.arg_parser().add<bool>(
39 /* group= */ "Wasm",
40 /* short= */ nullptr,
41 /* long= */ "--no-special-case-quicksort-two-elements",
42 /* description= */ "disable special case handling for sorting two elements using quicksort",
43 /* callback= */ [](bool){ options::special_case_quicksort_two_elements = false; }
44 );
45 C.arg_parser().add<bool>(
46 /* group= */ "Wasm",
47 /* short= */ nullptr,
48 /* long= */ "--partition-hard-boundary",
49 /* description= */ "partition data using a hard boundary, i.e. pivot element strictly splits the data; "
50 "otherwise, a soft boundary is used, i.e. boundary can be anywhere in the data range equal "
51 "to the pivot",
52 /* callback= */ [](bool){ options::partition_hard_boundary = true; }
53 );
54}
55
56}
57
58
59/*======================================================================================================================
60 * sorting
61 *====================================================================================================================*/
62
63template<bool CmpPredicated, bool IsGlobal>
64void m::wasm::quicksort(Buffer<IsGlobal> &buffer, const std::vector<SortingOperator::order_type> &order)
65{
66 static_assert(IsGlobal, "quicksort on local buffers is not yet supported");
67
68 /*----- Create load and swap proxies for buffer. -----*/
69 auto load = buffer.create_load_proxy();
70 auto swap = buffer.create_swap_proxy();
71
72 /*---- Create branchless binary partition function. -----*/
73 /* Receives the ID of the first tuple to partition, the past-the-end ID to partition, and an environment
74 * containing the entries of the pivot element needed for ordering as parameters (note that the pivot element
75 * must not be contained in the interval [begin, end[ since these entries may be swapped which would render the
76 * given environment invalid). Returns ID of partition boundary s.t. all elements before this boundary are smaller
77 * than or equal to the pivot element and all elements after or equal this boundary are greater than (or equal to
78 * iff `options::partition_hard_boundary` is unset) the pivot element. */
79 auto partition = [&](U32x1 _begin, U32x1 _end, const Environment &env_pivot) -> U32x1 {
80 Var<U32x1> begin(_begin), end(_end);
81
82 Wasm_insist(begin < end);
83
84 U32x1 last = end - 1U;
85
86 DO_WHILE(begin < end) {
87 /*----- Load entire begin tuple. -----*/
88 auto env_begin = [&](){
90 load(begin);
91 return S.extract();
92 }();
93
94 /*----- Load entire last tuple. -----*/
95 auto env_last = [&](){
97 load(last.clone());
98 return S.extract();
99 }();
100
101 /*----- Swap entire begin and last tuples. -----*/
102 swap(begin, last, env_begin, env_last);
103 /* Note that environments are now also swapped, i.e. `env_begin` contains still the values of the former
104 * begin tuple which is now located at ID `last` and vice versa, except for `NChar`s since they are only
105 * pointers to the actual values, i.e. `env_begin` contains still the addresses of the former begin tuple
106 * where now the values of the last tuple are stored and vice versa. */
107
108 /*----- Adapt environments to match their previous meanings before swapping tuples. -----*/
109 for (auto &e : buffer.schema()) {
110 M_insist(env_begin.template is<NChar>(e.id) == env_last.template is<NChar>(e.id),
111 "either both or none of the entries must be `NChar`s");
112 if (not env_begin.template is<NChar>(e.id)) {
113 /* Swap entry in environments. */
114 auto tmp = env_begin.extract(e.id);
115 env_begin.add(e.id, env_last.extract(e.id));
116 env_last.add(e.id, std::move(tmp));
117 }
118 }
119
120 /*----- Compare begin and last tuples to pivot element and advance cursors respectively. -----*/
121 Boolx1 begin_cmp_pivot =
122 options::partition_hard_boundary ? compare<CmpPredicated>(env_begin, env_pivot, order) < 0
123 : compare<CmpPredicated>(env_begin, env_pivot, order) <= 0;
124 Boolx1 last_ge_pivot = compare<CmpPredicated>(env_last, env_pivot, order) >= 0;
125
126 begin += begin_cmp_pivot.to<uint32_t>();
127 end -= last_ge_pivot.to<uint32_t>();
128 }
129
130 return begin;
131 };
132
133 /*---- Create quicksort function. -----*/
134 /* Receives the ID of the first tuple to sort and the past-the-end ID to sort. */
135 FUNCTION(quicksort, void(uint32_t, uint32_t))
136 {
137 auto S = CodeGenContext::Get().scoped_environment(); // create scoped environment
138
139 buffer.setup_base_address(); // to access base address during loading and swapping as local
140
141 const auto begin = PARAMETER(0); // first ID to sort
142 auto end = PARAMETER(1); // past-the-end ID to sort
143 Wasm_insist(begin <= end);
144
145 U32x1 last = end - 1U;
146
147 Boolx1 cmp = options::special_case_quicksort_two_elements ? end - begin > 2U : end - begin >= 2U;
148 WHILE(cmp) {
149 Var<U32x1> mid((begin + end) >> 1U); // (begin + end) / 2
150
151 /*----- Load entire begin tuple. -----*/
152 auto env_begin = [&](){
154 load(begin);
155 return S.extract();
156 }();
157
158 /*----- Load entire mid tuple. -----*/
159 auto env_mid = [&](){
161 load(mid);
162 return S.extract();
163 }();
164
165 /*----- Load entire last tuple. -----*/
166 auto env_last = [&](){
168 load(last.clone());
169 return S.extract();
170 }();
171
172 /*----- Swap pivot (median of three) to begin. -----.*/
173 Boolx1 begin_le_mid = compare<CmpPredicated>(env_begin, env_mid, order) <= 0;
174 Boolx1 begin_le_last = compare<CmpPredicated>(env_begin, env_last, order) <= 0;
175 Boolx1 mid_le_last = compare<CmpPredicated>(env_mid, env_last, order) <= 0;
176 IF (begin_le_mid) {
177 IF (begin_le_last.clone()) {
178 IF (mid_le_last.clone()) {
179 swap(begin, mid, env_begin, env_mid); // [begin, mid, last]
180 } ELSE {
181 swap(begin, last.clone(), env_begin, env_last); // [begin, last, mid]
182 };
183 }; // else [last, begin, mid]
184 } ELSE {
185 IF (mid_le_last) {
186 IF (not begin_le_last) {
187 swap(begin, last.clone(), env_begin, env_last); // [mid, last, begin]
188 }; // else [mid, begin, last]
189 } ELSE {
190 swap(begin, mid, env_begin, env_mid); // [last, mid, begin]
191 };
192 };
193
194 /*----- Load entire pivot tuple. Must be loaded again as begin tuple may be swapped above. -----*/
195 U32x1 pivot = begin; // use begin as pivot
196 auto env_pivot = [&](){
198 load(pivot.clone());
199 return S.extract();
200 }();
201
202 /*----- Partition range [begin + 1, end[ using pivot. -----*/
203 mid = partition(begin + 1U, end, env_pivot);
204 swap(pivot, mid - 1U, env_pivot); // patch mid
205
206 /*----- Recurse right partition, if necessary. -----*/
207 IF (end - mid >= 2U) {
208 quicksort(mid, end);
209 };
210
211 /*----- Update end pointer. -----*/
212 end = mid - 1U;
213 }
214
215 if (options::special_case_quicksort_two_elements) {
216 IF (end - begin == 2U) {
217 /*----- Load entire begin tuple. -----*/
218 auto env_begin = [&](){
220 load(begin);
221 return S.extract();
222 }();
223
224 /*----- Load entire last tuple. -----*/
225 auto env_last = [&](){
227 load(last.clone());
228 return S.extract();
229 }();
230
231 /*----- Swap begin and last if they are not yet sorted. -----.*/
232 Boolx1 begin_gt_last = compare<CmpPredicated>(env_begin, env_last, order) > 0;
233 IF (begin_gt_last) {
234 swap(begin, last, env_begin, env_last);
235 };
236 };
237 } else {
238 last.discard(); // since it was always cloned
239 }
240
241 buffer.teardown_base_address();
242 }
243 quicksort(0, buffer.size());
244}
245
246// explicit instantiations to prevent linker errors
247template void m::wasm::quicksort<false>(GlobalBuffer&, const std::vector<SortingOperator::order_type>&);
248template void m::wasm::quicksort<true>(GlobalBuffer&, const std::vector<SortingOperator::order_type>&);
249
250
251/*======================================================================================================================
252 * hashing
253 *====================================================================================================================*/
254
255/*----- helper functions ---------------------------------------------------------------------------------------------*/
256
257template<typename T>
260
261template<typename T>
262requires signed_integral<T>
263U64x1 reinterpret_to_U64(m::wasm::PrimitiveExpr<T> value) { return value.make_unsigned(); }
264
265template<typename T>
266requires std::floating_point<T> and (sizeof(T) == 4)
267U64x1 reinterpret_to_U64(m::wasm::PrimitiveExpr<T> value) { return value.template reinterpret<int32_t>().make_unsigned(); }
268
269template<typename T>
270requires std::floating_point<T> and (sizeof(T) == 8)
271U64x1 reinterpret_to_U64(m::wasm::PrimitiveExpr<T> value) { return value.template reinterpret<int64_t>().make_unsigned(); }
272
273template<typename T>
274requires std::same_as<T, bool>
275U64x1 reinterpret_to_U64(m::wasm::PrimitiveExpr<T> value) { return value.template to<uint64_t>(); }
276
277
278/*----- bit mix functions --------------------------------------------------------------------------------------------*/
279
281{
282 /* Taken from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp by Austin Appleby. We use the
283 * optimized constants found by David Stafford, in particular the values for `Mix01`, as reported at
284 * http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html. */
285 Var<U64x1> res(bits);
286 res ^= res >> uint64_t(31);
287 res *= uint64_t(0x7fb5d329728ea185UL);
288 res ^= res >> uint64_t(27);
289 res *= uint64_t(0x81dadef4bc2dd44dUL);
290 res ^= res >> uint64_t(33);
291 return res;
292}
293
294
295/*----- hash functions -----------------------------------------------------------------------------------------------*/
296
297U64x1 m::wasm::fnv_1a(Ptr<U8x1> bytes, U32x1 num_bytes)
298{
299 Wasm_insist(not bytes.clone().is_nullptr(), "cannot compute hash of nullptr");
300
301 Var<U64x1> h(0xcbf29ce484222325UL);
302
303 Var<Ptr<U8x1>> ptr(bytes.clone());
304 WHILE (ptr != bytes + num_bytes.make_signed() and U8x1(*ptr).to<bool>()) {
305 h ^= *ptr;
306 h *= uint64_t(0x100000001b3UL);
307 ptr += 1;
308 }
309
310 return h;
311}
312
314{
315 Var<U64x1> h(0); // always set here
316
317 IF (_str.clone().is_null()) {
318 h = uint64_t(1UL << 63);
319 } ELSE {
320 if (_str.length() <= 8) {
321 /*----- If the string fits in a single U64x1, combine all characters and bit mix. -----*/
322 const Var<Ptr<Charx1>> str(_str.val());
323 for (int32_t i = 0; i != _str.length(); ++i) {
324 h <<= 8U;
325 Charx1 c = *(str + i);
326 h |= c.to<uint64_t>();
327 }
328 h = murmur3_bit_mix(h);
329 } else {
330 /*----- Compute FNV-1a hash of string. -----*/
331 h = fnv_1a(_str.to<void*>().to<uint8_t*>(), U32x1(_str.length()));
332 }
333 };
334
335 return h;
336}
337
338U64x1 m::wasm::murmur3_64a_hash(std::vector<std::pair<const Type*, SQL_t>> values)
339{
340 /* Inspired by https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp by Austin Appleby. We use
341 * constants from MurmurHash2_64 as reported on https://sites.google.com/site/murmurhash/. */
342 M_insist(values.size() != 0, "cannot compute hash of an empty sequence of values");
343
344 /*----- Handle a single value. -----*/
345 if (values.size() == 1) {
346 return std::visit(overloaded {
347 [&]<typename T>(Expr<T> val) -> U64x1 { return murmur3_bit_mix(val.hash()); },
348 [&](NChar val) -> U64x1 { return str_hash(val); },
349 [](auto) -> U64x1 { M_unreachable("SIMDfication currently not supported"); },
350 [](std::monostate) -> U64x1 { M_unreachable("invalid variant"); }
351 }, values.front().second);
352 }
353
354 /*----- Compute total size in bits of all values including NULL bits or rather nullptr. -----*/
355 uint64_t total_size_in_bits = 0;
356 for (const auto &p : values) {
357 std::visit(overloaded {
358 [&]<typename T>(const Expr<T> &val) -> void { total_size_in_bits += p.first->size(); },
359 [&](const NChar &val) -> void { total_size_in_bits += 8 * val.length(); },
360 [](auto&) -> void { M_unreachable("SIMDfication currently not supported"); },
361 [](std::monostate&) -> void { M_unreachable("invalid variant"); }
362 }, p.second);
363 }
364
365 /*----- If all values can be combined into a single U64x1 value, combine all values and bit mix. -----*/
366 if (total_size_in_bits <= 64) {
367 Var<U64x1> h(0);
368 for (auto &p : values) {
369 std::visit(overloaded {
370 [&]<typename T>(Expr<T> _val) -> void {
371 h <<= p.first->size();
372 if (_val.can_be_null()) {
373 auto [val, is_null] = _val.split();
374#if 0
375 IF (not is_null) {
376 h |= reinterpret_to_U64(val); // add reinterpreted value
377 };
378#else
379 h |= (~uint64_t(0) + is_null.template to<uint64_t>()) bitand reinterpret_to_U64(val);
380#endif
381 } else {
382 auto val = _val.insist_not_null();
383 h |= reinterpret_to_U64(val); // add reinterpreted value
384 }
385 },
386 [&](NChar _val) -> void {
387 IF (_val.clone().is_null()) {
388 uint64_t len_in_bits = 8 * _val.length();
389 h <<= len_in_bits;
390 } ELSE {
391 const Var<Ptr<Charx1>> val(_val.val());
392 for (int32_t i = 0; i != _val.length(); ++i) {
393 h <<= 8U;
394 Charx1 c = *(val + i);
395 h |= c.to<uint64_t>(); // add reinterpreted character
396 }
397 };
398 },
399 [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
400 [](std::monostate) -> void { M_unreachable("invalid variant"); }
401 }, p.second);
402 }
403 return murmur3_bit_mix(h);
404 }
405
406 /*----- Perform general Murmur3_64a. -----*/
407 U64x1 m(0xc6a4a7935bd1e995UL);
408 Var<U64x1> k; // always set before used
409 Var<U64x1> h(uint64_t(values.size()) * m.clone());
410
411 for (auto &p : values) {
412 std::visit(overloaded {
413 [&]<typename T>(Expr<T> val) -> void {
414 k = val.hash();
415 k *= m.clone();
416 k = rotl(k, 47UL);
417 k *= m.clone();
418 h ^= k;
419 h = rotl(h, 45UL);
420 h = h * uint64_t(5UL) + uint64_t(0xe6546b64UL);
421 },
422 [&](NChar val) -> void { h ^= str_hash(val); },
423 [](auto) -> void { M_unreachable("SIMDfication currently not supported"); },
424 [](std::monostate) -> void { M_unreachable("invalid variant"); }
425 }, p.second);
426 }
427 h ^= uint64_t(values.size());
428
429 m.discard(); // since it was always cloned
430
431 return murmur3_bit_mix(h);
432}
433
434
435/*----- hash tables --------------------------------------------------------------------------------------------------*/
436
437std::pair<HashTable::size_t, HashTable::size_t>
438HashTable::set_byte_offsets(std::vector<HashTable::offset_t> &offsets_in_bytes, const std::vector<const Type*> &types,
439 HashTable::offset_t initial_offset_in_bytes,
440 HashTable::offset_t initial_max_alignment_in_bytes)
441{
442 if (types.empty())
443 return { initial_offset_in_bytes, initial_max_alignment_in_bytes };
444
445 /*----- Collect all indices. -----*/
446 std::size_t indices[types.size()];
447 std::iota(indices, indices + types.size(), 0);
448
449 /*----- Sort indices by alignment. -----*/
450 std::stable_sort(indices, indices + types.size(), [&](std::size_t left, std::size_t right) {
451 return types[left]->alignment() > types[right]->alignment();
452 });
453
454 /*----- Compute offsets. -----*/
455 offsets_in_bytes.resize(types.size());
456 HashTable::offset_t current_offset_in_bytes = initial_offset_in_bytes;
457 HashTable::offset_t max_alignment_in_bytes = initial_max_alignment_in_bytes;
458 for (std::size_t idx = 0; idx != types.size(); ++idx) {
459 const auto sorted_idx = indices[idx];
460 offsets_in_bytes[sorted_idx] = current_offset_in_bytes;
461 current_offset_in_bytes += (types[sorted_idx]->size() + 7) / 8;
462 max_alignment_in_bytes =
463 std::max<HashTable::offset_t>(max_alignment_in_bytes, (types[sorted_idx]->alignment() + 7) / 8);
464 }
465
466 /*----- Compute entry size with padding. -----*/
467 if (const auto rem = current_offset_in_bytes % max_alignment_in_bytes; rem)
468 current_offset_in_bytes += max_alignment_in_bytes - rem;
469 return { current_offset_in_bytes, max_alignment_in_bytes };
470}
471
472
473/*----- chained hash tables ------------------------------------------------------------------------------------------*/
474
475template<bool IsGlobal>
476ChainedHashTable<IsGlobal>::ChainedHashTable(const Schema &schema, std::vector<HashTable::index_t> key_indices,
477 uint32_t initial_capacity)
478 : HashTable(schema, std::move(key_indices))
479{
480 std::vector<const Type*> types;
481 bool has_nullable = false;
482
483 /*----- Add pointer to next entry in linked collision list. -----*/
484 types.push_back(Type::Get_Integer(Type::TY_Vector, sizeof(uint32_t)));
485
486 /*----- Add schema types. -----*/
487 for (auto &e : schema_.get()) {
488 types.push_back(e.type);
489 has_nullable |= e.nullable();
490 }
491
492 if (has_nullable) {
493 /*----- Add type for NULL bitmap. Pointer to next entry in collision list cannot be NULL. -----*/
494 types.push_back(Type::Get_Bitmap(Type::TY_Vector, types.size() - 1));
495 }
496
497 /*----- Compute entry offsets and set entry size and alignment requirement. -----*/
498 std::vector<HashTable::offset_t> offsets;
500
501 /*----- Set offset for pointer to next entry in collision list. -----*/
502 ptr_offset_in_bytes_ = offsets.front();
503
504 if (has_nullable) {
505 /*----- Set offset for NULL bitmap and remove it from `offsets`. -----*/
506 null_bitmap_offset_in_bytes_ = offsets.back();
507 offsets.pop_back();
508 }
509
510 /*----- Set entry offset. Exclude offset for pointer to next entry in collision list. -----*/
511 entry_offsets_in_bytes_ = std::vector<HashTable::offset_t>(std::next(offsets.begin()), offsets.end());
512
513 /*----- Initialize capacity and absolute high watermark. -----*/
514 const auto capacity_init = ceil_to_pow_2(initial_capacity);
515 const auto mask_init = capacity_init - 1U;
516 const auto high_watermark_absolute_init = capacity_init;
517 if constexpr (IsGlobal) {
518 storage_.address_.init(0), // init with nullptr
519 storage_.mask_.init(mask_init);
520 storage_.high_watermark_absolute_.init(high_watermark_absolute_init);
521 } else {
522 mask_.emplace(mask_init);
523 high_watermark_absolute_.emplace(high_watermark_absolute_init);
524 }
525}
526
527template<bool IsGlobal>
529{
530 if constexpr (IsGlobal) { // free memory of global hash table when object is destroyed and no use may occur later
531 /*----- Free collision list entries. -----*/
532 Var<Ptr<void>> it(storage_.address_);
533 const Var<Ptr<void>> end(storage_.address_ + ((storage_.mask_ + 1U) * uint32_t(sizeof(uint32_t))).make_signed());
534 WHILE (it != end) {
535 Wasm_insist(storage_.address_ <= it and it < end, "bucket out-of-bounds");
536 Var<Ptr<void>> bucket_it(Ptr<void>(*it.to<uint32_t*>()));
537 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
538 const Var<Ptr<void>> tmp(bucket_it);
539 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>());
540 Module::Allocator().deallocate(tmp, entry_size_in_bytes_);
541 }
542 it += int32_t(sizeof(uint32_t));
543 }
544
545 /*----- Free all buckets. -----*/
546 Module::Allocator().deallocate(storage_.address_, (storage_.mask_ + 1U) * uint32_t(sizeof(uint32_t)));
547
548 /*----- Free dummy entries. -----*/
549 for (auto it = dummy_allocations_.rbegin(); it != dummy_allocations_.rend(); ++it)
550 Module::Allocator().deallocate(it->first, it->second);
551 if (predication_dummy_) {
552#if 1
553 Wasm_insist(Ptr<void>(*predication_dummy_->template to<uint32_t*>()).is_nullptr(),
554 "predication dummy must always contain an empty collision list");
555#else
556 Var<Ptr<void>> bucket_it(Ptr<void>(*predication_dummy_->template to<uint32_t*>()));
557 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
558 const Var<Ptr<void>> tmp(bucket_it);
559 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).to<uint32_t*>());
560 Module::Allocator().deallocate(tmp, entry_size_in_bytes_);
561 }
562#endif
563 Module::Allocator().deallocate(*predication_dummy_, sizeof(uint32_t));
564 }
565 }
566}
567
568template<bool IsGlobal>
570{
571 M_insist(not address_, "must not call `setup()` twice");
572 M_insist(not num_entries_, "must not call `setup()` twice");
573
574 /*----- Create local variables. -----*/
575 address_.emplace();
576 num_entries_.emplace();
577 if constexpr (IsGlobal) {
578 M_insist(not mask_, "must not call `setup()` twice");
579 M_insist(not high_watermark_absolute_, "must not call `setup()` twice");
580 mask_.emplace();
581 high_watermark_absolute_.emplace();
582 } else {
583 M_insist(bool(mask_)); // already initialized in c'tor
584 M_insist(bool(high_watermark_absolute_)); // already initialized in c'tor
585 }
586
587 /*----- For global hash tables, read values from global backups into local variables. -----*/
588 if constexpr (IsGlobal) {
589 /* omit assigning address here as it will always be set below */
590 *mask_ = storage_.mask_;
591 *num_entries_ = storage_.num_entries_;
592 *high_watermark_absolute_ = storage_.high_watermark_absolute_;
593 }
594
595 if constexpr (IsGlobal) {
596 IF (storage_.address_.is_nullptr()) { // hash table not yet allocated
597 /*----- Allocate memory for initial capacity. -----*/
598 *address_ = Module::Allocator().allocate(size_in_bytes(), sizeof(uint32_t));
599
600 /*----- Clear initial hash table. -----*/
601 clear();
602 } ELSE {
603 *address_ = storage_.address_;
604 };
605 } else {
606 /*----- Allocate memory for initial capacity. -----*/
607 *address_ = Module::Allocator().allocate(size_in_bytes(), sizeof(uint32_t));
608
609 /*----- Clear initial hash table. -----*/
610 clear();
611 }
612}
613
614template<bool IsGlobal>
616{
617 M_insist(bool(address_), "must call `setup()` before");
618 M_insist(bool(mask_), "must call `setup()` before");
619 M_insist(bool(num_entries_), "must call `setup()` before");
620 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
621
622 if constexpr (not IsGlobal) { // free memory of local hash table when user calls teardown method
623 /*----- Free collision list entries. -----*/
624 Var<Ptr<void>> it(begin());
625 WHILE (it != end()) {
626 Wasm_insist(begin() <= it and it < end(), "bucket out-of-bounds");
627 Var<Ptr<void>> bucket_it(Ptr<void>(*it.to<uint32_t*>()));
628 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
629 const Var<Ptr<void>> tmp(bucket_it);
630 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>());
631 Module::Allocator().deallocate(tmp, entry_size_in_bytes_);
632 }
633 it += int32_t(sizeof(uint32_t));
634 }
635
636 /*----- Free all buckets. -----*/
637 Module::Allocator().deallocate(*address_, size_in_bytes());
638
639 /*----- Free dummy entries. -----*/
640 for (auto it = dummy_allocations_.rbegin(); it != dummy_allocations_.rend(); ++it)
641 Module::Allocator().deallocate(it->first, it->second);
642 if (predication_dummy_) {
643#if 1
644 Wasm_insist(Ptr<void>(*predication_dummy_->template to<uint32_t*>()).is_nullptr(),
645 "predication dummy must always contain an empty collision list");
646#else
647 Var<Ptr<void>> bucket_it(Ptr<void>(*predication_dummy_->template to<uint32_t*>()));
648 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
649 const Var<Ptr<void>> tmp(bucket_it);
650 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).to<uint32_t*>());
651 Module::Allocator().deallocate(tmp, entry_size_in_bytes_);
652 }
653#endif
654 Module::Allocator().deallocate(*predication_dummy_, sizeof(uint32_t));
655 }
656 }
657
658 /*----- For global hash tables, write values from local variables into global backups. -----*/
659 if constexpr (IsGlobal) {
660 storage_.address_ = *address_;
661 storage_.mask_ = *mask_;
662 storage_.num_entries_ = *num_entries_;
663 storage_.high_watermark_absolute_ = *high_watermark_absolute_;
664 }
665
666 /*----- Destroy local variables. -----*/
667 address_.reset();
668 mask_.reset();
669 num_entries_.reset();
670 high_watermark_absolute_.reset();
671}
672
673template<bool IsGlobal>
675{
676 Var<Ptr<void>> it(begin());
677 WHILE (it != end()) {
678 Wasm_insist(begin() <= it and it < end(), "entry out-of-bounds");
679#if 0
680 Var<Ptr<void>> bucket_it(Ptr<void>(*it.to<uint32_t*>())); // XXX: may be random address
681 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
682 const Var<Ptr<void>> tmp(bucket_it);
683 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).to<uint32_t*>());
684 Module::Allocator().deallocate(tmp, entry_size_in_bytes_); // free collision list entry
685 }
686#endif
687 *(it + ptr_offset_in_bytes_).template to<uint32_t*>() = 0U; // set to nullptr
688 it += int32_t(sizeof(uint32_t));
689 }
690}
691
692template<bool IsGlobal>
694{
695 M_insist(bool(mask_), "must call `setup()` before");
696 M_insist(key.size() == key_indices_.size(),
697 "provided number of key elements does not match hash table's number of key indices");
698
699 /*----- Collect types of key together with the respective value. -----*/
700 std::vector<std::pair<const Type*, SQL_t>> values;
701 values.reserve(key_indices_.size());
702 auto key_it = key.begin();
703 for (auto k : key_indices_)
704 values.emplace_back(schema_.get()[k].type, std::move(*key_it++));
705
706 /*----- Compute hash of key using Murmur3_64a. -----*/
707 U64x1 hash = murmur3_64a_hash(std::move(values));
708
709 /*----- Compute bucket address. -----*/
710 U32x1 bucket_idx = hash.to<uint32_t>() bitand *mask_; // modulo capacity
711 Ptr<void> bucket = begin() + (bucket_idx * uint32_t(sizeof(uint32_t))).make_signed();
712 Wasm_insist(begin() <= bucket.clone() and bucket.clone() < end(), "bucket out-of-bounds");
713 return bucket;
714}
715
716template<bool IsGlobal>
718{
719 /*----- If predication is used, introduce predication variable and update it before inserting a key. -----*/
720 std::optional<Boolx1> pred;
721 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
722 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
723 pred.emplace(env.extract_predicate<_Boolx1>().is_true_and_not_null());
724 if (not predication_dummy_)
725 const_cast<ChainedHashTable<IsGlobal>*>(this)->create_predication_dummy();
726 }
727 M_insist(not pred or predication_dummy_);
728
729 /*----- Compute bucket address by hashing the key. Create constant variable to do not recompute the hash. -----*/
730 const Var<Ptr<void>> bucket(
731 pred ? Select(*pred, hash_to_bucket(std::move(key)), *predication_dummy_) // use dummy if predicate is not fulfilled
732 : hash_to_bucket(std::move(key))
733 );
734
735 return bucket;
736}
737
738template<bool IsGlobal>
740{
741 M_insist(bool(num_entries_), "must call `setup()` before");
742 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
743
744 /*----- If high watermark is reached, perform rehashing and update high watermark. -----*/
745 IF (*num_entries_ == *high_watermark_absolute_) { // XXX: num_entries_ - 1U iff predication predicate is not fulfilled
746 rehash();
747 update_high_watermark();
748 };
749
750 return emplace_without_rehashing(std::move(key));
751}
752
753template<bool IsGlobal>
755{
756 M_insist(bool(num_entries_), "must call `setup()` before");
757 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
758
759 Wasm_insist(*num_entries_ < *high_watermark_absolute_);
760
761 /*----- If predication is used, introduce predication variable and update it before inserting a key. -----*/
762 std::optional<Var<Boolx1>> pred;
763 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
764 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
765 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
766 if (not predication_dummy_)
767 create_predication_dummy();
768 }
769 M_insist(not pred or predication_dummy_);
770
771 /*----- Compute bucket address by hashing the key. Create constant variable to do not recompute the hash. -----*/
772 const Var<Ptr<void>> bucket(
773 pred ? Select(*pred, hash_to_bucket(clone(key)), *predication_dummy_) // use dummy if predicate is not fulfilled
774 : hash_to_bucket(clone(key))
775 ); // clone key since we need it again for insertion
776
777 /*----- Allocate memory for entry. -----*/
778 Var<Ptr<void>> entry = Module::Allocator().allocate(entry_size_in_bytes_, entry_max_alignment_in_bytes_);
779
780 /*----- Iff no predication is used or predicate is fulfilled, insert entry at collision list's front. -----*/
781 *(entry + ptr_offset_in_bytes_).template to<uint32_t*>() = *bucket.to<uint32_t*>();
782 *bucket.to<uint32_t*>() = pred ? Select(*pred, entry.to<uint32_t>(), *bucket.to<uint32_t*>())
783 : entry.to<uint32_t>(); // FIXME: entry memory never freed iff predicate is not fulfilled
784
785 /*----- Update number of entries. -----*/
786 *num_entries_ += pred ? pred->to<uint32_t>() : U32x1(1);
787
788 /*----- Insert key. -----*/
789 insert_key(entry, std::move(key)); // move key at last use
790
791 /*----- Return entry handle containing all values. -----*/
792 return value_entry(entry);
793}
794
795template<bool IsGlobal>
796std::pair<HashTable::entry_t, Boolx1> ChainedHashTable<IsGlobal>::try_emplace(std::vector<SQL_t> key)
797{
798 M_insist(bool(num_entries_), "must call `setup()` before");
799 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
800
801 /*----- If high watermark is reached, perform rehashing and update high watermark. -----*/
802 IF (*num_entries_ == *high_watermark_absolute_) { // XXX: num_entries_ - 1U iff predication predicate is not fulfilled
803 rehash();
804 update_high_watermark();
805 };
806 Wasm_insist(*num_entries_ < *high_watermark_absolute_);
807
808 /*----- If predication is used, introduce predication variable and update it before inserting a key. -----*/
809 std::optional<Var<Boolx1>> pred;
810 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
811 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
812 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
813 if (not predication_dummy_)
814 create_predication_dummy();
815 }
816 M_insist(not pred or predication_dummy_);
817
818 /*----- Compute bucket address by hashing the key. Create constant variable to do not recompute the hash. -----*/
819 const Var<Ptr<void>> bucket(
820 pred ? Select(*pred, hash_to_bucket(clone(key)), *predication_dummy_) // use dummy if predicate is not fulfilled
821 : hash_to_bucket(clone(key))
822 ); // clone key since we need it again for insertion
823
824 /*----- Probe collision list, abort and skip insertion if key already exists. -----*/
825 Var<Boolx1> entry_inserted(false);
826 Var<Ptr<void>> bucket_it(Ptr<void>(*bucket.to<uint32_t*>()));
827 BLOCK(insert_entry) {
828 IF (bucket_it.is_nullptr()) { // empty collision list
829 bucket_it = bucket - ptr_offset_in_bytes_; // set bucket iterator to point to bucket's collision list front
830 } ELSE {
831 LOOP () {
832 GOTO(equal_key(bucket_it, clone(key)), insert_entry); // clone key (see above)
833 const Var<Ptr<void>> next_bucket_it(
834 Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>())
835 );
836 BREAK(next_bucket_it.is_nullptr());
837 bucket_it = next_bucket_it;
838 CONTINUE();
839 }
840 };
841 Wasm_insist(Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>()).is_nullptr());
842 if (pred)
843 Wasm_insist(*pred or bucket_it == bucket - ptr_offset_in_bytes_,
844 "predication dummy must always contain an empty collision list");
845
846 /*----- Set flag to indicate insertion. -----*/
847 entry_inserted = true;
848
849 /*----- Allocate memory for entry. -----*/
850 Var<Ptr<void>> entry = Module::Allocator().allocate(entry_size_in_bytes_, entry_max_alignment_in_bytes_);
851
852 /*----- Iff no predication is used or predicate is fulfilled, insert entry at the collision list's end. -----*/
853 *(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>() = pred ? Select(*pred, entry.to<uint32_t>(), 0U)
854 : entry.to<uint32_t>(); // FIXME: entry memory never freed iff predicate is not fulfilled
855
856 /*----- Set bucket iterator to inserted entry. -----*/
857 bucket_it = entry;
858
859 /*----- Update number of entries. -----*/
860 *num_entries_ += pred ? pred->to<uint32_t>() : U32x1(1);
861
862 /*----- Insert key. -----*/
863 insert_key(entry, std::move(key)); // move key at last use
864 }
865
866 /* GOTO from above jumps here */
867
868 /*----- Return entry handle containing all values and the flag whether an insertion was performed. -----*/
869 return { value_entry(bucket_it), entry_inserted };
870}
871
872template<bool IsGlobal>
873std::pair<HashTable::entry_t, Boolx1> ChainedHashTable<IsGlobal>::find(std::vector<SQL_t> key, hint_t bucket_hint)
874{
875 Ptr<void> bucket =
876 bucket_hint ? *bucket_hint : compute_bucket(clone(key)); // clone key since we need it again for comparison
877
878 /*----- Probe collision list, abort if key already exists. -----*/
879 Var<Ptr<void>> bucket_it(Ptr<void>(*bucket.to<uint32_t*>()));
880 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
881 BREAK(equal_key(bucket_it, std::move(key))); // move key at last use
882 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>());
883 }
884
885 /*----- Key is found iff end of collision list is not yet reached. -----*/
886 Boolx1 key_found = not bucket_it.is_nullptr();
887
888 /*----- Return entry handle containing both keys and values and the flag whether key was found. -----*/
889 return { value_entry(bucket_it), key_found };
890}
891
892template<bool IsGlobal>
894{
895 /*----- Iterate over all collision list entries and call pipeline (with entry handle argument). -----*/
896 Var<Ptr<void>> it(begin());
897 WHILE (it != end()) {
898 Wasm_insist(begin() <= it and it < end(), "bucket out-of-bounds");
899 Var<Ptr<void>> bucket_it(Ptr<void>(*it.to<uint32_t*>()));
900 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
901 Pipeline(entry(bucket_it));
902 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>());
903 }
904 it += int32_t(sizeof(uint32_t));
905 }
906}
907
908template<bool IsGlobal>
910 bool predicated, hint_t bucket_hint) const
911{
912 Ptr<void> bucket =
913 bucket_hint ? *bucket_hint : compute_bucket(clone(key)); // clone key since we need it again for comparison
914
915 /*----- Iterate over collision list entries and call pipeline (with entry handle argument) on matches. -----*/
916 Var<Ptr<void>> bucket_it(Ptr<void>(*bucket.to<uint32_t*>()));
917 WHILE (not bucket_it.is_nullptr()) { // another entry in collision list
918 if (predicated) {
919 CodeGenContext::Get().env().add_predicate(equal_key(bucket_it, std::move(key)));
920 Pipeline(entry(bucket_it));
921 } else {
922 IF (equal_key(bucket_it, std::move(key))) { // match found
923 Pipeline(entry(bucket_it));
924 };
925 }
926 bucket_it = Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>());
927 }
928}
929
930template<bool IsGlobal>
932{
933 /*----- Allocate memory for a dummy entry. -----*/
934 var_t<Ptr<void>> entry; // create global variable iff `IsGlobal` to be able to access it later for deallocation
935 entry = Module::Allocator().allocate(entry_size_in_bytes_, entry_max_alignment_in_bytes_);
936
937 /*----- Store address and size of dummy entry to free them later. -----*/
938 dummy_allocations_.emplace_back(entry, entry_size_in_bytes_);
939
940 /*----- Return *local* entry handle containing all values. -----*/
941 return value_entry(M_CONSTEXPR_COND(IsGlobal, Var<Ptr<void>>(entry.val()).val(), entry.val()));
942}
943
944template<bool IsGlobal>
945Boolx1 ChainedHashTable<IsGlobal>::equal_key(Ptr<void> entry, std::vector<SQL_t> key) const
946{
947 Var<Boolx1> res(true);
948
949 for (std::size_t i = 0; i < key_indices_.size(); ++i) {
950 /* do not skip duplicated comparison of the same slot since probing might need this comparison */
951
952 auto &e = schema_.get()[key_indices_[i]];
953 const auto off = entry_offsets_in_bytes_[key_indices_[i]];
954 auto compare_equal = [&]<typename T>() {
955 using type = typename T::type;
956 M_insist(std::holds_alternative<T>(key[i]));
957 if (e.nullable()) { // entry may be NULL
958 const_reference_t<T> ref((entry.clone() + off).template to<type*>(),
959 entry.clone() + null_bitmap_offset_in_bytes_, key_indices_[i]);
960 res = res and ref == *std::get_if<T>(&key[i]);
961 } else { // entry must not be NULL
962 const_reference_t<T> ref((entry.clone() + off).template to<type*>());
963 res = res and ref == *std::get_if<T>(&key[i]);
964 }
965 };
967 [&](const Boolean&) { compare_equal.template operator()<_Boolx1>(); },
968 [&](const Numeric &n) {
969 switch (n.kind) {
970 case Numeric::N_Int:
971 case Numeric::N_Decimal:
972 switch (n.size()) {
973 default: M_unreachable("invalid size");
974 case 8: compare_equal.template operator()<_I8x1 >(); break;
975 case 16: compare_equal.template operator()<_I16x1>(); break;
976 case 32: compare_equal.template operator()<_I32x1>(); break;
977 case 64: compare_equal.template operator()<_I64x1>(); break;
978 }
979 break;
980 case Numeric::N_Float:
981 if (n.size() <= 32)
982 compare_equal.template operator()<_Floatx1>();
983 else
984 compare_equal.template operator()<_Doublex1>();
985 }
986 },
987 [&](const CharacterSequence &cs) {
988 M_insist(std::holds_alternative<NChar>(key[i]));
989 NChar val((entry.clone() + off).template to<char*>(), e.nullable(), &cs);
990 if (e.nullable()) { // entry may be NULL
991 const_reference_t<NChar> ref(val, entry.clone() + null_bitmap_offset_in_bytes_, key_indices_[i]);
992 res = res and ref == *std::get_if<NChar>(&key[i]);
993 } else { // entry must not be NULL
995 res = res and ref == *std::get_if<NChar>(&key[i]);
996 }
997 },
998 [&](const Date&) { compare_equal.template operator()<_I32x1>(); },
999 [&](const DateTime&) { compare_equal.template operator()<_I64x1>(); },
1000 [](auto&&) { M_unreachable("invalid type"); },
1001 }, *e.type);
1002 }
1003
1004 entry.discard(); // since it was always cloned
1005
1006 return res;
1007}
1008
1009template<bool IsGlobal>
1010void ChainedHashTable<IsGlobal>::insert_key(Ptr<void> entry, std::vector<SQL_t> key)
1011{
1012 for (std::size_t i = 0; i < key_indices_.size(); ++i) {
1013 /* NOTE: `std::find` results in quadratic complexity but we expect rather short keys anyway */
1014 if (std::find(key_indices_.begin(), key_indices_.begin() + i, key_indices_[i]) != key_indices_.begin() + i) {
1015 discard(key[i]);
1016 continue; // skip duplicated writing of the same slot
1017 }
1018
1019 auto &e = schema_.get()[key_indices_[i]];
1020 const auto off = entry_offsets_in_bytes_[key_indices_[i]];
1021 auto insert = [&]<typename T>() {
1022 using type = typename T::type;
1023 M_insist(std::holds_alternative<T>(key[i]));
1024 if (e.nullable()) { // entry may be NULL
1025 reference_t<T> ref((entry.clone() + off).template to<type*>(),
1026 entry.clone() + null_bitmap_offset_in_bytes_, key_indices_[i]);
1027 ref = *std::get_if<T>(&key[i]);
1028 } else { // entry must not be NULL
1029 reference_t<T> ref((entry.clone() + off).template to<type*>());
1030 ref = *std::get_if<T>(&key[i]);
1031 }
1032 };
1034 [&](const Boolean&) { insert.template operator()<_Boolx1>(); },
1035 [&](const Numeric &n) {
1036 switch (n.kind) {
1037 case Numeric::N_Int:
1038 case Numeric::N_Decimal:
1039 switch (n.size()) {
1040 default: M_unreachable("invalid size");
1041 case 8: insert.template operator()<_I8x1 >(); break;
1042 case 16: insert.template operator()<_I16x1>(); break;
1043 case 32: insert.template operator()<_I32x1>(); break;
1044 case 64: insert.template operator()<_I64x1>(); break;
1045 }
1046 break;
1047 case Numeric::N_Float:
1048 if (n.size() <= 32)
1049 insert.template operator()<_Floatx1>();
1050 else
1051 insert.template operator()<_Doublex1>();
1052 }
1053 },
1054 [&](const CharacterSequence &cs) {
1055 M_insist(std::holds_alternative<NChar>(key[i]));
1056 NChar val((entry.clone() + off).template to<char*>(), e.nullable(), &cs);
1057 if (e.nullable()) { // entry may be NULL
1058 reference_t<NChar> ref(val, entry.clone() + null_bitmap_offset_in_bytes_, key_indices_[i]);
1059 ref = *std::get_if<NChar>(&key[i]);
1060 } else { // entry must not be NULL
1061 reference_t<NChar> ref(val);
1062 ref = *std::get_if<NChar>(&key[i]);
1063 }
1064 },
1065 [&](const Date&) { insert.template operator()<_I32x1>(); },
1066 [&](const DateTime&) { insert.template operator()<_I64x1>(); },
1067 [](auto&&) { M_unreachable("invalid type"); },
1068 }, *e.type);
1069 }
1070
1071 entry.discard(); // since it was always cloned
1072}
1073
1074template<bool IsGlobal>
1076{
1077 entry_t value_entry;
1078
1079 for (std::size_t i = 0; i < value_indices_.size(); ++i) {
1080 /* there are no duplicates in the value indexes */
1081
1082 auto &e = schema_.get()[value_indices_[i]];
1083 const auto off = entry_offsets_in_bytes_[value_indices_[i]];
1084 auto add = [&]<typename T>() {
1085 using type = typename T::type;
1086 if (e.nullable()) { // entry may be NULL
1087 reference_t<T> ref((entry.clone() + off).template to<type*>(),
1088 entry.clone() + null_bitmap_offset_in_bytes_, value_indices_[i]);
1089 value_entry.add(e.id, std::move(ref));
1090 } else { // entry must not be NULL
1091 reference_t<T> ref((entry.clone() + off).template to<type*>());
1092 value_entry.add(e.id, std::move(ref));
1093 }
1094 };
1096 [&](const Boolean&) { add.template operator()<_Boolx1>(); },
1097 [&](const Numeric &n) {
1098 switch (n.kind) {
1099 case Numeric::N_Int:
1100 case Numeric::N_Decimal:
1101 switch (n.size()) {
1102 default: M_unreachable("invalid size");
1103 case 8: add.template operator()<_I8x1 >(); break;
1104 case 16: add.template operator()<_I16x1>(); break;
1105 case 32: add.template operator()<_I32x1>(); break;
1106 case 64: add.template operator()<_I64x1>(); break;
1107 }
1108 break;
1109 case Numeric::N_Float:
1110 if (n.size() <= 32)
1111 add.template operator()<_Floatx1>();
1112 else
1113 add.template operator()<_Doublex1>();
1114 }
1115 },
1116 [&](const CharacterSequence &cs) {
1117 NChar val((entry.clone() + off).template to<char*>(), e.nullable(), &cs);
1118 if (e.nullable()) { // entry may be NULL
1119 reference_t<NChar> ref(val, entry.clone() + null_bitmap_offset_in_bytes_, value_indices_[i]);
1120 value_entry.add(e.id, std::move(ref));
1121 } else { // entry must not be NULL
1122 reference_t<NChar> ref(val);
1123 value_entry.add(e.id, std::move(ref));
1124 }
1125 },
1126 [&](const Date&) { add.template operator()<_I32x1>(); },
1127 [&](const DateTime&) { add.template operator()<_I64x1>(); },
1128 [](auto&&) { M_unreachable("invalid type"); },
1129 }, *e.type);
1130 }
1131
1132 entry.discard(); // since it was always cloned
1133
1134 return value_entry;
1135}
1136
1137template<bool IsGlobal>
1139{
1140 const_entry_t _entry;
1141
1142 for (std::size_t i = 0; i < key_indices_.size() + value_indices_.size(); ++i) {
1143 const bool is_key = i < key_indices_.size();
1144 /* NOTE: `std::find` results in quadratic complexity but we expect rather short keys anyway */
1145 if (is_key and std::find(key_indices_.begin(), key_indices_.begin() + i, key_indices_[i]) != key_indices_.begin() + i)
1146 continue; // skip duplicated key to the same slot
1147
1148 const auto idx = is_key ? key_indices_[i] : value_indices_[i - key_indices_.size()];
1149 auto &e = schema_.get()[idx];
1150 const auto off = entry_offsets_in_bytes_[idx];
1151 auto add = [&]<typename T>() {
1152 using type = typename T::type;
1153 if (e.nullable()) { // entry may be NULL
1154 const_reference_t<T> ref((entry.clone() + off).template to<type*>(),
1155 entry.clone() + null_bitmap_offset_in_bytes_, idx);
1156 _entry.add(e.id, std::move(ref));
1157 } else { // entry must not be NULL
1158 const_reference_t<T> ref((entry.clone() + off).template to<type*>());
1159 _entry.add(e.id, std::move(ref));
1160 }
1161 };
1163 [&](const Boolean&) { add.template operator()<_Boolx1>(); },
1164 [&](const Numeric &n) {
1165 switch (n.kind) {
1166 case Numeric::N_Int:
1167 case Numeric::N_Decimal:
1168 switch (n.size()) {
1169 default: M_unreachable("invalid size");
1170 case 8: add.template operator()<_I8x1 >(); break;
1171 case 16: add.template operator()<_I16x1>(); break;
1172 case 32: add.template operator()<_I32x1>(); break;
1173 case 64: add.template operator()<_I64x1>(); break;
1174 }
1175 break;
1176 case Numeric::N_Float:
1177 if (n.size() <= 32)
1178 add.template operator()<_Floatx1>();
1179 else
1180 add.template operator()<_Doublex1>();
1181 }
1182 },
1183 [&](const CharacterSequence &cs) {
1184 NChar val((entry.clone() + off).template to<char*>(), e.nullable(), &cs);
1185 if (e.nullable()) { // entry may be NULL
1186 const_reference_t<NChar> ref(val, entry.clone() + null_bitmap_offset_in_bytes_, idx);
1187 _entry.add(e.id, std::move(ref));
1188 } else { // entry must not be NULL
1189 const_reference_t<NChar> ref(val);
1190 _entry.add(e.id, std::move(ref));
1191 }
1192 },
1193 [&](const Date&) { add.template operator()<_I32x1>(); },
1194 [&](const DateTime&) { add.template operator()<_I64x1>(); },
1195 [](auto&&) { M_unreachable("invalid type"); },
1196 }, *e.type);
1197 }
1198
1199 entry.discard(); // since it was always cloned
1200
1201 return _entry;
1202}
1203
1204template<bool IsGlobal>
1206{
1207 if (options::insist_no_rehashing)
1208 Throw(exception::unreachable, "rehashing must not occur");
1209
1210 auto emit_rehash = [this](){
1211 auto S = CodeGenContext::Get().scoped_environment(); // fresh environment to remove predication while rehashing
1212
1213 M_insist(bool(address_), "must call `setup()` before");
1214 M_insist(bool(mask_), "must call `setup()` before");
1215
1216 /*----- Store old begin and end (since they will be overwritten). -----*/
1217 const Var<Ptr<void>> begin_old(begin());
1218 const Var<Ptr<void>> end_old(end());
1219
1220 /*----- Double capacity. -----*/
1221 *mask_ = (*mask_ << 1U) + 1U;
1222
1223 /*----- Allocate memory for new hash table with updated capacity. -----*/
1224 *address_ = Module::Allocator().allocate(size_in_bytes(), sizeof(uint32_t));
1225
1226 /*----- Clear newly created hash table. -----*/
1227 clear();
1228
1229 /*----- Insert each element from old hash table into new one. -----*/
1230 Var<Ptr<void>> it(begin_old.val());
1231 WHILE (it != end_old) {
1232 Wasm_insist(begin_old <= it and it < end_old, "bucket out-of-bounds");
1233 Var<Ptr<void>> bucket_it(Ptr<void>(*it.to<uint32_t*>()));
1234 WHILE (not bucket_it.is_nullptr()) { // another entry in old collision list
1235 auto e_old = entry(it);
1236
1237 /*----- Access key from old entry. -----*/
1238 std::vector<SQL_t> key;
1239 for (auto k : key_indices_) {
1240 std::visit(overloaded {
1241 [&](auto &&r) -> void { key.emplace_back(r); },
1242 [](std::monostate) -> void { M_unreachable("invalid reference"); },
1243 }, e_old.get(schema_.get()[k].id));
1244 }
1245
1246 /*----- Compute new bucket address by hashing the key. Create variable to do not recompute the hash. -*/
1247 const Var<Ptr<void>> bucket(hash_to_bucket(std::move(key)));
1248
1249 /*----- Store next entry's address in old collision list (since it will be overwritten). -----*/
1250 const Var<Ptr<void>> tmp(Ptr<void>(*(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>()));
1251
1252 /*----- Insert old entry at new collision list's front. No reallocation of the entry is needed. -----*/
1253 *(bucket_it + ptr_offset_in_bytes_).template to<uint32_t*>() = *bucket.to<uint32_t*>();
1254 *bucket.to<uint32_t*>() = bucket_it.to<uint32_t>();
1255
1256 /*----- Advance to next entry in old collision list. -----*/
1257 bucket_it = tmp.val();
1258 }
1259
1260 /*----- Advance to next bucket in old hash table. -----*/
1261 it += int32_t(sizeof(uint32_t));
1262 }
1263
1264 /*----- Free old hash table (without collision list entries since they are reused). -----*/
1265 U32x1 size = (end_old - begin_old).make_unsigned();
1266 Module::Allocator().deallocate(begin_old, size);
1267 };
1268
1269 if constexpr (IsGlobal) {
1270 if (not rehash_) {
1271 /*----- Backup former local variables to be able to use new ones for rehashing function. -----*/
1272 auto old_address = std::exchange(address_, std::nullopt);
1273 auto old_mask = std::exchange(mask_, std::nullopt);
1274 /* omit `num_entries_` and `high_watermark_absolute_` as they are never accessed during rehashing */
1275
1276 /*----- Create function for rehashing. -----*/
1277 FUNCTION(rehash, void(void))
1278 {
1279 /*----- Perform setup for local variables. -----*/
1280 address_.emplace(storage_.address_);
1281 mask_.emplace(storage_.mask_);
1282
1283 emit_rehash();
1284
1285 /*----- Perform teardown for local variables. -----*/
1286 storage_.address_ = *address_;
1287 storage_.mask_ = *mask_;
1288 address_.reset();
1289 mask_.reset();
1290 }
1291 rehash_ = std::move(rehash);
1292
1293 /*----- Restore local variables. -----*/
1294 std::exchange(address_, std::move(old_address));
1295 std::exchange(mask_, std::move(old_mask));
1296 }
1297
1298 /*----- Store local variables in global backups. -----*/
1299 storage_.address_ = *address_;
1300 storage_.mask_ = *mask_;
1301
1302 /*----- Call rehashing function. ------*/
1303 M_insist(bool(rehash_));
1304 (*rehash_)();
1305
1306 /*----- Restore local variables from global backups. -----*/
1307 *address_ = storage_.address_;
1308 *mask_ = storage_.mask_;
1309 } else {
1310 /*----- Emit rehashing code. ------*/
1311 emit_rehash();
1312 }
1313}
1314
1315// explicit instantiations to prevent linker errors
1316template struct m::wasm::ChainedHashTable<false>;
1317template struct m::wasm::ChainedHashTable<true>;
1318
1319
1320/*----- open addressing hash tables ----------------------------------------------------------------------------------*/
1321
1323{
1324 Var<Ptr<void>> it(begin());
1325 WHILE (it != end()) {
1326 Wasm_insist(begin() <= it and it < end(), "entry out-of-bounds");
1327 reference_count(it) = ref_t(0);
1328 it += int32_t(entry_size_in_bytes_);
1329 }
1330}
1331
1333{
1334 M_insist(key.size() == key_indices_.size(),
1335 "provided number of key elements does not match hash table's number of key indices");
1336
1337 /*----- Collect types of key together with the respective value. -----*/
1338 std::vector<std::pair<const Type*, SQL_t>> values;
1339 values.reserve(key_indices_.size());
1340 auto key_it = key.begin();
1341 for (auto k : key_indices_)
1342 values.emplace_back(schema_.get()[k].type, std::move(*key_it++));
1343
1344 /*----- Compute hash of key using Murmur3_64a. -----*/
1345 U64x1 hash = murmur3_64a_hash(std::move(values));
1346
1347 /*----- Compute bucket address. -----*/
1348 U32x1 bucket_idx = hash.to<uint32_t>() bitand mask(); // modulo capacity
1349 Ptr<void> bucket = begin() + (bucket_idx * entry_size_in_bytes_).make_signed();
1350 Wasm_insist(begin() <= bucket.clone() and bucket.clone() < end(), "bucket out-of-bounds");
1351 return bucket;
1352}
1353
1354template<bool IsGlobal, bool ValueInPlace>
1356 std::vector<HashTable::index_t> key_indices,
1357 uint32_t initial_capacity)
1358 : OpenAddressingHashTableBase(schema, std::move(key_indices))
1359{
1360 std::vector<const Type*> types;
1361 bool has_nullable = false;
1362
1363 /*----- Add reference counter. -----*/
1364 types.push_back(Type::Get_Integer(Type::TY_Vector, sizeof(ref_t)));
1365
1366 if constexpr (ValueInPlace) {
1367 /*----- Add schema types. -----*/
1368 for (auto &e : schema_.get()) {
1369 types.push_back(e.type);
1370 has_nullable |= e.nullable();
1371 }
1372
1373 if (has_nullable) {
1374 /*----- Add type for NULL bitmap. Reference counter cannot be NULL. -----*/
1375 types.push_back(Type::Get_Bitmap(Type::TY_Vector, types.size() - 1));
1376 }
1377
1378 /*----- Compute entry offsets and set entry size and alignment requirement. -----*/
1379 std::vector<HashTable::offset_t> offsets;
1381
1382 /*----- Set offset for reference counter. -----*/
1383 refs_offset_in_bytes_ = offsets.front();
1384
1385 if (has_nullable) {
1386 /*----- Set offset for NULL bitmap and remove it from `offsets`. -----*/
1387 layout_.null_bitmap_offset_in_bytes_ = offsets.back();
1388 offsets.pop_back();
1389 }
1390
1391 /*----- Set entry offset. Exclude offset for reference counter. -----*/
1392 layout_.entry_offsets_in_bytes_ = std::vector<HashTable::offset_t>(std::next(offsets.begin()), offsets.end());
1393 } else {
1394 /*----- Add key types. -----*/
1395 for (std::size_t i = 0; i < schema_.get().num_entries(); ++i) {
1396 if (not contains(key_indices_, i))
1397 continue;
1398 types.push_back(schema_.get()[i].type);
1399 has_nullable |= schema_.get()[i].nullable();
1400 }
1401
1402 /*----- Add type for pointer to out-of-place values. -----*/
1403 types.push_back(Type::Get_Integer(Type::TY_Vector, 4));
1404
1405 if (has_nullable) {
1406 /*----- Add type for keys NULL bitmap. Reference counter and pointer to values cannot be NULL. -----*/
1407 types.push_back(Type::Get_Bitmap(Type::TY_Vector, types.size() - 2));
1408 }
1409
1410 /*----- Compute entry offsets and set entry size and alignment requirement. -----*/
1411 std::vector<HashTable::offset_t> offsets;
1413
1414 /*----- Set offset for reference counter. -----*/
1415 refs_offset_in_bytes_ = offsets.front();
1416
1417 if (has_nullable) {
1418 /*----- Set offset for keys NULL bitmap and remove it from `offsets`. -----*/
1419 layout_.keys_null_bitmap_offset_in_bytes_ = offsets.back();
1420 offsets.pop_back();
1421 }
1422
1423 /*----- Set offset for pointer to out-of-place values and key offsets. Exclude offset for reference counter. -*/
1424 layout_.ptr_offset_in_bytes_ = offsets.back();
1425 layout_.key_offsets_in_bytes_ =
1426 std::vector<HashTable::offset_t>(std::next(offsets.begin()), std::prev(offsets.end()));
1427
1428 /*----- Add value types. -----*/
1429 types.clear();
1430 has_nullable = false;
1431 for (std::size_t i = 0; i < schema_.get().num_entries(); ++i) {
1432 if (not contains(value_indices_, i))
1433 continue;
1434 types.push_back(schema_.get()[i].type);
1435 has_nullable |= schema_.get()[i].nullable();
1436 }
1437
1438 if (has_nullable) {
1439 /*----- Add type for values NULL bitmap. -----*/
1440 types.push_back(Type::Get_Bitmap(Type::TY_Vector, types.size()));
1441
1442 /*----- Compute out-of-place entry offsets and set entry size and alignment requirement. -----*/
1443 offsets.clear();
1444 std::tie(layout_.values_size_in_bytes_, layout_.values_max_alignment_in_bytes_) =
1445 set_byte_offsets(offsets, types);
1446
1447 /*----- Set offset for values NULL bitmap and value offsets. -----*/
1448 layout_.values_null_bitmap_offset_in_bytes_ = offsets.back();
1449 layout_.value_offsets_in_bytes_ =
1450 std::vector<HashTable::offset_t>(offsets.begin(), std::prev(offsets.end()));
1451 } else {
1452 /*----- Set value offsets, size, and alignment requirement. -----*/
1453 std::tie(layout_.values_size_in_bytes_, layout_.values_max_alignment_in_bytes_) =
1454 set_byte_offsets(layout_.value_offsets_in_bytes_, types);
1455 }
1456 }
1457
1458 /*----- Initialize capacity and absolute high watermark. -----*/
1459 M_insist(initial_capacity < std::numeric_limits<uint32_t>::max(),
1460 "incremented initial capacity would exceed data type");
1461 ++initial_capacity; // since at least one entry must always be unoccupied for lookups
1462 /* at least capacity 4 to ensure absolute high watermark of at least 1 even for minimal percentage of 0.5 */
1463 const auto capacity_init = std::max<uint32_t>(4, ceil_to_pow_2(initial_capacity));
1464 const auto mask_init = capacity_init - 1U;
1465 const auto high_watermark_absolute_init = capacity_init - 1U; // at least one entry must always be unoccupied
1466 if constexpr (IsGlobal) {
1467 storage_.address_.init(0), // init with nullptr
1468 storage_.mask_.init(mask_init);
1469 storage_.high_watermark_absolute_.init(high_watermark_absolute_init);
1470 } else {
1471 mask_.emplace(mask_init);
1472 high_watermark_absolute_.emplace(high_watermark_absolute_init);
1473 }
1474}
1475
1476template<bool IsGlobal, bool ValueInPlace>
1478{
1479 if constexpr (IsGlobal) { // free memory of global hash table when object is destroyed and no use may occur later
1480 if constexpr (not ValueInPlace) {
1481 /*----- Free out-of-place values. -----*/
1482 Var<Ptr<void>> it(storage_.address_);
1483 const Var<Ptr<void>> end(storage_.address_ + ((storage_.mask_ + 1U) * entry_size_in_bytes_).make_signed());
1484 WHILE (it != end) {
1485 Wasm_insist(storage_.address_ <= it and it < end, "entry out-of-bounds");
1486 IF (reference_count(it) != ref_t(0)) { // occupied
1487 Module::Allocator().deallocate(Ptr<void>(*(it + layout_.ptr_offset_in_bytes_).template to<uint32_t*>()),
1488 layout_.values_size_in_bytes_);
1489 };
1490 it += int32_t(entry_size_in_bytes_);
1491 }
1492 }
1493
1494 /*----- Free all entries. -----*/
1495 Module::Allocator().deallocate(storage_.address_, (storage_.mask_ + 1U) * entry_size_in_bytes_);
1496
1497 /*----- Free dummy entries. -----*/
1498 for (auto it = dummy_allocations_.rbegin(); it != dummy_allocations_.rend(); ++it)
1499 Module::Allocator().deallocate(it->first, it->second);
1500 }
1501}
1502
1503template<bool IsGlobal, bool ValueInPlace>
1505{
1506 M_insist(not address_, "must not call `setup()` twice");
1507 M_insist(not num_entries_, "must not call `setup()` twice");
1508
1509 /*----- Create local variables. -----*/
1510 address_.emplace();
1511 num_entries_.emplace();
1512 if constexpr (IsGlobal) {
1513 M_insist(not mask_, "must not call `setup()` twice");
1514 M_insist(not high_watermark_absolute_, "must not call `setup()` twice");
1515 mask_.emplace();
1516 high_watermark_absolute_.emplace();
1517 } else {
1518 M_insist(bool(mask_)); // already initialized in c'tor
1519 M_insist(bool(high_watermark_absolute_)); // already initialized in c'tor
1520 }
1521
1522 /*----- For global hash tables, read values from global backups into local variables. -----*/
1523 if constexpr (IsGlobal) {
1524 /* omit assigning address here as it will always be set below */
1525 *mask_ = storage_.mask_;
1526 *num_entries_ = storage_.num_entries_;
1527 *high_watermark_absolute_ = storage_.high_watermark_absolute_;
1528 }
1529
1530 if constexpr (IsGlobal) {
1531 IF (storage_.address_.is_nullptr()) { // hash table not yet allocated
1532 /*----- Allocate memory for initial capacity. -----*/
1533 *address_ = Module::Allocator().allocate(size_in_bytes(), entry_max_alignment_in_bytes_);
1534
1535 /*----- Clear initial hash table. -----*/
1536 clear();
1537 } ELSE {
1538 *address_ = storage_.address_;
1539 };
1540 } else {
1541 /*----- Allocate memory for initial capacity. -----*/
1542 *address_ = Module::Allocator().allocate(size_in_bytes(), entry_max_alignment_in_bytes_);
1543
1544 /*----- Clear initial hash table. -----*/
1545 clear();
1546 }
1547}
1548
1549template<bool IsGlobal, bool ValueInPlace>
1551{
1552 M_insist(bool(address_), "must call `setup()` before");
1553 M_insist(bool(mask_), "must call `setup()` before");
1554 M_insist(bool(num_entries_), "must call `setup()` before");
1555 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
1556
1557 if constexpr (not IsGlobal) { // free memory of local hash table when user calls teardown method
1558 if constexpr (not ValueInPlace) {
1559 /*----- Free out-of-place values. -----*/
1560 Var<Ptr<void>> it(begin());
1561 WHILE (it != end()) {
1562 Wasm_insist(begin() <= it and it < end(), "entry out-of-bounds");
1563 IF (reference_count(it) != ref_t(0)) { // occupied
1564 Module::Allocator().deallocate(Ptr<void>(*(it + layout_.ptr_offset_in_bytes_).template to<uint32_t*>()),
1565 layout_.values_size_in_bytes_);
1566 };
1567 it += int32_t(entry_size_in_bytes_);
1568 }
1569 }
1570
1571 /*----- Free all entries. -----*/
1572 Module::Allocator().deallocate(*address_, size_in_bytes());
1573
1574 /*----- Free dummy entries. -----*/
1575 for (auto it = dummy_allocations_.rbegin(); it != dummy_allocations_.rend(); ++it)
1576 Module::Allocator().deallocate(it->first, it->second);
1577 }
1578
1579 /*----- For global hash tables, write values from local variables into global backups. -----*/
1580 if constexpr (IsGlobal) {
1581 storage_.address_ = *address_;
1582 storage_.mask_ = *mask_;
1583 storage_.num_entries_ = *num_entries_;
1584 storage_.high_watermark_absolute_ = *high_watermark_absolute_;
1585 }
1586
1587 /*----- Destroy local variables. -----*/
1588 address_.reset();
1589 mask_.reset();
1590 num_entries_.reset();
1591 high_watermark_absolute_.reset();
1592}
1593
1594template<bool IsGlobal, bool ValueInPlace>
1596{
1597 /*----- If predication is used, introduce predication variable and update it before inserting a key. -----*/
1598 std::optional<Boolx1> pred;
1599 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
1600 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
1601 pred.emplace(env.extract_predicate<_Boolx1>().is_true_and_not_null());
1602 if (not predication_dummy_)
1603 const_cast<OpenAddressingHashTable<IsGlobal, ValueInPlace>*>(this)->create_predication_dummy();
1604 }
1605 M_insist(not pred or predication_dummy_);
1606
1607 /*----- Compute bucket address by hashing the key. Create constant variable to do not recompute the hash. -----*/
1608 const Var<Ptr<void>> bucket(
1609 pred ? Select(*pred, hash_to_bucket(std::move(key)), *predication_dummy_) // use dummy if predicate is not fulfilled
1610 : hash_to_bucket(std::move(key))
1611 );
1612
1613 return bucket;
1614}
1615
1616template<bool IsGlobal, bool ValueInPlace>
1618{
1619 M_insist(bool(num_entries_), "must call `setup()` before");
1620 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
1621
1622 /*----- If high watermark is reached, perform rehashing and update high watermark. -----*/
1623 IF (*num_entries_ == *high_watermark_absolute_) { // XXX: num_entries_ - 1U iff predication predicate is not fulfilled
1624 rehash();
1625 update_high_watermark();
1626 };
1627
1628 auto slot = emplace_without_rehashing(std::move(key));
1629
1630 if constexpr (ValueInPlace) {
1631 /*----- Return entry handle containing all values. -----*/
1632 return value_entry(slot);
1633 } else {
1634 /*----- Allocate memory for out-of-place values and set pointer to it. -----*/
1635 Ptr<void> ptr =
1636 Module::Allocator().allocate(layout_.values_size_in_bytes_, layout_.values_max_alignment_in_bytes_);
1637 *(slot + layout_.ptr_offset_in_bytes_).template to<uint32_t*>() = ptr.clone().to<uint32_t>();
1638
1639 /*----- Return entry handle containing all values. -----*/
1640 return value_entry(ptr);
1641 }
1642}
1643
1644template<bool IsGlobal, bool ValueInPlace>
1646{
1647 M_insist(bool(num_entries_), "must call `setup()` before");
1648 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
1649
1650 Wasm_insist(*num_entries_ < *high_watermark_absolute_);
1651
1652 /*----- If predication is used, introduce predication variable and update it before inserting a key. -----*/
1653 std::optional<Var<Boolx1>> pred;
1654 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
1655 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
1656 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
1657 if (not predication_dummy_)
1658 create_predication_dummy();
1659 }
1660 M_insist(not pred or predication_dummy_);
1661
1662 /*----- Compute bucket address by hashing the key. Create constant variable to do not recompute the hash. -----*/
1663 const Var<Ptr<void>> bucket(
1664 pred ? Select(*pred, hash_to_bucket(clone(key)), *predication_dummy_) // use dummy if predicate is not fulfilled
1665 : hash_to_bucket(clone(key))
1666 ); // clone key since we need it again for insertion
1667
1668 /*----- Get reference count, i.e. occupied slots, of this bucket. -----*/
1669 Var<PrimitiveExpr<ref_t>> refs(reference_count(bucket));
1670
1671 /*----- Skip slots which are occupied anyway. -----*/
1672 Ptr<void> _slot = probing_strategy().skip_slots(bucket, refs);
1673 Wasm_insist(begin() <= _slot.clone() and _slot.clone() < end(), "slot out-of-bounds");
1674 Var<Ptr<void>> slot(_slot);
1675
1676 /*----- Search first unoccupied slot. -----*/
1677 WHILE (reference_count(slot) != ref_t(0)) {
1678 refs += ref_t(1);
1679 Wasm_insist(refs <= *num_entries_, "probing strategy has to find unoccupied slot if there is one");
1680 slot = probing_strategy().advance_to_next_slot(slot, refs);
1681 Wasm_insist(begin() <= slot and slot < end(), "slot out-of-bounds");
1682 }
1683
1684 /*----- Update reference count of this bucket. -----*/
1685 reference_count(bucket) = refs + ref_t(1); // no predication special case since bucket equals slot if dummy is used
1686
1687 /*----- Iff no predication is used or predicate is fulfilled, set slot as occupied. -----*/
1688 reference_count(slot) = pred ? pred->to<ref_t>() : PrimitiveExpr<ref_t>(1);
1689
1690 /*----- Update number of entries. -----*/
1691 *num_entries_ += pred ? pred->to<uint32_t>() : U32x1(1);
1692 Wasm_insist(*num_entries_ < capacity(), "at least one entry must always be unoccupied for lookups");
1693
1694 /*----- Insert key. -----*/
1695 insert_key(slot, std::move(key)); // move key at last use
1696
1697 return slot;
1698}
1699
1700template<bool IsGlobal, bool ValueInPlace>
1701std::pair<HashTable::entry_t, Boolx1>
1703{
1704 M_insist(bool(num_entries_), "must call `setup()` before");
1705 M_insist(bool(high_watermark_absolute_), "must call `setup()` before");
1706
1707 /*----- If high watermark is reached, perform rehashing and update high watermark. -----*/
1708 IF (*num_entries_ == *high_watermark_absolute_) { // XXX: num_entries_ - 1U iff predication predicate is not fulfilled
1709 rehash();
1710 update_high_watermark();
1711 };
1712 Wasm_insist(*num_entries_ < *high_watermark_absolute_);
1713
1714 /*----- If predication is used, introduce predication variable and update it before inserting a key. -----*/
1715 std::optional<Var<Boolx1>> pred;
1716 if (auto &env = CodeGenContext::Get().env(); env.predicated()) {
1717 M_insist(CodeGenContext::Get().num_simd_lanes() == 1, "invalid number of SIMD lanes");
1718 pred = env.extract_predicate<_Boolx1>().is_true_and_not_null();
1719 if (not predication_dummy_)
1720 create_predication_dummy();
1721 }
1722 M_insist(not pred or predication_dummy_);
1723
1724 /*----- Compute bucket address by hashing the key. Create constant variable to do not recompute the hash. -----*/
1725 const Var<Ptr<void>> bucket(
1726 pred ? Select(*pred, hash_to_bucket(clone(key)), *predication_dummy_) // use dummy if predicate is not fulfilled
1727 : hash_to_bucket(clone(key))
1728 ); // clone key since we need it again for insertion
1729
1730 /*----- Set reference count, i.e. occupied slots, of this bucket to its initial value. -----*/
1731 Var<PrimitiveExpr<ref_t>> refs(0);
1732
1733 /*----- Probe slots, abort and skip insertion if key already exists. -----*/
1734 Var<Boolx1> entry_inserted(false);
1735 Var<Ptr<void>> slot(bucket.val());
1736 BLOCK(insert_entry) {
1737 WHILE (reference_count(slot) != ref_t(0)) {
1738 GOTO(equal_key(slot, clone(key)), insert_entry); // clone key (see above)
1739 refs += ref_t(1);
1740 Wasm_insist(refs <= *num_entries_, "probing strategy has to find unoccupied slot if there is one");
1741 slot = probing_strategy().advance_to_next_slot(slot, refs);
1742 Wasm_insist(begin() <= slot and slot < end(), "slot out-of-bounds");
1743 }
1744 Wasm_insist(reference_count(slot) == ref_t(0));
1745 if (pred)
1746 Wasm_insist(*pred or refs == ref_t(0), "predication dummy must always be unoccupied");
1747
1748 /*----- Set flag to indicate insertion. -----*/
1749 entry_inserted = true;
1750
1751 /*----- Update reference count of this bucket. -----*/
1752 Wasm_insist(reference_count(bucket) <= refs, "reference count must increase if unoccupied slot is found");
1753 reference_count(bucket) = refs + ref_t(1); // no pred. special case since bucket equals slot if dummy is used
1754
1755 /*----- Iff no predication is used or predicate is fulfilled, set slot as occupied. -----*/
1756 reference_count(slot) = pred ? pred->to<ref_t>() : PrimitiveExpr<ref_t>(1);
1757
1758 /*----- Update number of entries. -----*/
1759 *num_entries_ += pred ? pred->to<uint32_t>() : U32x1(1);
1760 Wasm_insist(*num_entries_ < capacity(), "at least one entry must always be unoccupied for lookups");
1761
1762 /*----- Insert key. -----*/
1763 insert_key(slot, std::move(key)); // move key at last use
1764
1765 if constexpr (not ValueInPlace) {
1766 /*----- Allocate memory for out-of-place values and set pointer to it. -----*/
1767 Ptr<void> ptr =
1768 Module::Allocator().allocate(layout_.values_size_in_bytes_, layout_.values_max_alignment_in_bytes_);
1769 *(slot + layout_.ptr_offset_in_bytes_).template to<uint32_t*>() = ptr.clone().to<uint32_t>();
1770
1771 if (pred) {
1772 /*----- Store address and size of dummy predication entry to free them later. -----*/
1773 var_t<Ptr<void>> ptr_; // create global variable iff `IsGlobal` to access it later for deallocation
1774 ptr_ = ptr.clone();
1775 dummy_allocations_.emplace_back(ptr_, layout_.values_size_in_bytes_);
1776 }
1777
1778 ptr.discard(); // since it was always cloned
1779 }
1780 }
1781
1782 /* GOTO from above jumps here */
1783
1784 if constexpr (not ValueInPlace) {
1785 /*----- Set slot pointer to out-of-place values. -----*/
1786 slot = *(slot + layout_.ptr_offset_in_bytes_).template to<uint32_t*>();
1787 }
1788
1789 /*----- Return entry handle containing all values and the flag whether an insertion was performed. -----*/
1790 return { value_entry(slot), entry_inserted };
1791}
1792
1793template<bool IsGlobal, bool ValueInPlace>
1794std::pair<HashTable::entry_t, Boolx1> OpenAddressingHashTable<IsGlobal, ValueInPlace>::find(std::vector<SQL_t> key,
1795 hint_t bucket_hint)
1796{
1797 M_insist(bool(num_entries_), "must call `setup()` before");
1798
1799 Ptr<void> bucket =
1800 bucket_hint ? *bucket_hint : compute_bucket(clone(key)); // clone key since we need it again for comparison
1801
1802 /*----- Get reference count, i.e. occupied slots, of this bucket. -----*/
1803 const Var<PrimitiveExpr<ref_t>> refs(reference_count(bucket.clone()));
1804
1805 /*----- Probe slots, abort if end of bucket is reached or key already exists. -----*/
1806 Var<Ptr<void>> slot(bucket);
1807 Var<PrimitiveExpr<ref_t>> steps(0);
1808 WHILE (steps != refs) {
1809 Wasm_insist(reference_count(slot) != ref_t(0), "slot in bucket list must be occupied");
1810 BREAK(equal_key(slot, std::move(key))); // move key at last use
1811 steps += ref_t(1);
1812 Wasm_insist(steps <= *num_entries_, "probing strategy has to find unoccupied slot if there is one");
1813 slot = probing_strategy().advance_to_next_slot(slot, steps);
1814 Wasm_insist(begin() <= slot and slot < end(), "slot out-of-bounds");
1815 }
1816
1817 /*----- Key is found iff end of bucket is reached. -----*/
1818 Boolx1 key_found = steps != refs;
1819
1820 if constexpr (not ValueInPlace) {
1821 /*----- Set slot pointer to out-of-place values. -----*/
1822 slot = *(slot + layout_.ptr_offset_in_bytes_).template to<uint32_t*>();
1823 }
1824
1825 /*----- Return entry handle containing both keys and values and the flag whether key was found. -----*/
1826 return { value_entry(slot), key_found };
1827}
1828
1829template<bool IsGlobal, bool ValueInPlace>
1831{
1832 /*----- Iterate over all entries and call pipeline (with entry handle argument) on occupied ones. -----*/
1833 Var<Ptr<void>> it(begin());
1834 WHILE (it != end()) {
1835 Wasm_insist(begin() <= it and it < end(), "entry out-of-bounds");
1836 IF (reference_count(it) != ref_t(0)) { // occupied
1837 Pipeline(entry(it));
1838 };
1839 it += int32_t(entry_size_in_bytes_);
1840 }
1841}
1842
1843template<bool IsGlobal, bool ValueInPlace>
1846 bool predicated,
1847 hint_t bucket_hint) const
1848{
1849 M_insist(bool(num_entries_), "must call `setup()` before");
1850
1851 Ptr<void> bucket =
1852 bucket_hint ? *bucket_hint : compute_bucket(clone(key)); // clone key since we need it again for comparison
1853
1854 /*----- Get reference count, i.e. occupied slots, of this bucket. -----*/
1855 const Var<PrimitiveExpr<ref_t>> refs(reference_count(bucket.clone()));
1856
1857 /*----- Iterate over slots and call pipeline (with entry handle argument) on matches with the given key. -----*/
1858 Var<Ptr<void>> slot(bucket);
1859 Var<PrimitiveExpr<ref_t>> steps(0);
1860 WHILE (steps != refs) { // end of bucket not reached
1861 Wasm_insist(reference_count(slot) != ref_t(0), "slot in bucket list must be occupied");
1862 if (predicated) {
1863 CodeGenContext::Get().env().add_predicate(equal_key(slot, std::move(key)));
1864 Pipeline(entry(slot));
1865 } else {
1866 IF (equal_key(slot, std::move(key))) { // match found
1867 Pipeline(entry(slot));
1868 };
1869 }
1870 steps += ref_t(1);
1871 Wasm_insist(steps <= *num_entries_, "probing strategy has to find unoccupied slot if there is one");
1872 slot = probing_strategy().advance_to_next_slot(slot, steps);
1873 Wasm_insist(begin() <= slot and slot < end(), "slot out-of-bounds");
1874 }
1875}
1876
1877template<bool IsGlobal, bool ValueInPlace>
1879{
1880 if constexpr (ValueInPlace) {
1881 /*----- Allocate memory for a dummy slot. -----*/
1882 var_t<Ptr<void>> slot; // create global variable iff `IsGlobal` to be able to access it later for deallocation
1883 slot = Module::Allocator().allocate(entry_size_in_bytes_, entry_max_alignment_in_bytes_);
1884
1885 /*----- Store address and size of dummy slot to free them later. -----*/
1886 dummy_allocations_.emplace_back(slot, entry_size_in_bytes_);
1887
1888 /*----- Return *local* entry handle containing all values. -----*/
1889 return value_entry(M_CONSTEXPR_COND(IsGlobal, Var<Ptr<void>>(slot.val()).val(), slot.val()));
1890 } else {
1891 /*----- Allocate memory for out-of-place dummy values. -----*/
1892 var_t<Ptr<void>> ptr; // create global variable iff `IsGlobal` to be able to access it later for deallocation
1893 ptr = Module::Allocator().allocate(layout_.values_size_in_bytes_, layout_.values_max_alignment_in_bytes_);
1894
1895 /*----- Store address and size of dummy values to free them later. -----*/
1896 dummy_allocations_.emplace_back(ptr, layout_.values_size_in_bytes_);
1897
1898 /*----- Return *local* entry handle containing all values. -----*/
1899 return value_entry(M_CONSTEXPR_COND(IsGlobal, Var<Ptr<void>>(ptr.val()).val(), ptr.val()));
1900 }
1901}
1902
1903template<bool IsGlobal, bool ValueInPlace>
1905{
1906 Var<Boolx1> res(true);
1907
1908 const auto off_null_bitmap = M_CONSTEXPR_COND(ValueInPlace, layout_.null_bitmap_offset_in_bytes_,
1909 layout_.keys_null_bitmap_offset_in_bytes_);
1910 for (std::size_t i = 0; i < key_indices_.size(); ++i) {
1911 /* do not skip duplicated comparison of the same slot since probing might need this comparison */
1912
1913 auto &e = schema_.get()[key_indices_[i]];
1914 const auto idx = [&](){
1915 if constexpr (ValueInPlace) {
1916 return key_indices_[i];
1917 } else { // return number of indices in [0, key_indices_[i]) that are part of key
1918 std::size_t count = 0;
1919 for (index_t idx = 0; idx != key_indices_[i]; ++idx)
1920 count += contains(key_indices_, idx);
1921 return count;
1922 }
1923 }();
1924 const auto off = M_CONSTEXPR_COND(ValueInPlace, layout_.entry_offsets_in_bytes_[idx],
1925 layout_.key_offsets_in_bytes_[idx]);
1926 auto compare_equal = [&]<typename T>() {
1927 using type = typename T::type;
1928 M_insist(std::holds_alternative<T>(key[i]));
1929 if (e.nullable()) { // entry may be NULL
1930 const_reference_t<T> ref((slot.clone() + off).template to<type*>(), slot.clone() + off_null_bitmap, idx);
1931 res = res and ref == *std::get_if<T>(&key[i]);
1932 } else { // entry must not be NULL
1933 const_reference_t<T> ref((slot.clone() + off).template to<type*>());
1934 res = res and ref == *std::get_if<T>(&key[i]);
1935 }
1936 };
1938 [&](const Boolean&) { compare_equal.template operator()<_Boolx1>(); },
1939 [&](const Numeric &n) {
1940 switch (n.kind) {
1941 case Numeric::N_Int:
1942 case Numeric::N_Decimal:
1943 switch (n.size()) {
1944 default: M_unreachable("invalid size");
1945 case 8: compare_equal.template operator()<_I8x1 >(); break;
1946 case 16: compare_equal.template operator()<_I16x1>(); break;
1947 case 32: compare_equal.template operator()<_I32x1>(); break;
1948 case 64: compare_equal.template operator()<_I64x1>(); break;
1949 }
1950 break;
1951 case Numeric::N_Float:
1952 if (n.size() <= 32)
1953 compare_equal.template operator()<_Floatx1>();
1954 else
1955 compare_equal.template operator()<_Doublex1>();
1956 }
1957 },
1958 [&](const CharacterSequence &cs) {
1959 M_insist(std::holds_alternative<NChar>(key[i]));
1960 NChar val((slot.clone() + off).template to<char*>(), e.nullable(), &cs);
1961 if (e.nullable()) { // entry may be NULL
1962 const_reference_t<NChar> ref(val, slot.clone() + off_null_bitmap, idx);
1963 res = res and ref == *std::get_if<NChar>(&key[i]);
1964 } else { // entry must not be NULL
1965 const_reference_t<NChar> ref(val);
1966 res = res and ref == *std::get_if<NChar>(&key[i]);
1967 }
1968 },
1969 [&](const Date&) { compare_equal.template operator()<_I32x1>(); },
1970 [&](const DateTime&) { compare_equal.template operator()<_I64x1>(); },
1971 [](auto&&) { M_unreachable("invalid type"); },
1972 }, *e.type);
1973 }
1974
1975 slot.discard(); // since it was always cloned
1976
1977 return res;
1978}
1979
1980template<bool IsGlobal, bool ValueInPlace>
1982{
1983 const auto off_null_bitmap = M_CONSTEXPR_COND(ValueInPlace, layout_.null_bitmap_offset_in_bytes_,
1984 layout_.keys_null_bitmap_offset_in_bytes_);
1985 for (std::size_t i = 0; i < key_indices_.size(); ++i) {
1986 /* NOTE: `std::find` results in quadratic complexity but we expect rather short keys anyway */
1987 if (std::find(key_indices_.begin(), key_indices_.begin() + i, key_indices_[i]) != key_indices_.begin() + i) {
1988 discard(key[i]);
1989 continue; // skip duplicated writing of the same slot
1990 }
1991
1992 auto &e = schema_.get()[key_indices_[i]];
1993 const auto idx = [&](){
1994 if constexpr (ValueInPlace) {
1995 return key_indices_[i];
1996 } else { // return number of indices in [0, key_indices_[i]) that are part of key
1997 std::size_t count = 0;
1998 for (index_t idx = 0; idx != key_indices_[i]; ++idx)
1999 count += contains(key_indices_, idx);
2000 return count;
2001 }
2002 }();
2003 const auto off = M_CONSTEXPR_COND(ValueInPlace, layout_.entry_offsets_in_bytes_[idx],
2004 layout_.key_offsets_in_bytes_[idx]);
2005 auto insert = [&]<typename T>() {
2006 using type = typename T::type;
2007 M_insist(std::holds_alternative<T>(key[i]));
2008 if (e.nullable()) { // entry may be NULL
2009 reference_t<T> ref((slot.clone() + off).template to<type*>(), slot.clone() + off_null_bitmap, idx);
2010 ref = *std::get_if<T>(&key[i]);
2011 } else { // entry must not be NULL
2012 reference_t<T> ref((slot.clone() + off).template to<type*>());
2013 ref = *std::get_if<T>(&key[i]);
2014 }
2015 };
2017 [&](const Boolean&) { insert.template operator()<_Boolx1>(); },
2018 [&](const Numeric &n) {
2019 switch (n.kind) {
2020 case Numeric::N_Int:
2021 case Numeric::N_Decimal:
2022 switch (n.size()) {
2023 default: M_unreachable("invalid size");
2024 case 8: insert.template operator()<_I8x1 >(); break;
2025 case 16: insert.template operator()<_I16x1>(); break;
2026 case 32: insert.template operator()<_I32x1>(); break;
2027 case 64: insert.template operator()<_I64x1>(); break;
2028 }
2029 break;
2030 case Numeric::N_Float:
2031 if (n.size() <= 32)
2032 insert.template operator()<_Floatx1>();
2033 else
2034 insert.template operator()<_Doublex1>();
2035 }
2036 },
2037 [&](const CharacterSequence &cs) {
2038 M_insist(std::holds_alternative<NChar>(key[i]));
2039 NChar val((slot.clone() + off).template to<char*>(), e.nullable(), &cs);
2040 if (e.nullable()) { // entry may be NULL
2041 reference_t<NChar> ref(val, slot.clone() + off_null_bitmap, idx);
2042 ref = *std::get_if<NChar>(&key[i]);
2043 } else { // entry must not be NULL
2044 reference_t<NChar> ref(val);
2045 ref = *std::get_if<NChar>(&key[i]);
2046 }
2047 },
2048 [&](const Date&) { insert.template operator()<_I32x1>(); },
2049 [&](const DateTime&) { insert.template operator()<_I64x1>(); },
2050 [](auto&&) { M_unreachable("invalid type"); },
2051 }, *e.type);
2052 }
2053
2054 slot.discard(); // since it was always cloned
2055}
2056
2057template<bool IsGlobal, bool ValueInPlace>
2059{
2060 entry_t value_entry;
2061
2062 const auto off_null_bitmap = M_CONSTEXPR_COND(ValueInPlace, layout_.null_bitmap_offset_in_bytes_,
2063 layout_.values_null_bitmap_offset_in_bytes_);
2064 for (std::size_t i = 0; i < value_indices_.size(); ++i) {
2065 /* there are no duplicates in the value indexes */
2066
2067 auto &e = schema_.get()[value_indices_[i]];
2068 const auto idx = [&](){
2069 if constexpr (ValueInPlace) {
2070 return value_indices_[i];
2071 } else { // return number of indices in [0, value_indices_[i]) that are part of value
2072 std::size_t count = 0;
2073 for (index_t idx = 0; idx != value_indices_[i]; ++idx)
2074 count += contains(value_indices_, idx);
2075 return count;
2076 }
2077 }();
2078 const auto off = M_CONSTEXPR_COND(ValueInPlace, layout_.entry_offsets_in_bytes_[idx],
2079 layout_.value_offsets_in_bytes_[idx]);
2080 auto add = [&]<typename T>() {
2081 using type = typename T::type;
2082 if (e.nullable()) { // entry may be NULL
2083 reference_t<T> ref((ptr.clone() + off).template to<type*>(), ptr.clone() + off_null_bitmap, idx);
2084 value_entry.add(e.id, std::move(ref));
2085 } else { // entry must not be NULL
2086 reference_t<T> ref((ptr.clone() + off).template to<type*>());
2087 value_entry.add(e.id, std::move(ref));
2088 }
2089 };
2091 [&](const Boolean&) { add.template operator()<_Boolx1>(); },
2092 [&](const Numeric &n) {
2093 switch (n.kind) {
2094 case Numeric::N_Int:
2095 case Numeric::N_Decimal:
2096 switch (n.size()) {
2097 default: M_unreachable("invalid size");
2098 case 8: add.template operator()<_I8x1 >(); break;
2099 case 16: add.template operator()<_I16x1>(); break;
2100 case 32: add.template operator()<_I32x1>(); break;
2101 case 64: add.template operator()<_I64x1>(); break;
2102 }
2103 break;
2104 case Numeric::N_Float:
2105 if (n.size() <= 32)
2106 add.template operator()<_Floatx1>();
2107 else
2108 add.template operator()<_Doublex1>();
2109 }
2110 },
2111 [&](const CharacterSequence &cs) {
2112 NChar val((ptr.clone() + off).template to<char*>(), e.nullable(), &cs);
2113 if (e.nullable()) { // entry may be NULL
2114 reference_t<NChar> ref(val, ptr.clone() + off_null_bitmap, idx);
2115 value_entry.add(e.id, std::move(ref));
2116 } else { // entry must not be NULL
2117 reference_t<NChar> ref(val);
2118 value_entry.add(e.id, std::move(ref));
2119 }
2120 },
2121 [&](const Date&) { add.template operator()<_I32x1>(); },
2122 [&](const DateTime&) { add.template operator()<_I64x1>(); },
2123 [](auto&&) { M_unreachable("invalid type"); },
2124 }, *e.type);
2125 }
2126
2127 ptr.discard(); // since it was always cloned
2128
2129 return value_entry;
2130}
2131
2132template<bool IsGlobal, bool ValueInPlace>
2134{
2135 const_entry_t entry;
2136
2137 std::unique_ptr<Ptr<void>> value;
2138 if constexpr (not ValueInPlace) {
2139 const Var<Ptr<void>> value_(*(slot.clone() + layout_.ptr_offset_in_bytes_).template to<uint32_t*>());
2140 value = std::make_unique<Ptr<void>>(value_);
2141 }
2142
2143 auto ptr = &slot;
2144 auto off_null_bitmap = M_CONSTEXPR_COND(ValueInPlace, layout_.null_bitmap_offset_in_bytes_,
2145 layout_.keys_null_bitmap_offset_in_bytes_);
2146 for (std::size_t i = 0; i < key_indices_.size() + value_indices_.size(); ++i) {
2147 if constexpr (not ValueInPlace) {
2148 if (i == key_indices_.size()) {
2149 /* If end of key is reached, switch variables to out-of-place value entries. */
2150 M_insist(bool(value));
2151 ptr = &*value;
2152 off_null_bitmap = layout_.values_null_bitmap_offset_in_bytes_;
2153 }
2154 }
2155
2156 const bool is_key = i < key_indices_.size();
2157 /* NOTE: `std::find` results in quadratic complexity but we expect rather short keys anyway */
2158 if (is_key and std::find(key_indices_.begin(), key_indices_.begin() + i, key_indices_[i]) != key_indices_.begin() + i)
2159 continue; // skip duplicated key to the same slot
2160
2161 const auto schema_idx = is_key ? key_indices_[i] : value_indices_[i - key_indices_.size()];
2162 auto &e = schema_.get()[schema_idx];
2163 const auto idx = [&](){
2164 if constexpr (ValueInPlace) {
2165 return schema_idx;
2166 } else { // return number of indices in [0, schema_idx) that are part of key or rather value
2167 std::size_t count = 0;
2168 for (index_t idx = 0; idx != schema_idx; ++idx)
2169 count += is_key ? contains(key_indices_, idx) : contains(value_indices_, idx);
2170 return count;
2171 }
2172 }();
2173 const auto off =
2174 M_CONSTEXPR_COND(ValueInPlace,
2175 layout_.entry_offsets_in_bytes_[idx],
2176 is_key ? layout_.key_offsets_in_bytes_[idx] : layout_.value_offsets_in_bytes_[idx]);
2177 auto add = [&]<typename T>() {
2178 using type = typename T::type;
2179 if (e.nullable()) { // entry may be NULL
2180 const_reference_t<T> ref((ptr->clone() + off).template to<type*>(), ptr->clone() + off_null_bitmap, idx);
2181 entry.add(e.id, std::move(ref));
2182 } else { // entry must not be NULL
2183 const_reference_t<T> ref((ptr->clone() + off).template to<type*>());
2184 entry.add(e.id, std::move(ref));
2185 }
2186 };
2188 [&](const Boolean&) { add.template operator()<_Boolx1>(); },
2189 [&](const Numeric &n) {
2190 switch (n.kind) {
2191 case Numeric::N_Int:
2192 case Numeric::N_Decimal:
2193 switch (n.size()) {
2194 default: M_unreachable("invalid size");
2195 case 8: add.template operator()<_I8x1 >(); break;
2196 case 16: add.template operator()<_I16x1>(); break;
2197 case 32: add.template operator()<_I32x1>(); break;
2198 case 64: add.template operator()<_I64x1>(); break;
2199 }
2200 break;
2201 case Numeric::N_Float:
2202 if (n.size() <= 32)
2203 add.template operator()<_Floatx1>();
2204 else
2205 add.template operator()<_Doublex1>();
2206 }
2207 },
2208 [&](const CharacterSequence &cs) {
2209 NChar val((ptr->clone() + off).template to<char*>(), e.nullable(), &cs);
2210 if (e.nullable()) { // entry may be NULL
2211 const_reference_t<NChar> ref(val, ptr->clone() + off_null_bitmap, idx);
2212 entry.add(e.id, std::move(ref));
2213 } else { // entry must not be NULL
2214 const_reference_t<NChar> ref(val);
2215 entry.add(e.id, std::move(ref));
2216 }
2217 },
2218 [&](const Date&) { add.template operator()<_I32x1>(); },
2219 [&](const DateTime&) { add.template operator()<_I64x1>(); },
2220 [](auto&&) { M_unreachable("invalid type"); },
2221 }, *e.type);
2222 }
2223
2224 slot.discard(); // since it was always cloned
2225 if (value) value->discard(); // since it was always cloned
2226
2227 return entry;
2228}
2229
2230template<bool IsGlobal, bool ValueInPlace>
2232{
2233 if (options::insist_no_rehashing)
2234 Throw(exception::unreachable, "rehashing must not occur");
2235
2236 auto emit_rehash = [this](){
2237 auto S = CodeGenContext::Get().scoped_environment(); // fresh environment to remove predication while rehashing
2238
2239 M_insist(bool(address_), "must call `setup()` before");
2240 M_insist(bool(mask_), "must call `setup()` before");
2241 M_insist(bool(num_entries_), "must call `setup()` before");
2242
2243 /*----- Store old begin and end (since they will be overwritten). -----*/
2244 const Var<Ptr<void>> begin_old(begin());
2245 const Var<Ptr<void>> end_old(end());
2246
2247 /*----- Double capacity. -----*/
2248 *mask_ = (*mask_ << 1U) + 1U;
2249
2250 /*----- Allocate memory for new hash table with updated capacity. -----*/
2251 *address_ = Module::Allocator().allocate(size_in_bytes(), entry_max_alignment_in_bytes_);
2252
2253 /*----- Clear newly created hash table. -----*/
2254 clear();
2255
2256#ifndef NDEBUG
2257 /*----- Store old number of entries. -----*/
2258 const Var<U32x1> num_entries_old(*num_entries_);
2259#endif
2260
2261 /*----- Reset number of entries (since they will be incremented at each insertion into the new hash table). --*/
2262 *num_entries_ = 0U;
2263
2264 /*----- Insert each element from old hash table into new one. -----*/
2265 Var<Ptr<void>> it(begin_old.val());
2266 WHILE (it != end_old) {
2267 Wasm_insist(begin_old <= it and it < end_old, "entry out-of-bounds");
2268 IF (reference_count(it) != ref_t(0)) { // entry in old hash table is occupied
2269 auto e_old = entry(it);
2270
2271 /*----- Access key from old entry. -----*/
2272 std::vector<SQL_t> key;
2273 for (auto k : key_indices_) {
2274 std::visit(overloaded {
2275 [&](auto &&r) -> void { key.emplace_back(r); },
2276 [](std::monostate) -> void { M_unreachable("invalid reference"); },
2277 }, e_old.get(schema_.get()[k].id));
2278 }
2279
2280 /*----- Insert key into new hash table. No rehashing needed since the new hash table is large enough. */
2281 auto slot = emplace_without_rehashing(std::move(key));
2282
2283 if constexpr (ValueInPlace) {
2284 /*----- Get entry handle containing all values of new entry. -----*/
2285 auto e_new = value_entry(slot);
2286
2287 /*----- Insert values from old entry into new one. -----*/
2288 for (auto v : value_indices_) {
2289 auto id = schema_.get()[v].id;
2290 std::visit(overloaded {
2291 [&]<sql_type T>(reference_t<T> &&r) -> void { r = e_old.template extract<T>(id); },
2292 [](std::monostate) -> void { M_unreachable("invalid reference"); },
2293 }, e_new.extract(id));
2294 }
2295 M_insist(e_new.empty());
2296 } else {
2297 /*----- Set pointer to out-of-place values of new entry to the one of old entry. -----*/
2298 *(slot + layout_.ptr_offset_in_bytes_).template to<uint32_t*>() =
2299 *(it + layout_.ptr_offset_in_bytes_).template to<uint32_t*>();
2300 }
2301 };
2302
2303 /*----- Advance to next entry in old hash table. -----*/
2304 it += int32_t(entry_size_in_bytes_);
2305 }
2306
2307#ifndef NDEBUG
2308 Wasm_insist(*num_entries_ == num_entries_old, "number of entries of old and new hash table do not match");
2309#endif
2310
2311 /*----- Free old hash table. -----*/
2312 U32x1 size = (end_old - begin_old).make_unsigned();
2313 Module::Allocator().deallocate(begin_old, size);
2314 };
2315
2316 if constexpr (IsGlobal) {
2317 if (not rehash_) {
2318 /*----- Backup former local variables to be able to use new ones for rehashing function. -----*/
2319 auto old_address = std::exchange(address_, std::nullopt);
2320 auto old_mask = std::exchange(mask_, std::nullopt);
2321 auto old_num_entries = std::exchange(num_entries_, std::nullopt);
2322 auto old_high_watermark_absolute = std::exchange(high_watermark_absolute_, std::nullopt);
2323
2324 /*----- Create function for rehashing. -----*/
2325 FUNCTION(rehash, void(void))
2326 {
2327 /*----- Perform setup for local variables. -----*/
2328 address_.emplace(storage_.address_);
2329 mask_.emplace(storage_.mask_);
2330 num_entries_.emplace(storage_.num_entries_);
2331 high_watermark_absolute_.emplace(storage_.high_watermark_absolute_);
2332
2333 emit_rehash();
2334
2335 /*----- Perform teardown for local variables. -----*/
2336 storage_.address_ = *address_;
2337 storage_.mask_ = *mask_;
2338 storage_.num_entries_ = *num_entries_;
2339 storage_.high_watermark_absolute_ = *high_watermark_absolute_;
2340 address_.reset();
2341 mask_.reset();
2342 num_entries_.reset();
2343 high_watermark_absolute_.reset();
2344 }
2345 rehash_ = std::move(rehash);
2346
2347 /*----- Restore local variables. -----*/
2348 std::exchange(address_, std::move(old_address));
2349 std::exchange(mask_, std::move(old_mask));
2350 std::exchange(num_entries_, std::move(old_num_entries));
2351 std::exchange(high_watermark_absolute_, std::move(old_high_watermark_absolute));
2352 }
2353
2354 /*----- Store local variables in global backups. -----*/
2355 storage_.address_ = *address_;
2356 storage_.mask_ = *mask_;
2357 storage_.num_entries_ = *num_entries_;
2358 storage_.high_watermark_absolute_ = *high_watermark_absolute_;
2359
2360 /*----- Call rehashing function. ------*/
2361 M_insist(bool(rehash_));
2362 (*rehash_)();
2363
2364 /*----- Restore local variables from global backups. -----*/
2365 *address_ = storage_.address_;
2366 *mask_ = storage_.mask_;
2367 *num_entries_ = storage_.num_entries_;
2368 *high_watermark_absolute_ = storage_.high_watermark_absolute_;
2369 } else {
2370 /*----- Emit rehashing code. ------*/
2371 emit_rehash();
2372 }
2373}
2374
2375// explicit instantiations to prevent linker errors
2380
2381
2382/*----- probing strategies for open addressing hash tables -----------------------------------------------------------*/
2383
2385{
2386 Wasm_insist(skips.clone() < ht_.capacity());
2387 const Var<Ptr<void>> slot(bucket + (skips * ht_.entry_size_in_bytes()).make_signed());
2388 Wasm_insist(slot < ht_.end() + ht_.size_in_bytes().make_signed());
2389 return Select(slot < ht_.end(), slot, slot - ht_.size_in_bytes().make_signed());
2390}
2391
2393{
2394 current_step.discard(); // not needed for linear probing
2395
2396 const Var<Ptr<void>> next(slot + ht_.entry_size_in_bytes());
2397 Wasm_insist(next <= ht_.end());
2398 return Select(next < ht_.end(), next, ht_.begin());
2399}
2400
2402{
2403 auto skips_cloned = skips.clone();
2404 U32x1 slots_skipped = (skips_cloned * (skips + 1U)) >> 1U; // compute gaussian sum
2405 U32x1 slots_skipped_mod = slots_skipped bitand ht_.mask(); // modulo capacity
2406 const Var<Ptr<void>> slot(bucket + (slots_skipped_mod * ht_.entry_size_in_bytes()).make_signed());
2407 Wasm_insist(slot < ht_.end() + ht_.size_in_bytes().make_signed());
2408 return Select(slot < ht_.end(), slot, slot - ht_.size_in_bytes().make_signed());
2409}
2410
2412{
2413 const Var<Ptr<void>> next(slot + (current_step * ht_.entry_size_in_bytes()).make_signed());
2414 Wasm_insist(next < ht_.end() + ht_.size_in_bytes().make_signed());
2415 return Select(next < ht_.end(), next, next - ht_.size_in_bytes().make_signed());
2416}
__attribute__((constructor(202))) static void register_interpreter()
void insert(std::vector< const ast::Expr * > &exprs, const std::vector< const ast::Designator * > &insertions)
Like std::vector::insert() but adds only those elements of insertions which are not already contained...
Definition: QueryGraph.cpp:610
U64x1 reinterpret_to_U64(m::wasm::PrimitiveExpr< T > value)
Definition: WasmAlgo.cpp:259
#define Wasm_insist(...)
Definition: WasmDSL.hpp:373
#define Throw(...)
Definition: WasmMacro.hpp:48
#define ELSE
Definition: WasmMacro.hpp:24
#define LOOP(...)
Definition: WasmMacro.hpp:30
#define WHILE(...)
Definition: WasmMacro.hpp:43
#define BLOCK(...)
Definition: WasmMacro.hpp:15
#define PARAMETER(IDX)
Definition: WasmMacro.hpp:20
#define IF(COND)
Definition: WasmMacro.hpp:23
#define FUNCTION(NAME, TYPE)
Definition: WasmMacro.hpp:17
#define DO_WHILE(...)
Definition: WasmMacro.hpp:37
void add(const char *group_name, const char *short_name, const char *long_name, const char *description, Callback &&callback)
Adds a new group option to the ArgParser.
Definition: ArgParser.hpp:84
Check whether.
Definition: concepts.hpp:31
#define M_unreachable(MSG)
Definition: macro.hpp:146
#define M_CONSTEXPR_COND(COND, IF_TRUE, IF_FALSE)
Definition: macro.hpp:54
#define M_insist(...)
Definition: macro.hpp:129
auto indices
Definition: WasmDSL.hpp:2429
template void quicksort< true >(GlobalBuffer &, const std::vector< SortingOperator::order_type > &)
U64x1 str_hash(NChar str)
Hashes the string str.
Definition: WasmAlgo.cpp:313
template void quicksort< false >(GlobalBuffer &, const std::vector< SortingOperator::order_type > &)
typename detail::var_helper< T >::type Var
Local variable.
Definition: WasmDSL.hpp:5779
auto make_unsigned()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_unsigned_t<T>, L>.
Definition: WasmDSL.hpp:3658
auto make_signed()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_signed_t<T>, L>.
Definition: WasmDSL.hpp:3650
void GOTO(const Block &block)
Jumps to the end of block.
Definition: WasmDSL.hpp:6199
U64x1 murmur3_bit_mix(U64x1 bits)
Mixes the bits of bits using the Murmur3 algorithm.
Definition: WasmAlgo.cpp:280
void quicksort(Buffer< IsGlobal > &buffer, const std::vector< SortingOperator::order_type > &order)
Sorts the buffer buffer using the quicksort algorithm and a branchless binary partition algorithm.
Definition: WasmAlgo.cpp:64
and
Constructs a new PrimitiveExpr from a constant value.
Definition: WasmDSL.hpp:1519
Bool< L > value
Definition: WasmUtil.hpp:1317
Bool< L > is_null(SQL_t &variant)
Definition: WasmUtil.hpp:461
auto Select(C &&_cond, T &&_tru, U &&_fals)
Definition: WasmDSL.hpp:6215
Bool< L > uint8_t n
Definition: WasmUtil.hpp:1318
void discard()
Discards this.
Definition: WasmDSL.hpp:1588
PrimitiveExpr< uint64_t, L > L L L L U
Definition: WasmDSL.hpp:2352
for(std::size_t idx=1;idx< num_vectors;++idx) res.emplace((vectors_[idx].bitmask()<< uint32_t(idx *vector_type return * res
Definition: WasmDSL.hpp:3696
std::size_t bool
Definition: WasmDSL.hpp:528
void CONTINUE(std::size_t level=1)
Definition: WasmDSL.hpp:6187
U64x1 fnv_1a(Ptr< U8x1 > bytes, U32x1 num_bytes)
Hashes num_bytes bytes of bytes using the FNV-1a algorithm.
Definition: WasmAlgo.cpp:297
std::pair<::wasm::Expression *, std::list< std::shared_ptr< Bit > > > move()
Moves the underlying Binaryen ::wasm::Expression and the referenced bits out of this.
Definition: WasmDSL.hpp:1567
U64x1 murmur3_64a_hash(std::vector< std::pair< const Type *, SQL_t > > values)
Hashes the elements of values where the first element is the type of the value to hash and the second...
Definition: WasmAlgo.cpp:338
PrimitiveExpr< uint64_t, L > hash() and(L
PrimitiveExpr clone() const
Creates and returns a deep copy of this.
Definition: WasmDSL.hpp:1577
void BREAK(std::size_t level=1)
Definition: WasmDSL.hpp:6176
static constexpr std::size_t num_simd_lanes
‍the number of SIMD lanes of the represented expression, i.e. 1 for scalar and at least 2 for vectori...
Definition: WasmDSL.hpp:1466
‍mutable namespace
Definition: Backend.hpp:10
bool M_EXPORT contains(const H &haystack, const N &needle)
Checks whether haystack contains needle.
Definition: fn.hpp:383
void swap(PlanTableBase< Actual > &first, PlanTableBase< Actual > &second)
Definition: PlanTable.hpp:394
T(x)
and
Definition: enum_ops.hpp:12
auto visit(Callable &&callable, Base &obj, m::tag< Callable > &&=m::tag< Callable >())
Generic implementation to visit a class hierarchy, with similar syntax as std::visit.
Definition: Visitor.hpp:138
‍command-line options for the HeuristicSearchPlanEnumerator
Definition: V8Engine.cpp:44
STL namespace.
The boolean type.
Definition: Type.hpp:230
The catalog contains all Databases and keeps track of all meta information of the database system.
Definition: Catalog.hpp:215
static Catalog & Get()
Return a reference to the single Catalog instance.
m::ArgParser & arg_parser()
Definition: Catalog.hpp:253
The type of character strings, both fixed length and varying length.
Definition: Type.hpp:290
The date type.
Definition: Type.hpp:364
The date type.
Definition: Type.hpp:335
The numeric type represents integer and floating-point types of different precision and scale.
Definition: Type.hpp:393
Implements push-based evaluation of a pipeline in the plan.
A Schema represents a sequence of identifiers, optionally with a prefix, and their associated types.
Definition: Schema.hpp:39
static Pooled< Bitmap > Get_Bitmap(category_t category, std::size_t length)
Returns a Bitmap type of the given category and length.
Definition: Type.cpp:70
static Pooled< Numeric > Get_Integer(category_t category, unsigned num_bytes)
Returns a Numeric type for integrals of given category and num_bytes bytes.
Definition: Type.cpp:94
Buffers tuples by materializing them into memory.
Definition: WasmUtil.hpp:1070
buffer_load_proxy_t< IsGlobal > create_load_proxy(param_t tuple_value_schema=param_t(), param_t tuple_addr_schema=param_t()) const
Creates and returns a proxy object to load value tuples of schema tuple_value_schema (default: entire...
Definition: WasmUtil.cpp:2535
const Schema & schema() const
Returns the schema of the buffer.
Definition: WasmUtil.hpp:1106
void setup_base_address()
Performs the setup of the local base address of this buffer by reading it from the global backup.
Definition: WasmUtil.hpp:1147
void teardown_base_address()
Performs the teardown of the local base address of this buffer by destroying it but without storing i...
Definition: WasmUtil.hpp:1157
buffer_swap_proxy_t< IsGlobal > create_swap_proxy(param_t tuple_schema=param_t()) const
Creates and returns a proxy object to swap tuples of schema tuple_schema (default: entire tuples) in ...
Definition: WasmUtil.cpp:2570
U32x1 size() const
Returns the current size of the buffer.
Definition: WasmUtil.hpp:1119
void rehash()
Performs rehashing, i.e.
Definition: WasmAlgo.cpp:1205
HashTable::offset_t null_bitmap_offset_in_bytes_
‍offset of NULL bitmap; only specified if at least one entry is nullable
Definition: WasmAlgo.hpp:636
std::optional< Var< U32x1 > > high_watermark_absolute_
‍maximum number of entries before growing the hash table is required
Definition: WasmAlgo.hpp:645
void setup() override
Performs the setup of all local variables of the hash table (by reading them from the global backups ...
Definition: WasmAlgo.cpp:569
void clear() override
Clears the hash table.
Definition: WasmAlgo.cpp:674
std::pair< entry_t, Boolx1 > find(std::vector< SQL_t > key, hint_t bucket_hint) override
Tries to find an entry with key key in the hash table.
Definition: WasmAlgo.cpp:873
void for_each_in_equal_range(std::vector< SQL_t > key, callback_t Pipeline, bool predicated, hint_t bucket_hint) const override
Calls Pipeline for each entry with key key in the hash table, where the key comparison is performed p...
Definition: WasmAlgo.cpp:909
void for_each(callback_t Pipeline) const override
Calls Pipeline for each entry contained in the hash table.
Definition: WasmAlgo.cpp:893
entry_t value_entry(Ptr< void > entry) const
Returns a handle for the entry at address entry which may be used to write the values of the correspo...
Definition: WasmAlgo.cpp:1075
HashTable::offset_t ptr_offset_in_bytes_
offset of pointer to next entry in linked collision list
Definition: WasmAlgo.hpp:637
HashTable::size_t entry_size_in_bytes_
entry size in bytes
Definition: WasmAlgo.hpp:632
Boolx1 equal_key(Ptr< void > entry, std::vector< SQL_t > key) const
Compares the key of the entry at address entry with key and returns true iff they are equal.
Definition: WasmAlgo.cpp:945
std::pair< entry_t, Boolx1 > try_emplace(std::vector< SQL_t > key) override
If no entry with key key already exists, inserts one into the hash table, i.e.
Definition: WasmAlgo.cpp:796
entry_t emplace_without_rehashing(std::vector< SQL_t > key)
Inserts an entry into the hash table with key key regardless whether it already exists,...
Definition: WasmAlgo.cpp:754
void teardown() override
Performs the teardown of all local variables of the hash table (by storing them into the global backu...
Definition: WasmAlgo.cpp:615
HashTable::size_t entry_max_alignment_in_bytes_
alignment requirement in bytes of a single entry
Definition: WasmAlgo.hpp:633
std::vector< HashTable::offset_t > entry_offsets_in_bytes_
entry offsets, i.e.
Definition: WasmAlgo.hpp:634
void insert_key(Ptr< void > entry, std::vector< SQL_t > key)
Inserts the key key into the entry at address entry.
Definition: WasmAlgo.cpp:1010
chained_hash_table_storage< IsGlobal > storage_
‍if IsGlobal, contains backups for address, capacity, number of entries, and absolute high watermark
Definition: WasmAlgo.hpp:647
Ptr< void > compute_bucket(std::vector< SQL_t > key) const override
Computes the bucket for key key.
Definition: WasmAlgo.cpp:717
entry_t dummy_entry() override
Returns a handle to a newly created dummy entry which may be used to write the values for this entry.
Definition: WasmAlgo.cpp:931
const_entry_t entry(Ptr< void > entry) const
Returns a handle for the entry at address entry which may be used to read both the keys and the value...
Definition: WasmAlgo.cpp:1138
std::optional< Var< U32x1 > > mask_
‍mask of hash table, i.e. number of buckets / collision lists minus 1; always a power of 2 minus 1
Definition: WasmAlgo.hpp:641
Ptr< void > hash_to_bucket(std::vector< SQL_t > key) const
Returns the bucket address for the key key by hashing it.
Definition: WasmAlgo.cpp:693
std::conditional_t< IsGlobal, Global< T >, Var< T > > var_t
‍variable type dependent on whether the hash table should be globally usable
Definition: WasmAlgo.hpp:630
ChainedHashTable(const Schema &schema, std::vector< HashTable::index_t > key_indices, uint32_t initial_capacity)
Creates a chained hash table with schema schema, keys at key_indices, and an initial capacity for ini...
Definition: WasmAlgo.cpp:476
entry_t emplace(std::vector< SQL_t > key) override
Inserts an entry into the hash table with key key regardless whether it already exists,...
Definition: WasmAlgo.cpp:739
Environment & env()
Returns the current Environment.
Definition: WasmUtil.hpp:905
static CodeGenContext & Get()
Definition: WasmUtil.hpp:889
Scope scoped_environment()
Creates a new, scoped Environment.
Definition: WasmUtil.hpp:897
Binds Schema::Identifiers to Expr<T>s.
Definition: WasmUtil.hpp:563
void add_predicate(SQL_boolean_t &&pred)
‍Adds the predicate pred to the predication predicate.
Definition: WasmUtil.hpp:759
Helper struct as proxy to access a hash table entry.
Definition: WasmAlgo.hpp:369
value_t get(const Schema::Identifier &id) const
‍Returns the copied entry for identifier id.
Definition: WasmAlgo.hpp:462
void add(Schema::Identifier id, the_reference< T, IsConst > &&ref)
‍Adds a mapping from identifier id to reference ref.
Definition: WasmAlgo.hpp:438
Helper struct as proxy to access a single value (inclusive NULL bit) of a hash table entry.
Definition: WasmAlgo.hpp:66
Hash table to hash key-value pairs in memory.
Definition: WasmAlgo.hpp:56
std::size_t index_t
Definition: WasmAlgo.hpp:57
std::vector< index_t > key_indices_
keys of hash table
Definition: WasmAlgo.hpp:505
std::vector< index_t > value_indices_
values of hash table
Definition: WasmAlgo.hpp:506
std::function< void(const_entry_t)> callback_t
Definition: WasmAlgo.hpp:486
std::optional< Ptr< void > > hint_t
Definition: WasmAlgo.hpp:487
std::reference_wrapper< const Schema > schema_
schema of hash table
Definition: WasmAlgo.hpp:504
std::pair< size_t, size_t > set_byte_offsets(std::vector< offset_t > &offsets_in_bytes, const std::vector< const Type * > &types, offset_t initial_offset_in_bytes=0, offset_t initial_max_alignment_in_bytes=1)
Sets the byte offsets of an entry containing values of types types in offsets_in_bytes with the start...
Definition: WasmAlgo.cpp:438
Ptr< void > skip_slots(Ptr< void > bucket, U32x1 skips) const override
Returns the address of the skips -th (starting with index 0) slot in the bucket starting at bucket.
Definition: WasmAlgo.cpp:2384
Ptr< void > advance_to_next_slot(Ptr< void > slot, U32x1 current_step) const override
Returns the address of the current_step -th slot (starting with index 0) of a bucket which follows th...
Definition: WasmAlgo.cpp:2392
friend struct Allocator
Definition: WasmDSL.hpp:652
std::size_t length() const
Definition: WasmUtil.hpp:81
NChar clone() const
Definition: WasmUtil.hpp:53
Ptr< Charx1 > val()
Definition: WasmUtil.hpp:55
Boolx1 is_null()
Definition: WasmUtil.hpp:63
const OpenAddressingHashTableBase & ht_
open addressing hash table which uses this probing strategy
Definition: WasmAlgo.hpp:777
HashTable::offset_t refs_offset_in_bytes_
offset in bytes for reference counter
Definition: WasmAlgo.hpp:793
HashTable::size_t entry_size_in_bytes() const
Returns the size in bytes of a single entry in the hash table.
Definition: WasmAlgo.hpp:816
virtual Ptr< void > begin() const =0
Returns the address of the first entry.
HashTable::size_t entry_size_in_bytes_
entry size in bytes
Definition: WasmAlgo.hpp:794
Reference< ref_t > reference_count(Ptr< void > entry) const
Returns a Reference to the reference counter for the entry at address entry.
Definition: WasmAlgo.hpp:826
U32x1 size_in_bytes() const
Returns the overall size in bytes of the hash table.
Definition: WasmAlgo.hpp:814
virtual U32x1 mask() const =0
Returns the mask of the hash table, i.e.
void clear() override
Clears the hash table.
Definition: WasmAlgo.cpp:1322
virtual Ptr< void > end() const =0
Returns the address of the past-the-end entry.
Ptr< void > hash_to_bucket(std::vector< SQL_t > key) const
Returns the bucket address for the key key by hashing it.
Definition: WasmAlgo.cpp:1332
uint32_t ref_t
4 bytes for reference counting
Definition: WasmAlgo.hpp:771
HashTable::size_t entry_max_alignment_in_bytes_
alignment requirement in bytes of a single entry
Definition: WasmAlgo.hpp:795
U32x1 capacity() const
Returns the capacity of the hash table.
Definition: WasmAlgo.hpp:812
Boolx1 equal_key(Ptr< void > slot, std::vector< SQL_t > key) const
Compares the key of the slot at address slot with key and returns true iff they are equal.
Definition: WasmAlgo.cpp:1904
std::optional< Var< U32x1 > > high_watermark_absolute_
maximum number of entries before growing the hash table is required
Definition: WasmAlgo.hpp:910
const_entry_t entry(Ptr< void > slot) const
Returns a handle for the slot at address slot which may be used to read both the keys and the values ...
Definition: WasmAlgo.cpp:2133
open_addressing_hash_table_storage< IsGlobal > storage_
‍if IsGlobal, contains backups for address, capacity, number of entries, and absolute high watermark
Definition: WasmAlgo.hpp:912
void for_each(callback_t Pipeline) const override
Calls Pipeline for each entry contained in the hash table.
Definition: WasmAlgo.cpp:1830
void teardown() override
Performs the teardown of all local variables of the hash table (by storing them into the global backu...
Definition: WasmAlgo.cpp:1550
void rehash()
Performs rehashing, i.e.
Definition: WasmAlgo.cpp:2231
std::optional< Var< U32x1 > > mask_
mask of hash table; always a power of 2 minus 1, i.e. 0b0..01..1
Definition: WasmAlgo.hpp:908
std::pair< entry_t, Boolx1 > try_emplace(std::vector< SQL_t > key) override
If no entry with key key already exists, inserts one into the hash table, i.e.
Definition: WasmAlgo.cpp:1702
entry_t value_entry(Ptr< void > ptr) const
Returns a handle which may be used to write the values of the corresponding entry.
Definition: WasmAlgo.cpp:2058
void setup() override
Performs the setup of all local variables of the hash table (by reading them from the global backups ...
Definition: WasmAlgo.cpp:1504
std::pair< entry_t, Boolx1 > find(std::vector< SQL_t > key, hint_t bucket_hint) override
Tries to find an entry with key key in the hash table.
Definition: WasmAlgo.cpp:1794
Ptr< void > emplace_without_rehashing(std::vector< SQL_t > key)
Inserts an entry into the hash table with key key regardless whether it already exists,...
Definition: WasmAlgo.cpp:1645
Ptr< void > compute_bucket(std::vector< SQL_t > key) const override
Computes the bucket for key key.
Definition: WasmAlgo.cpp:1595
entry_t emplace(std::vector< SQL_t > key) override
Inserts an entry into the hash table with key key regardless whether it already exists,...
Definition: WasmAlgo.cpp:1617
void insert_key(Ptr< void > slot, std::vector< SQL_t > key)
Inserts the key key into the slot at address slot.
Definition: WasmAlgo.cpp:1981
std::conditional_t< IsGlobal, Global< T >, Var< T > > var_t
‍variable type dependent on whether the hash table should be globally usable
Definition: WasmAlgo.hpp:904
entry_t dummy_entry() override
Returns a handle to a newly created dummy entry which may be used to write the values for this entry.
Definition: WasmAlgo.cpp:1878
void for_each_in_equal_range(std::vector< SQL_t > key, callback_t Pipeline, bool predicated, hint_t bucket_hint) const override
Calls Pipeline for each entry with key key in the hash table, where the key comparison is performed p...
Definition: WasmAlgo.cpp:1844
OpenAddressingHashTable(const Schema &schema, std::vector< HashTable::index_t > key_indices, uint32_t initial_capacity)
Creates an open addressing hash table with schema schema, keys at key_indices, and an initial capacit...
Definition: WasmAlgo.cpp:1355
open_addressing_hash_table_layout< ValueInPlace > layout_
layout of hash table
Definition: WasmAlgo.hpp:906
Ptr< void > skip_slots(Ptr< void > bucket, U32x1 skips) const override
Returns the address of the skips -th (starting with index 0) slot in the bucket starting at bucket.
Definition: WasmAlgo.cpp:2401
Ptr< void > advance_to_next_slot(Ptr< void > slot, U32x1 current_step) const override
Returns the address of the current_step -th slot (starting with index 0) of a bucket which follows th...
Definition: WasmAlgo.cpp:2411