[llvm] Add CondGroup infrastructure and unittests. (PR #170922)

via llvm-commits llvm-commits at lists.llvm.org
Fri Dec 5 13:08:53 PST 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-adt

Author: James Player (jameswplayer)

<details>
<summary>Changes</summary>

Enable simple disjunctive comparison between one value and a group of values.

Groups of values can be constructed at runtime for groups involving non-consteval or non-integral values.  Or if the group consists entirely of integral literals, it can be expressed in an optimized compile-time bitset representation.

**Function construction example:**
```
bool isPostOp(StringRef Token) {
  return Token == cgrp::anyOf("++", "--");
}
```
In this case, comparing to the group returned from `cgrp::anyOf()` expands to:
```
Token == "++" || Token == "--"
```
The comparison is legal as long as there exists a valid `operator==()` comparison between the singleton value and every member of the group.  Note that the group is expressed as a tuple-like object, so types need not match.

As long as the elements of the group support `constexpr` copy construction and or equivalence comparison, the group may also be used in a `consteval` context.  For example:
```
static_assert("Baz" == cgrp::anyOf("Foo", "Baz", "Buz"));
```
or stored to a variable:
```
inline constexpr auto FooGroup = cgrp::makeGroup("Foo", "Baz", "Buz");
static_assert("Baz" == cgrp::anyOf(FooGroup));
```

**Optimized groups of integral / enum literals:**

We found that folks like creating large groups of integral / enum literals.  In order to optimize the representation of these groups at compile-time, the user must supply them as template arguments.  Therefore we distinguish these groups by creating them via `inline constexpr` template variable instantiations.  For example:
```
inline constexpr FivesGroup =
  cgrp::Literals<5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70>;

static_assert(1 != cgrp::anyOf(FivesGroup));
static_assert(35 == cgrp::anyOf(FivesGroup));
```
In this case, `FivesGroup` will be expressed with bitset-like representation requiring two 64-bit integers.  A disjunctive comparison is then a simple O(1) bit-extraction from that array.  The bitset-like representation must be evaluated at compile-time because all the information is stored in template parameters.

`cgrp::AnyOf<...>` serves as a short-cut to create and compare a group of integral literals inline:
```
static_assert(25 == cgrp::AnyOf<5, 10, 15, 20, 25, 30>);
```

These literal groups are automatically clustered to save space in the ultimate bitset representation.  For example:
```
inline constexpr auto Clusters = cgrp::Literals<1, 2, 3, 4, 5, 6, 1001, 1002, 1003, 1004, 1005>;
```
The `Clusters` derived representation will consist of two bitsets, one with a start offset of `1` and the other a start offset of `1001`.  A disjunctive comparison against `Clusters` results in two bit-extraction operations across the two clusters.

The clustering logic contains heuristics which decide whether to start a new cluster or treat a value as a singleton.  Singleton values get added to a catch-all sequence that are checked after all the bitset-like clusters.


**Composing groups of literals**

Literal groups may be composed via set operators at compile-time.  The resulting set has its representation re-optimized specifically for the values in the group.  The following set operators are supported: `|` (union), `&` (intersection), `-` (difference).  The composition operators are always evaluated at compile-time.  Example usage:
```
inline constexpr auto Evens = cgrp::Literals<2, 4, 6, 8, 10>;
inline constexpr auto Odds = cgrp::Literals<1, 3, 5, 7, 9>;

inline constexpr auto OneToTen = Evens | Odds;
static_assert(1 == cgrp::anyOf(OneToTen) && 10 == cgrp::anyOf(OneToTen));
```


**Iterating groups of literals**

In order to keep these groups as a single source of truth, it's also possible to iterate the values.  The underlying storage for the container interface is an array of the elements sorted at compile-time.  This array is not instantiated unless the user instantiates any member of the container interface.
```
void foo() {
  constexpr auto Fives = cgrp::Literals<5, 10, 15, 20, 25>;
  for (auto Elem : Fives)
    processElem(Elem);
}
```

---

Patch is 187.71 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/170922.diff


7 Files Affected:

- (added) llvm/include/llvm/ADT/CondGroup.h (+983) 
- (added) llvm/include/llvm/ADT/ConstexprUtils.h (+469) 
- (added) llvm/include/llvm/ADT/MetaSet.h (+1083) 
- (modified) llvm/unittests/ADT/CMakeLists.txt (+3) 
- (added) llvm/unittests/ADT/CondGroupTest.cpp (+947) 
- (added) llvm/unittests/ADT/ConstexprUtilsTest.cpp (+973) 
- (added) llvm/unittests/ADT/MetaSetTest.cpp (+1267) 


``````````diff
diff --git a/llvm/include/llvm/ADT/CondGroup.h b/llvm/include/llvm/ADT/CondGroup.h
new file mode 100644
index 0000000000000..4f75e7fb799a7
--- /dev/null
+++ b/llvm/include/llvm/ADT/CondGroup.h
@@ -0,0 +1,983 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implements the condition group and associated comparisons.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_CONDGROUP_H
+#define LLVM_ADT_CONDGROUP_H
+
+#include "llvm/ADT/ConstexprUtils.h"
+#include "llvm/ADT/MetaSet.h"
+
+#include <cstdint>
+#include <optional>
+#include <type_traits>
+#include <utility>
+
+namespace llvm::cgrp {
+
+/// Marker class used as a base for all CondGroup classes.
+///
+/// All `CondGroup`-like classes must derive from this base class in order for
+/// template logic to function correctly.
+struct CondGroupBase {};
+
+namespace cgrp_detail {
+
+template <typename T> using IsCondGroup = std::is_base_of<CondGroupBase, T>;
+
+template <typename T> struct IsStdOptional : std::false_type {};
+
+template <typename T>
+struct IsStdOptional<std::optional<T>> : std::true_type {};
+
+template <typename T, bool = std::is_enum_v<T>> struct UnderlyingTypeImpl {
+  using type = T;
+};
+
+/// Determine whether the specified type is an enum class type.
+template <typename T, bool = std::is_enum_v<T>>
+class IsEnumClass : public std::false_type {};
+
+template <typename EnumT>
+class IsEnumClass<EnumT, true>
+    : public std::bool_constant<
+          !std::is_convertible_v<EnumT, std::underlying_type_t<EnumT>>> {};
+
+template <typename EnumT>
+struct UnderlyingTypeImpl<EnumT, true> : std::underlying_type<EnumT> {};
+
+template <typename T>
+using UnderlyingType = typename UnderlyingTypeImpl<T>::type;
+
+/// No integral type could be determined from a type pack.
+struct BadIntegerType : CETypeError {};
+
+template <bool AllSigned, bool AllUnsigned, size_t MaxSize>
+class GetIntegralTypeImpl {
+public:
+  using type = BadIntegerType;
+};
+
+template <size_t MaxSize> class GetIntegralTypeImpl<true, false, MaxSize> {
+  static_assert(MaxSize <= 8u);
+
+  static constexpr auto chooseType() {
+    if constexpr (MaxSize == 0u)
+      // A zero MaxSize is only possible when the set of values is empty.  Use a
+      // simple `int` to satisfy this corner-case.
+      return int();
+
+    else if constexpr (MaxSize == 1u)
+      return int8_t();
+
+    else if constexpr (MaxSize == 2u)
+      return int16_t();
+
+    else if constexpr (MaxSize == 4u)
+      return int32_t();
+
+    else
+      return int64_t();
+  }
+
+public:
+  using type = decltype(chooseType());
+};
+
+template <size_t MaxSize> class GetIntegralTypeImpl<false, true, MaxSize> {
+  static_assert(MaxSize <= 8u);
+
+  static constexpr auto chooseType() {
+    if constexpr (MaxSize == 1u)
+      return uint8_t();
+
+    else if constexpr (MaxSize == 2u)
+      return uint16_t();
+
+    else if constexpr (MaxSize == 4u)
+      return uint32_t();
+
+    else
+      return uint64_t();
+  }
+
+public:
+  using type = decltype(chooseType());
+};
+
+/// `true` when all the types are unsigned integral or enums with unsigned
+/// underlying types; `false` otherwise.
+///
+/// An empty list of types is considered 'signed'.
+template <typename... Tn>
+inline constexpr bool AllUnsigned =
+    sizeof...(Tn) &&
+    std::conjunction_v<std::is_unsigned<UnderlyingType<Tn>>...>;
+
+/// `true` when all the types are signed integral or enums with signed
+/// underlying types; `false` otherwise.
+///
+/// An empty list of types is not considered 'unsigned'.
+template <typename... Tn>
+inline constexpr bool AllSigned =
+    std::conjunction_v<std::is_signed<UnderlyingType<Tn>>...>;
+
+template <typename... Tn>
+using GetIntegralType =
+    typename GetIntegralTypeImpl<AllSigned<Tn...>, AllUnsigned<Tn...>,
+                                 ce_max<size_t>(0, sizeof(Tn)...)>::type;
+
+/// Indicate that multiple distinct enum types were encountered during an
+/// `EnumMeet` operation.
+struct EnumMeetMultipleEnums : CETypeError {};
+
+template <typename T0, typename T1> class EnumMeetImpl {
+  template <typename ArgT>
+  static constexpr bool ValidArg =
+      std::is_integral_v<ArgT> || std::is_void_v<ArgT> || std::is_enum_v<ArgT>;
+
+  /// Determine the meet type given two non-error types.
+  ///
+  /// Assume that if only one type is an enum class, then it must be `U0`.
+  template <typename U0, typename U1> static constexpr auto chooseTypeLegal() {
+    if constexpr (IsEnumClass<U0>::value) {
+      if constexpr (!IsEnumClass<U1>::value || std::is_same_v<U0, U1>)
+        return U0();
+      else
+        // Return an error type from this context instead of `static_assert`
+        // here in order to provide the instantiating context in the compiler
+        // error output.
+        return EnumMeetMultipleEnums();
+    } else {
+      static_assert(
+          !IsEnumClass<U1>::value,
+          "Assumption that single enum class type occurs in 'U0' violated.");
+      // void return type.
+      return;
+    }
+  }
+
+  static constexpr auto chooseType() {
+    if constexpr (std::is_base_of_v<CETypeError, T0>) {
+      return T0();
+    } else if constexpr (std::is_base_of_v<CETypeError, T1>) {
+      return T1();
+    } else {
+      static_assert(ValidArg<T0>,
+                    "Unexpected type... expected integral, enum or void type.");
+      static_assert(ValidArg<T1>,
+                    "Unexpected type... expected integral, enum or void type.");
+
+      // Conditionally swap T0 and T1 before calling `chooseTypeLegal()` to
+      // ensure that if only one is an enum class, it occurs as the first type.
+      if constexpr (IsEnumClass<T0>::value)
+        return chooseTypeLegal<T0, T1>();
+      else
+        return chooseTypeLegal<T1, T0>();
+    }
+  }
+
+public:
+  using type = decltype(chooseType());
+};
+
+template <typename T0, typename T1>
+using EnumMeetT = typename EnumMeetImpl<T0, T1>::type;
+
+template <typename... Tn> class GetEnumTypeImpl {};
+
+template <typename T0, typename... Tn>
+class GetEnumTypeImpl<T0, Tn...> : public GetEnumTypeImpl<Tn...> {
+  using ChildEnumT = typename GetEnumTypeImpl<Tn...>::type;
+
+public:
+  using type = EnumMeetT<T0, ChildEnumT>;
+};
+
+template <> class GetEnumTypeImpl<> {
+public:
+  using type = void;
+};
+
+template <typename... Tn>
+using GetEnumType = typename GetEnumTypeImpl<Tn...>::type;
+
+} // namespace cgrp_detail
+
+/// Recursive tuple implementation.
+///
+/// \note This exists because it compiles faster on many toolchains compared to
+/// using std::tuple.  Certain gcc toolchains seem to blow up with extensive
+/// std::tuple use.
+template <typename... Tn> class CondGroupTuple {};
+
+namespace cgrp_detail {
+
+template <typename SingleT, typename GroupElemT>
+constexpr bool elemEqual(SingleT const &Single, GroupElemT const &Elem) {
+  if constexpr (IsCondGroup<GroupElemT>::value) {
+    return Elem.equalDisj(Single);
+  } else if constexpr (std::is_integral_v<SingleT> &&
+                       std::is_enum_v<GroupElemT>) {
+    return Single == std::underlying_type_t<GroupElemT>(Elem);
+  } else if constexpr (std::is_enum_v<SingleT> &&
+                       std::is_integral_v<GroupElemT>) {
+    return std::underlying_type_t<SingleT>(Single) == Elem;
+  } else if constexpr (std::is_same_v<std::nullopt_t, GroupElemT>) {
+    // `std::optional` uses its own operator==() to compare the wrapped
+    // optional type to any conditional group type.  That means we may be
+    // encountering an optional's wrapped type here.  If we are comparing a
+    // non-nullopt_t value, then equality is always `false` because the parent
+    // optional contained a valid value.
+    return std::is_same_v<std::nullopt_t, SingleT>;
+  } else {
+    return Single == Elem;
+  }
+}
+
+} // namespace cgrp_detail
+
+template <typename T0, typename... Tn>
+class CondGroupTuple<T0, Tn...> : public CondGroupTuple<Tn...> {
+  using RestT = CondGroupTuple<Tn...>;
+
+private:
+  T0 CurVal;
+
+public:
+  template <typename... Un>
+  constexpr CondGroupTuple(T0 const &Cur, Un &&...Rest)
+      : RestT(std::forward<Un>(Rest)...), CurVal(Cur) {}
+
+  template <typename... Un>
+  constexpr CondGroupTuple(T0 &&Cur, Un &&...Rest)
+      : RestT(std::forward<Un>(Rest)...), CurVal(std::move(Cur)) {}
+
+  /// Disjunction of '==' comparisons.
+  template <typename U> constexpr bool equalDisj(U const &Single) const {
+    return cgrp_detail::elemEqual(Single, CurVal) ||
+           getRest().equalDisj(Single);
+  }
+
+private:
+  constexpr RestT const &getRest() const { return *this; }
+};
+
+template <typename T0> class CondGroupTuple<T0> : public CondGroupBase {
+  T0 CurVal;
+
+public:
+  constexpr CondGroupTuple(T0 const &Cur) : CurVal(Cur) {}
+  constexpr CondGroupTuple(T0 &&Cur) : CurVal(std::move(Cur)) {}
+
+  template <typename U> constexpr bool equalDisj(U const &Single) const {
+    return cgrp_detail::elemEqual(Single, CurVal);
+  }
+};
+
+template <> class CondGroupTuple<> : public CondGroupBase {
+public:
+  template <typename U> bool equalDisj(U const &Single) const { return false; }
+};
+
+/// Class which distributes comparisons across all its data.
+///
+/// The basic `CondGroup` class uses `std::tuple`-like storage for elements of
+/// the group.
+///
+/// This class records a group of conditions which can be compared using
+/// the following functions:
+///  - `llvm::cgrp::anyOf()`
+///
+/// Above functions synthesize a comparison class which will distribute
+/// equal / inequal comparisons against a single value.  This enables
+/// the user to very succinctly specify a comparison across a group of
+/// possible values.
+///
+/// For example:
+/// \code{.cpp}
+///  enum class Op {
+///   Add, AddLo, AddHi,
+///   Sub,
+///   Mul, MulLo, MulHi,
+///   Mad,
+///   Load, Store,
+///   Barrier,
+///  };
+///
+///  constexpr auto ArithGroup =
+///    llvm::cgrp::makeGroup(Op::Add, Op::Sub, Op::Mul);
+///
+///  if (getOp() == cgrp::anyOf(ArithGroup)) {
+///    // Matched at least one of the arithmatic opcodes in `ArithGroup'.
+///  }
+/// \endcode
+///
+/// This comparison is functionally eqivalent to the long-form:
+/// \code{.cpp}
+///  if (getOp() == Op::Add || getOp() == Op::Sub || getOp() == Op::Mul) {
+///    // Process Arith op.
+///  }
+/// \endcode
+///
+/// The only difference here is that `getOp()` is only called once.
+/// Short-circuiting will occur if an early comparison matches (in
+/// the case of `cgrp::anyOf()`).
+///
+/// \note All of the expressions passed to `cgrp::anyOf()` will be evaluated,
+/// regardless of short-circuiting due to function call semantics.
+///
+///
+/// Users can nest `CondGroup` objects to compose larger logical groups, and
+/// the comparisons will behave efficiently & correctly.
+///
+/// For example:
+/// \code{.cpp}
+///  static constexpr auto AddGroup =
+///    llvm::cgrp::makeGroup(Op::AddLo, Op::AddHi, Op::Add);
+///
+///  static constexpr auto MulMadGroup =
+///    llvm::cgrp::makeGroup(Op::MulLo, Op::MulHi, Op::Mul, Op::Mad);
+///
+///  static constexpr auto StrangeGroup =
+///    llvm::cgrp::makeGroup(AddGroup, MulMadGroup, Op::Bar);
+///
+///  if (getOp() == cgrp::anyOf(StrangeGroup))
+///    // Opcode belongs to 'StrangeGroup`.
+/// \endcode
+template <typename... Tn> class CondGroup : public CondGroupTuple<Tn...> {
+public:
+  using CondGroup::CondGroupTuple::CondGroupTuple;
+};
+
+/// `CondGroup`-like class which stores integral or enum elements in a MetaSet
+/// (e.g. `MetaBitset` or `MetaSequenceSet`).
+///
+/// When applicable, this group can be much more space efficient than the tuple
+/// representation.  Additionally, the `MetaBitset` can save run-time by
+/// querying hundreds of elements in an O(1) bit extraction.
+///
+/// \tparam MetaSetT  MetaSet type to query for membership.
+/// \tparam EnumT  If `void` then all enum or integral types can be compared to
+/// the group; otherwise, all queried types must be integral or convertible to
+/// this type.
+template <typename MetaSetT, typename EnumT = void>
+class CondGroupMetaSet : public CondGroupBase,
+                         public MetaSetSortedContainer<MetaSetT> {
+public:
+  using set_type = MetaSetT;
+
+public:
+  constexpr CondGroupMetaSet() {}
+
+  /// Disjunction of '==' comparisons.
+  template <typename U> constexpr bool equalDisj(U const &Single) const {
+    if constexpr (cgrp_detail::IsEnumClass<U>::value) {
+      using EnumMeetT = cgrp_detail::EnumMeetT<U, EnumT>;
+
+      static_assert(
+          !std::is_base_of_v<CETypeError, EnumMeetT>,
+          "enum class type compared with a CondGroupMetaSet containing a "
+          "different enum class type.");
+    }
+    return MetaSetT::contains(Single);
+  }
+};
+
+/// Create a condition group object containing the specified \p Values.
+/// \relates CondGroup
+///
+/// \param Values  A list of values to forward to the `CondGroup` object.
+///
+/// \return A `CondGroup` containing all the specified values.
+template <typename... Tn> constexpr auto makeGroup(Tn &&...Values) {
+  return CondGroup<std::decay_t<Tn>...>(std::forward<Tn>(Values)...);
+}
+namespace cgrp_detail {
+
+/// Do not create any MetaBitset with more than this number of words (either
+/// dense or sparse).
+inline constexpr size_t BitsetWordLimit = 8u;
+
+/// Do not create any MetaBitset with more than this number of bits (either
+/// dense or sparse).
+inline constexpr size_t BitsetBitLimit = BitsetWordLimit * 64u;
+
+template <typename SeqT> class ChooseMetaSetImpl {};
+
+template <typename IntT, IntT... Values>
+class ChooseMetaSetImpl<std::integer_sequence<IntT, Values...>> {
+  // Don't use a bitset representation if there are too few values since the
+  // compiler seems to optimize these quite effectively.
+  static constexpr size_t MinValueCount = 4;
+
+  static constexpr auto chooseMetaBitsetType() {
+    constexpr IntT MinVal = ce_min<IntT>(Values...);
+    constexpr IntT MaxVal = ce_max<IntT>(Values...);
+
+    // Compute the number of words that would be required for a bitset
+    // representation.  Do not use a bitset if this exceeds a word limit imposed
+    // by the `MetaBitset` class.
+    constexpr size_t BitsetNumWords =
+        MetaBitsetNumWordsDetailed<IntT, MinVal, MaxVal>;
+
+    // The bitset representation will require this many bytes in .rodata.
+    constexpr size_t BitsetArraySize = BitsetNumWords * sizeof(uint64_t);
+
+    constexpr size_t BitsetPreferenceAddend = sizeof...(Values) < 8 ? 16U : 32U;
+
+    constexpr size_t TupleSize = sizeof(IntT) * sizeof...(Values);
+
+    constexpr size_t BitsetArrayUpperBound = ce_min<size_t>(
+        // (1.5 * TupleSize) + BitsetPreferenceAddend
+        TupleSize + (TupleSize >> 1) + BitsetPreferenceAddend,
+        // Max size we want for a single bitset buffer.
+        BitsetWordLimit * sizeof(uint64_t));
+
+    // BitsetSize <= 1.5*TupleSize + BitsetPreferenceAddend
+    constexpr bool UseSingleMetabitset =
+        BitsetArraySize <= BitsetArrayUpperBound;
+
+    if constexpr (UseSingleMetabitset)
+      return MakeMetaBitsetDetailed<IntT, MinVal, MaxVal, Values...>();
+    else
+      return MakeMetaSparseBitset<IntT, BitsetBitLimit, Values...>();
+  }
+
+  static constexpr auto chooseMetaSetType() {
+    if constexpr (sizeof...(Values) < MinValueCount)
+      return MakeMetaSequenceSet<IntT, Values...>();
+    else
+      return chooseMetaBitsetType();
+  }
+
+public:
+  using type = decltype(chooseMetaSetType());
+};
+
+template <auto... Values> class ChooseGroup {
+  using IntT = GetIntegralType<decltype(Values)...>;
+
+  static_assert(
+      std::is_integral_v<IntT>,
+      "CondGroups of literals must be either integral or enum types and must "
+      "all share the same sign.");
+
+  using EnumT = GetEnumType<decltype(Values)...>;
+
+  static_assert(
+      !std::is_same_v<cgrp_detail::EnumMeetMultipleEnums, EnumT>,
+      "Multiple enum class types encountered while building a CondGroup of "
+      "literals; at most one enum class type may be present in the value "
+      "pack.");
+
+  static_assert(!std::is_base_of_v<CETypeError, EnumT>,
+                "Unspecified error while deriving the enum type for a "
+                "CondGroup of literals.");
+
+  using SequenceType =
+      std::integer_sequence<IntT, static_cast<IntT>(Values)...>;
+  using MetaSetT = typename ChooseMetaSetImpl<SequenceType>::type;
+
+public:
+  using type = CondGroupMetaSet<MetaSetT, EnumT>;
+};
+
+template <auto... Values> constexpr auto makeLiteralGroup() {
+  using GroupT = typename ChooseGroup<Values...>::type;
+  return GroupT();
+}
+
+} // namespace cgrp_detail
+
+/// Instantiate a condition group of literal values which may be heavily
+/// optimized depending on the specified \p Values.
+///
+/// \tparam Values  Literal values to add to the condition group.
+///
+/// If the literals meet the following conditions:
+///  - All integral or enum types
+///  - All the same signedness
+///
+/// Then compile-time optimizations can be applied to the representation.  The
+/// ideal cases for optimizing the representation are:
+///
+///  1. A single cluster of literals which are close to eachother in value.
+///
+///  2. Multiple clusters of literals which are close to eachother in value,
+///     with a relatively small number of values scattered between the clusters.
+///
+/// \note The specification of literals does not need to be in any order to
+/// recieve representation optimizations.  Additionally, repeat elements in the
+/// group are tolerated but discouraged.
+///
+///
+/// \section single_cluster Single Cluster of Values
+///
+/// If single cluster of values is detected, then a single `MetaBitset` is used
+/// to represent the condition group.  This means that the storage will be
+/// limited to a `static constexpr` array of 64-bit integers.  Each bit in the
+/// array represents a value in the cluster offset from a potentially non-zero
+/// starting point.
+///
+/// The following is an example of a literal group which is represented as a
+/// single `MetaBitset`:
+/// \code{.cpp}
+///  static constexpr auto Fives =
+///     cgrp::Literals<5, 15, 25, 35, 45, 55, 65, 75, 85, 95, 10, 20, 30, 40,
+///                    50, 60, 70, 80, 90, 100>;
+///
+///  // Verify the representation.
+///  static_assert(std::is_same_v<
+///                  std::remove_cv_t<decltype(Fives)>,
+///                  CondGroupMetaSet<
+///                    MetaBitset<long, 5L,
+///                               0x1084210842108421, // Word 0
+///                               0x84210842          // Word 1
+///                    >
+///                  >
+///                >);
+/// \endcode
+///
+/// In this example, `Fives` is represented by a single `MetaBitset` with a
+/// start offset of `5` and two 64-bit words specified as template parameters.
+/// This means that group inclusion queries will be performed in O(1) as a
+/// bit-extraction from a 128-bit buffer.
+///
+///
+/// \section multi_cluster Multiple Clusters of Values
+///
+/// If the specified values span too large of a range to fit in a single
+/// `MetaBitset`, then cluster paritioning is performed.  The literals are
+/// sorted and then paritioned based on a sliding window of maximal allowed
+/// `MetaBitset` size.  Any clusters which are too small (less than a few
+/// elements) are added to a fall-back `MetaSequenceSet` (which is the
+/// meta-programming equivalent of the tuple representation).  The remaining
+/// clusters which are sufficiently large are converted to `MetaBitset`s.
+///
+/// \code{.cpp}
+///  static constexpr auto Clusters =
+///      cgrp::Literals<10000, 10002, 10004, 10006, 10008,
+///                     1000, 1002, 1004, 1006, 1008,
+///                     5000, 8000>;
+///
+///  static_assert(
+///      std::is_same_v<
+///          std::remove_cv_t<decltype(Clusters)>,
+///          CondGroupMetaSet<
+///              MetaBitset<long, 1000, 0x155>,
+///              MetaBitset<long, 10000, 0x155>,
+///              MetaSequenceSet<std::i...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list