[llvm] [ADT] Speed up FoldingSet with Swiss Table (PR #218156)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 22 12:48:21 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-adt
Author: Kazu Hirata (kazutakahirata)
<details>
<summary>Changes</summary>
This patch speeds up FoldingSet by reimplementing it as an
open-addressing Swiss Table hash set. With this patch, "clang -O3"
compiles 1.40% faster on a wide variety of source files.
Without this patch, FoldingSet is implemented as a single-link chained
hash table where each FoldingSetNode stores a pointer to the next node
in a circular linked list.
With this patch, FoldingSet uses an open-addressing Swiss Table layout
with 1-byte control metadata where probing checks 8 control bytes in
parallel using 64-bit bitwise operations.
In addition, each FoldingSetNode replaces the bucket link pointer with
a cached 32-bit hash value (FoldingSetHash). This brings several
benefits:
- Fast node removal: Code that modifies or deletes nodes in place
(such as SelectionDAG CSE maps or ScalarEvolution UniqueSCEVs) can
call RemoveNode(N) and locate the slot directly using the cached hash
without re-profiling the node.
- Zero re-profiling on table growth: When the table doubles in capacity,
existing nodes are re-inserted using their cached hashes, completely
eliminating the need to re-run Profile() and rebuild temporary
FoldingSetNodeID buffers.
To keep the patch size reasonable, this patch leaves certain dead code
and parameters, such as ComputeNodeHash and the Info parameter to
InsertNode. We will remove them in a follow-up patch.
Assisted-by: Antigravity
---
Patch is 23.60 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/218156.diff
2 Files Affected:
- (modified) llvm/include/llvm/ADT/FoldingSet.h (+63-37)
- (modified) llvm/lib/Support/FoldingSet.cpp (+215-154)
``````````diff
diff --git a/llvm/include/llvm/ADT/FoldingSet.h b/llvm/include/llvm/ADT/FoldingSet.h
index ab4fa2712d4a5..bd75f6047f3e9 100644
--- a/llvm/include/llvm/ADT/FoldingSet.h
+++ b/llvm/include/llvm/ADT/FoldingSet.h
@@ -37,9 +37,8 @@ namespace llvm {
/// it, otherwise return the bucket it should be inserted into.
/// 2. Given a node that has already been created, remove it from the set.
///
-/// This class is implemented as a single-link chained hash table, where the
-/// "buckets" are actually the nodes themselves (the next pointer is in the
-/// node). The last node points back to the bucket to simplify node removal.
+/// This class is implemented as an open-addressing Swiss Table hash set that
+/// uniques heap-allocated objects by computing and comparing their profile IDs.
///
/// Any node that is to be included in the folding set must be a subclass of
/// FoldingSetNode. The node class must also define a Profile method used to
@@ -289,23 +288,33 @@ class FoldingSetNodeID {
};
//===----------------------------------------------------------------------===//
-/// Implements the folding set functionality. The main structure is an array of
-/// buckets. Each bucket is indexed by the hash of the nodes it contains. The
-/// bucket itself points to the nodes contained in the bucket via a singly
-/// linked list. The last node in the list points back to the bucket to
-/// facilitate node removal.
-///
+/// Non-templated base class for FoldingSet and ContextualFoldingSet to enable
+/// type erasure, shared memory management, and reduced template bloat.
class FoldingSetBase {
+public:
+ static constexpr uint8_t Empty = 0x80;
+ static constexpr uint8_t Deleted = 0xFE;
+ static constexpr unsigned GroupWidth = 8;
+
protected:
- /// Array of bucket chains.
- void **Buckets;
+ /// Sentinel control group used for empty and moved-from sets.
+ static constexpr uint8_t EmptyGroup[GroupWidth] = {
+ Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty};
+
+ /// Control bytes for Swiss Table probing.
+ uint8_t *Ctrl = const_cast<uint8_t *>(EmptyGroup);
+
+ /// Array of buckets.
+ void **Buckets = nullptr;
/// Length of the Buckets array. Always a power of 2.
- unsigned NumBuckets;
+ unsigned NumBuckets = 0;
+
+ /// Number of nodes in the folding set.
+ unsigned NumNodes = 0;
- /// Number of nodes in the folding set. Growth occurs when NumNodes
- /// is greater than twice the number of buckets.
- unsigned NumNodes;
+ /// Number of deleted nodes (tombstones) in the table.
+ unsigned NumDeleted = 0;
LLVM_ABI explicit FoldingSetBase(unsigned Log2InitSize);
LLVM_ABI FoldingSetBase(FoldingSetBase &&Arg);
@@ -314,19 +323,18 @@ class FoldingSetBase {
public:
//===--------------------------------------------------------------------===//
- /// This class is used to maintain the singly linked bucket list in
- /// a folding set.
+ /// This class is used to maintain node state in a folding set.
class Node {
private:
- // NextInFoldingSetBucket - next link in the bucket list.
- void *NextInFoldingSetBucket = nullptr;
+ // Cached 32-bit hash value to avoid re-profiling on growth and removal.
+ uint32_t FoldingSetHash = 0;
public:
Node() = default;
// Accessors
- void *getNextInBucket() const { return NextInFoldingSetBucket; }
- void SetNextInBucket(void *N) { NextInFoldingSetBucket = N; }
+ uint32_t getFoldingSetHash() const { return FoldingSetHash; }
+ void setFoldingSetHash(uint32_t Hash) { FoldingSetHash = Hash; }
};
/// Remove all nodes from the folding set.
@@ -340,11 +348,7 @@ class FoldingSetBase {
/// Returns the number of nodes permitted in the folding set
/// before a rebucket operation is performed.
- unsigned capacity() const {
- // We allow a load factor of up to 2.0,
- // so that means our capacity is NumBuckets * 2
- return NumBuckets * 2;
- }
+ unsigned capacity() const { return (NumBuckets * 7) / 8; }
protected:
/// Functions provided by the derived class to compute folding properties.
@@ -369,6 +373,22 @@ class FoldingSetBase {
};
private:
+ // Set a control byte and mirror it at the end of the table if needed.
+ void setCtrlMirrored(unsigned Index, uint8_t Value) {
+ Ctrl[Index] = Value;
+ if (Index < GroupWidth)
+ Ctrl[NumBuckets + Index] = Value;
+ }
+
+ // Test if a control byte indicates an empty or deleted slot.
+ static bool isEmptyOrDeleted(uint8_t C) { return (C & 0x80) != 0; }
+
+ // Insert a node into an empty or deleted slot without checking capacity.
+ void insertImpl(void *N, uint32_t Hash);
+
+ // Allow the iterator to access the control bytes and bucket array.
+ friend class FoldingSetIteratorImpl;
+
/// Resize the hash table and rehash everything. \p NewBucketCount must be a
/// power of two, and must be greater than the old bucket count.
void GrowBucketCount(unsigned NewBucketCount, const FoldingSetInfo &Info);
@@ -496,13 +516,13 @@ class FoldingSetImpl : public FoldingSetBase, public Trait::ContextStorage {
public:
using iterator = FoldingSetIterator<T>;
- iterator begin() { return iterator(Buckets); }
- iterator end() { return iterator(Buckets + NumBuckets); }
+ iterator begin() { return iterator(this, 0); }
+ iterator end() { return iterator(this, NumBuckets); }
using const_iterator = FoldingSetIterator<const T>;
- const_iterator begin() const { return const_iterator(Buckets); }
- const_iterator end() const { return const_iterator(Buckets + NumBuckets); }
+ const_iterator begin() const { return const_iterator(this, 0); }
+ const_iterator end() const { return const_iterator(this, NumBuckets); }
/// Grow the number of buckets so that we can hold at least \p EltCount
/// nodes before rebucketing. May allocate more space than requested.
@@ -638,28 +658,34 @@ template <class T, class VectorT = SmallVector<T *, 8>> class FoldingSetVector {
/// how to walk the folding set hash table.
class FoldingSetIteratorImpl {
protected:
- FoldingSetNode *NodePtr;
+ const FoldingSetBase *Set = nullptr;
+ unsigned Index = 0;
- LLVM_ABI FoldingSetIteratorImpl(void **Bucket);
+ LLVM_ABI FoldingSetIteratorImpl(const FoldingSetBase *Set, unsigned Index);
LLVM_ABI void advance();
+ FoldingSetNode *getNode() const {
+ return static_cast<FoldingSetNode *>(Set->Buckets[Index]);
+ }
+
public:
bool operator==(const FoldingSetIteratorImpl &RHS) const {
- return NodePtr == RHS.NodePtr;
+ return Index == RHS.Index;
}
bool operator!=(const FoldingSetIteratorImpl &RHS) const {
- return NodePtr != RHS.NodePtr;
+ return Index != RHS.Index;
}
};
template <class T> class FoldingSetIterator : public FoldingSetIteratorImpl {
public:
- explicit FoldingSetIterator(void **Bucket) : FoldingSetIteratorImpl(Bucket) {}
+ explicit FoldingSetIterator(const FoldingSetBase *Set, unsigned Index)
+ : FoldingSetIteratorImpl(Set, Index) {}
- T &operator*() const { return *static_cast<T *>(NodePtr); }
+ T &operator*() const { return *static_cast<T *>(getNode()); }
- T *operator->() const { return static_cast<T *>(NodePtr); }
+ T *operator->() const { return static_cast<T *>(getNode()); }
inline FoldingSetIterator &operator++() { // Preincrement
advance();
diff --git a/llvm/lib/Support/FoldingSet.cpp b/llvm/lib/Support/FoldingSet.cpp
index d9ae1aca5fc4a..526bb0b7ebaf1 100644
--- a/llvm/lib/Support/FoldingSet.cpp
+++ b/llvm/lib/Support/FoldingSet.cpp
@@ -15,6 +15,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Endian.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/SwapByteOrder.h"
#include <cassert>
@@ -130,46 +131,130 @@ FoldingSetNodeID::Intern(BumpPtrAllocator &Allocator) const {
return FoldingSetNodeIDRef(New, Bits.size());
}
+//===----------------------------------------------------------------------===//
+// FoldingSetBase Theory of Operations
+//
+// FoldingSet is implemented as an open-addressing Swiss Table hash set.
+//
+// Memory Layout:
+// A single heap allocation holds both the control bytes (Ctrl) and the bucket
+// pointers (Buckets) sequentially:
+// [ Ctrl (NumBuckets + GroupWidth bytes) ]
+// [ Buckets (NumBuckets * sizeof(void *) bytes) ]
+//
+// Control Byte Encoding:
+// - 0x80 (Empty): The slot has never been occupied.
+// - 0xFE (Deleted): The slot previously held an element that was removed.
+// - 0x00..0x7F (Occupied): Stores H2(Hash) = Hash & 0x7F (the low 7 bits).
+//
+// Control Byte Mirroring:
+// The first GroupWidth (8) control bytes are mirrored at the end of the
+// control array (Ctrl[NumBuckets .. NumBuckets + GroupWidth - 1]). This
+// allows 8-byte group loads to read past the end of the table without wrapping
+// or branching.
+//
+// Hash Splitting and Probing:
+// - H1(Hash) = Hash >> 7 determines the initial group index.
+// - H2(Hash) = Hash & 0x7F is stored in the control byte for fast filtering.
+// Probing proceeds triangularly in increments of GroupWidth (8 slots at a
+// time), checking control bytes in parallel via 64-bit SWAR bitmask operations.
+//
+// Hash Caching:
+// Nodes cache their 32-bit hash value to avoid recomputing profiles during
+// table rehashing and node removal.
+//
+// Load Factor:
+// The table doubles in capacity when (NumNodes + NumDeleted + 1) * 8 exceeds
+// NumBuckets * 7 (a maximum load factor of 87.5%).
+
//===----------------------------------------------------------------------===//
/// Helper functions for FoldingSetBase.
-/// GetNextPtr - In order to save space, each bucket is a
-/// singly-linked-list. In order to make deletion more efficient, we make
-/// the list circular, so we can delete a node without computing its hash.
-/// The problem with this is that the start of the hash buckets are not
-/// Nodes. If NextInBucketPtr is a bucket pointer, this method returns null:
-/// use GetBucketPtr when this happens.
-static FoldingSetBase::Node *GetNextPtr(void *NextInBucketPtr) {
- // The low bit is set if this is the pointer back to the bucket.
- if (reinterpret_cast<intptr_t>(NextInBucketPtr) & 1)
- return nullptr;
-
- return static_cast<FoldingSetBase::Node *>(NextInBucketPtr);
+// Extract the 7-bit tag stored in the control byte for fast filtering.
+static inline uint8_t H2(uint32_t Hash) {
+ return static_cast<uint8_t>(Hash & 0x7F);
}
-/// GetBucketPtr - Provides a casting of a bucket pointer for isNode
-/// testing.
-static void **GetBucketPtr(void *NextInBucketPtr) {
- intptr_t Ptr = reinterpret_cast<intptr_t>(NextInBucketPtr);
- assert((Ptr & 1) && "Not a bucket pointer");
- return reinterpret_cast<void **>(Ptr & ~intptr_t(1));
+// Extract the initial group index for probing.
+static inline unsigned H1(uint32_t Hash) { return Hash >> 7; }
+
+// Ensure the hash value never wraps to nullptr when encoded.
+static inline uint32_t sanitizeHash(uint32_t Hash) {
+ if (Hash == UINT32_MAX)
+ return 0;
+ return Hash;
}
-/// GetBucketFor - Hash the specified node ID and return the hash bucket for
-/// the specified ID.
-static void **GetBucketFor(unsigned Hash, void **Buckets, unsigned NumBuckets) {
- // NumBuckets is always a power of 2.
- unsigned BucketNum = Hash & (NumBuckets - 1);
- return Buckets + BucketNum;
+// Encode a 32-bit hash into a non-null opaque pointer token.
+static inline void *encodeHash(uint32_t Hash) {
+ return reinterpret_cast<void *>(static_cast<uintptr_t>(Hash) + 1);
}
-/// AllocateBuckets - Allocate initialized bucket memory.
-static void **AllocateBuckets(unsigned NumBuckets) {
- void **Buckets =
- static_cast<void **>(safe_calloc(NumBuckets + 1, sizeof(void *)));
- // Set the very last bucket to be a non-null "pointer".
- Buckets[NumBuckets] = reinterpret_cast<void *>(-1);
- return Buckets;
+// Decode a 32-bit hash from an opaque pointer token.
+static inline uint32_t decodeHash(void *InsertPos) {
+ return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(InsertPos) - 1);
+}
+
+namespace {
+
+// A bitmask representing matching slots within a probe group.
+struct BitMask {
+ uint64_t Mask;
+ explicit BitMask(uint64_t M) : Mask(M) {}
+ explicit operator bool() const { return Mask != 0; }
+ int lowestSetBit() const { return llvm::countr_zero(Mask) >> 3; }
+ BitMask removeLowestBit() const { return BitMask(Mask & (Mask - 1)); }
+};
+
+// An 8-byte group of control metadata that performs parallel slot matching.
+struct Group {
+ static_assert(FoldingSetBase::GroupWidth == 8,
+ "Group SWAR matching requires a group width of 8 bytes.");
+ static constexpr uint64_t Lsbs = 0x0101010101010101ULL;
+ static constexpr uint64_t Msbs = 0x8080808080808080ULL;
+
+ uint64_t Ctrl;
+
+ explicit Group(const uint8_t *Ptr) : Ctrl(support::endian::read64le(Ptr)) {}
+
+ BitMask matchByte(uint8_t Byte) const {
+ uint64_t X = Ctrl ^ (Lsbs * Byte);
+ return BitMask((X - Lsbs) & ~X & Msbs);
+ }
+
+ BitMask matchEmptyOrDeleted() const { return BitMask(Ctrl & Msbs); }
+
+ BitMask matchEmpty() const { return matchByte(FoldingSetBase::Empty); }
+};
+
+// Generate triangular probing offsets across group boundaries.
+struct ProbeSequence {
+ unsigned Mask;
+ unsigned Offset;
+ unsigned Step = 0;
+
+ ProbeSequence(uint32_t Hash, unsigned NumBuckets)
+ : Mask(NumBuckets - 1), Offset(H1(Hash) & Mask) {}
+
+ unsigned offset() const { return Offset; }
+ unsigned slot(int Pos) const { return (Offset + Pos) & Mask; }
+
+ void next() {
+ Step += FoldingSetBase::GroupWidth;
+ Offset = (Offset + Step) & Mask;
+ }
+};
+
+} // namespace
+
+/// AllocateBuckets - Allocate and initialize storage for Ctrl and Buckets.
+static std::pair<uint8_t *, void **> AllocateBuckets(unsigned NumBuckets) {
+ size_t CtrlBytes = NumBuckets + FoldingSetBase::GroupWidth;
+ size_t BucketsBytes = NumBuckets * sizeof(void *);
+ uint8_t *Ctrl = static_cast<uint8_t *>(safe_malloc(CtrlBytes + BucketsBytes));
+ void **Buckets = reinterpret_cast<void **>(Ctrl + CtrlBytes);
+ memset(Ctrl, FoldingSetBase::Empty, CtrlBytes);
+ return {Ctrl, Buckets};
}
//===----------------------------------------------------------------------===//
@@ -179,39 +264,68 @@ FoldingSetBase::FoldingSetBase(unsigned Log2InitSize) {
assert(5 < Log2InitSize && Log2InitSize < 32 &&
"Initial hash table size out of range");
NumBuckets = 1 << Log2InitSize;
- Buckets = AllocateBuckets(NumBuckets);
- NumNodes = 0;
+ std::tie(Ctrl, Buckets) = AllocateBuckets(NumBuckets);
}
FoldingSetBase::FoldingSetBase(FoldingSetBase &&Arg)
- : Buckets(Arg.Buckets), NumBuckets(Arg.NumBuckets), NumNodes(Arg.NumNodes) {
+ : Ctrl(Arg.Ctrl), Buckets(Arg.Buckets), NumBuckets(Arg.NumBuckets),
+ NumNodes(Arg.NumNodes), NumDeleted(Arg.NumDeleted) {
+ Arg.Ctrl = const_cast<uint8_t *>(EmptyGroup);
Arg.Buckets = nullptr;
Arg.NumBuckets = 0;
Arg.NumNodes = 0;
+ Arg.NumDeleted = 0;
}
FoldingSetBase &FoldingSetBase::operator=(FoldingSetBase &&RHS) {
- free(Buckets); // This may be null if the set is in a moved-from state.
+ if (this == &RHS)
+ return *this;
+
+ if (NumBuckets)
+ free(Ctrl);
+
+ Ctrl = RHS.Ctrl;
Buckets = RHS.Buckets;
NumBuckets = RHS.NumBuckets;
NumNodes = RHS.NumNodes;
+ NumDeleted = RHS.NumDeleted;
+ RHS.Ctrl = const_cast<uint8_t *>(EmptyGroup);
RHS.Buckets = nullptr;
RHS.NumBuckets = 0;
RHS.NumNodes = 0;
+ RHS.NumDeleted = 0;
return *this;
}
-FoldingSetBase::~FoldingSetBase() { free(Buckets); }
+FoldingSetBase::~FoldingSetBase() {
+ if (NumBuckets)
+ free(Ctrl);
+}
void FoldingSetBase::clear() {
- // Set all but the last bucket to null pointers.
- memset(Buckets, 0, NumBuckets * sizeof(void *));
-
- // Set the very last bucket to be a non-null "pointer".
- Buckets[NumBuckets] = reinterpret_cast<void *>(-1);
-
- // Reset the node count to zero.
+ if (NumBuckets == 0)
+ return;
+ memset(Ctrl, Empty, NumBuckets + GroupWidth);
NumNodes = 0;
+ NumDeleted = 0;
+}
+
+void FoldingSetBase::insertImpl(void *N, uint32_t Hash) {
+ uint8_t TargetH2 = H2(Hash);
+
+ for (ProbeSequence Seq(Hash, NumBuckets);; Seq.next()) {
+ Group G(Ctrl + Seq.offset());
+ if (BitMask Candidates = G.matchEmptyOrDeleted()) {
+ unsigned SlotIdx = Seq.slot(Candidates.lowestSetBit());
+ if (Ctrl[SlotIdx] == Deleted)
+ --NumDeleted;
+ setCtrlMirrored(SlotIdx, TargetH2);
+ Buckets[SlotIdx] = N;
+ static_cast<Node *>(N)->setFoldingSetHash(Hash);
+ ++NumNodes;
+ return;
+ }
+ }
}
void FoldingSetBase::GrowBucketCount(unsigned NewBucketCount,
@@ -221,125 +335,80 @@ void FoldingSetBase::GrowBucketCount(unsigned NewBucketCount,
assert(isPowerOf2_32(NewBucketCount) && "Bad bucket count!");
FoldingSetBase Tmp(llvm::Log2_32(NewBucketCount));
- FoldingSetNodeID TempID;
for (unsigned i = 0; i != NumBuckets; ++i) {
- void *Probe = Buckets[i];
- if (!Probe)
+ if (isEmptyOrDeleted(Ctrl[i]))
continue;
- while (Node *NodeInBucket = GetNextPtr(Probe)) {
- // Figure out the next link, remove NodeInBucket from the old link.
- Probe = NodeInBucket->getNextInBucket();
- NodeInBucket->SetNextInBucket(nullptr);
-
- // Insert the node into the new bucket, after recomputing the hash.
- Tmp.InsertNode(
- NodeInBucket,
- GetBucketFor(Info.ComputeNodeHash(this, NodeInBucket, TempID),
- Tmp.Buckets, Tmp.NumBuckets),
- Info);
- TempID.clear();
- }
+ Node *N = static_cast<Node *>(Buckets[i]);
+ Tmp.insertImpl(N, N->getFoldingSetHash());
}
*this = std::move(Tmp);
}
void FoldingSetBase::reserve(unsigned EltCount, const FoldingSetInfo &Info) {
- // This will give us somewhere between EltCount / 2 and
- // EltCount buckets. This puts us in the load factor
- // range of 1.0 - 2.0.
if (EltCount <= capacity())
return;
- GrowBucketCount(llvm::bit_floor(EltCount), Info);
+ unsigned RequiredBuckets = std::max((EltCount * 8 + 6) / 7, GroupWidth);
+ GrowBucketCount(llvm::bit_ceil(RequiredBuckets), Info);
}
FoldingSetBase::Node *FoldingSetBase::FindNodeOrInsertPos(
const FoldingSetNodeID &ID, void *&InsertPos, const FoldingSetInfo &Info) {
- unsigned IDHash = ID.ComputeHash();
- void **Bucket = GetBucketFor(IDHash, Buckets, NumBuckets);
- void *Probe = *Bucket;
-
- InsertPos = nullptr;
-
+ uint32_t IDHash = sanitizeHash(ID.ComputeHash());
+ uint8_t TargetH2 = H2(IDHash);
FoldingSetNodeID TempID;
- while (Node *NodeInBucket = GetNextPtr(Probe)) {
- if (Info.NodeEquals(this, NodeInBucket, ID, IDHash, TempID))
- return NodeInBucket;
- TempID.clear();
+ for (ProbeSequence Seq(IDHash, NumBuckets);; Seq.next()) {
+ Group G(Ctrl + Seq.offset());
+ for (BitMask Matches = G.matchByte(TargetH2); Matches;
+ Matches = Matches.removeLowestBit()) {
+ unsigned SlotIdx = Seq.slot(Matches.lowestSetBit());
+ Node *Candidate = static_cast<Node *>(Buckets[SlotIdx]);
+ if (Info.NodeEquals(this, Candidate, ID, IDHash, TempID)) {
+ InsertPos = nullptr;
+ return Candidate;
+ }
+ TempID.clear();
+ }
- Probe = NodeInBucket->getNextInBucket();
+ if (G.matchEmpty())
+ break;
}
- // Didn't find the node, return null with the bucket as the InsertPos.
- InsertPos = Bucket;
+ // Didn't find the node, return null with the encoded hash as the InsertPos.
+ InsertPos = encodeHash(IDHash);
return nullptr;
}
void FoldingSetBase::InsertNode(Node *N, void *InsertPos,
const FoldingSetInfo &Info) {
- assert(!N->getNextInBucket());
// Do we need to grow the hashtable?
- if (NumNodes + 1 > capacity()) {
+ if (NumNodes + NumDeleted + 1 > capacity())
GrowBucketCount(NumBuckets * 2, Info);
- FoldingSetNodeID TempID;
- InsertPos = GetBucketFor(Info.ComputeNodeHash(this, N, TempID), Buckets,
- NumBuckets);
- }
-
- ++NumNodes;
-
- /// The insert position is actually a bucket pointer.
- void **Bucket = static_cast<void **>(InsertPos);
-
- void *Next = *Bucket;
-
- // If this is the first insertion into this bucket, its next pointer will be
- // null. Pretend as if it pointed to itself, setting the low bit to indicate
- // that it is a pointer to the bucket.
- if (!Next)
- Next...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/218156
More information about the llvm-commits
mailing list