[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