[llvm] [Hashing] Replace CityHash mixers with xxh3 (PR #194567)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sat May 9 14:56:53 PDT 2026


================
@@ -362,63 +219,39 @@ template <typename T> auto get_hashable_data(const T &value) {
   }
 }
 
-/// Helper to store data from a value into a buffer and advance the
-/// pointer into that buffer.
-///
-/// This routine first checks whether there is enough space in the provided
-/// buffer, and if not immediately returns false. If there is space, it
-/// copies the underlying bytes of value into the buffer, advances the
-/// buffer_ptr past the copied bytes, and returns true.
-template <typename T>
-bool store_and_advance(char *&buffer_ptr, char *buffer_end, const T& value,
-                       size_t offset = 0) {
-  size_t store_size = sizeof(value) - offset;
-  if (buffer_ptr + store_size > buffer_end)
-    return false;
-  const char *value_data = reinterpret_cast<const char *>(&value);
-  std::memcpy(buffer_ptr, value_data + offset, store_size);
-  buffer_ptr += store_size;
-  return true;
-}
-
 /// Implement the combining of integral values into a hash_code.
 ///
 /// This overload is selected when the value type of the iterator is
 /// integral. Rather than computing a hash_code for each object and then
 /// combining them, this (as an optimization) directly combines the integers.
+///
+/// xxh3 has no streaming entry point in libLLVMSupport, so the byte stream is
+/// flattened to a buffer and hashed in one shot. A 64-byte on-stack buffer
+/// covers the common cases; longer non-contiguous ranges (the prior chunked
+/// CityHash impl was streaming and never allocated) fall back to the heap.
 template <typename InputIteratorT>
 hash_code hash_combine_range_impl(InputIteratorT first, InputIteratorT last) {
-  const uint64_t seed = get_execution_seed();
-  char buffer[64], *buffer_ptr = buffer;
-  char *const buffer_end = std::end(buffer);
-  while (first != last && store_and_advance(buffer_ptr, buffer_end,
-                                            get_hashable_data(*first)))
-    ++first;
-  if (first == last)
-    return hash_short(buffer, buffer_ptr - buffer, seed);
-  assert(buffer_ptr == buffer_end);
-
-  hash_state state = state.create(buffer, seed);
-  size_t length = 64;
-  while (first != last) {
-    // Fill up the buffer. We don't clear it, which re-mixes the last round
-    // when only a partial 64-byte chunk is left.
-    buffer_ptr = buffer;
-    while (first != last && store_and_advance(buffer_ptr, buffer_end,
-                                              get_hashable_data(*first)))
-      ++first;
-
-    // Rotate the buffer if we did a partial fill in order to simulate doing
-    // a mix of the last 64-bytes. That is how the algorithm works when we
-    // have a contiguous byte sequence, and we want to emulate that here.
-    std::rotate(buffer, buffer_ptr, buffer_end);
-
-    // Mix this chunk into the current state.
-    state.mix(buffer);
-    length += buffer_ptr - buffer;
-  };
-
-  return state.finalize(length);
+  alignas(uint64_t) char stack_buf[64];
+  std::unique_ptr<char[]> heap_buf;
+  char *buf = stack_buf;
+  size_t cap = sizeof(stack_buf);
+  size_t len = 0;
+  for (; first != last; ++first) {
+    auto data = get_hashable_data(*first);
+    if (len + sizeof(data) > cap) {
+      size_t new_cap = cap * 2;
+      while (new_cap < len + sizeof(data))
+        new_cap *= 2;
+      std::unique_ptr<char[]> new_buf(new char[new_cap]);
----------------
MaskRay wrote:

This is intentional.
` auto new_buf = std::make_unique<char[]>(new_cap);` value-initializes (i.e. zero-initializes) `new_buf`, which is redundant.

https://github.com/llvm/llvm-project/pull/194567


More information about the llvm-commits mailing list