LCOV - code coverage report
Current view: top level - src/backend - WasmAlgo.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 10 1459 0.7 %
Date: 2025-03-25 01:19:55 Functions: 3 874 0.3 %
Branches: 3 13414 0.1 %

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

Generated by: LCOV version 1.16