[llvm] Add CondGroup infrastructure and unittests. (PR #170922)
James Player via llvm-commits
llvm-commits at lists.llvm.org
Fri Dec 5 13:07:53 PST 2025
https://github.com/jameswplayer created https://github.com/llvm/llvm-project/pull/170922
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);
}
```
>From 51c0d677ca7e2945235aabbbd08cbb57fe26c294 Mon Sep 17 00:00:00 2001
From: James Player <jplayer at nvidia.com>
Date: Fri, 5 Dec 2025 12:46:12 -0800
Subject: [PATCH] Add CondGroup infrastructure and unittests.
---
llvm/include/llvm/ADT/CondGroup.h | 983 ++++++++++++++++
llvm/include/llvm/ADT/ConstexprUtils.h | 469 ++++++++
llvm/include/llvm/ADT/MetaSet.h | 1083 ++++++++++++++++++
llvm/unittests/ADT/CMakeLists.txt | 3 +
llvm/unittests/ADT/CondGroupTest.cpp | 947 +++++++++++++++
llvm/unittests/ADT/ConstexprUtilsTest.cpp | 973 ++++++++++++++++
llvm/unittests/ADT/MetaSetTest.cpp | 1267 +++++++++++++++++++++
7 files changed, 5725 insertions(+)
create mode 100644 llvm/include/llvm/ADT/CondGroup.h
create mode 100644 llvm/include/llvm/ADT/ConstexprUtils.h
create mode 100644 llvm/include/llvm/ADT/MetaSet.h
create mode 100644 llvm/unittests/ADT/CondGroupTest.cpp
create mode 100644 llvm/unittests/ADT/ConstexprUtilsTest.cpp
create mode 100644 llvm/unittests/ADT/MetaSetTest.cpp
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::integer_sequence<long, 5000, 8000>>
+/// >
+/// >
+/// );
+/// \endcode
+///
+/// In the example above, the type infrastructure is able to identify two
+/// distinct clusters that are efficiently represented as `MetaBitset`s and two
+/// singletons. One cluster starts at `10,000` and requires one 64-bit word in
+/// the `MetaBitset`. The other cluster starts at `1,000` and also requires a
+/// single 64-bit word in its `MetaBitset`. Finally the two singleton values
+/// are tracked in a seperate `MetaSequenceSet` and checked at the end during
+/// inclusion queries.
+///
+///
+/// \section singletons Singleton Values
+///
+/// In the case where no clusters can be formed, the representation is the same
+/// as the multi-cluster case except the `MetaBitset`s are omitted (i.e. a
+/// single `MetaSequenceSet` is used).
+///
+/// For example:
+/// \code{.cpp}
+/// static constexpr auto Singletons =
+/// cgrp::Literals<10000, 5000, 8000, 20000, 90000>;
+///
+/// static_assert(
+/// std::is_same_v<
+/// std::remove_cv_t<decltype(Singletons)>,
+/// CondGroupMetaSet<
+/// MetaSequenceSet<
+/// std::integer_sequence<
+/// long, 5000, 8000, 10000, 20000, 90000
+/// >
+/// >
+/// >
+/// >);
+/// \endcode
+///
+/// No clusters could be identified in the `Singletons` group, so all the values
+/// get pushed to a `MetaSequenceSet`.
+///
+/// \note That the order of the singleton values will always be in ascending
+/// order. This is because we already applied a constexpr sort to the original
+/// sequence in order to efficiently identify clusters.
+///
+/// \section restrictions Literal type restrictions
+///
+/// Extra type restrictions are applied to literal groups because their values
+/// are effectively down-cast to an integral type in order to enable a `MetaSet`
+/// representation. These restrictions are implied in the tuple-like
+/// `CondGroup` because each element of the group retains its original type.
+///
+///
+/// **Literal group formation:**
+///
+/// - Literals may only be integral or enum types.
+/// - Integral and enum types may be mixed in a group definition, but at most
+/// one enum type may be used.
+/// - The signedness of all types must agree (i.e. the signedness of the
+/// underlying_type for an enum).
+///
+/// These group formation restrictions ensure that the group of values can be
+/// represented by a `MetaSet` as well as prevent incompatible enum types from
+/// inadvertently being cast down to an integral type.
+///
+/// The following are examples of permitted literal groups:
+/// \code{.cpp}
+/// enum class Letter : unsigned { A, B, C, D, E, F };
+///
+/// // All integral values of the same type.
+/// static constexpr auto G0 = cgrp::Literals<5, 10, 15>;
+///
+/// // All integral values, mixed types with same sign.
+/// static constexpr auto G1 = cgrp::Literals<5, short(10), long long(15)>;
+///
+/// // All enum values of the same type.
+/// static constexpr auto G2 = cgrp::Literals<Letter::A, Letter::F>;
+///
+/// // Mixed enum and integral values; same signedness.
+/// static constexpr auto G3 = cgrp::Literals<Letter::D, 0u, 1000ull>;
+/// \endcode
+///
+/// The following are examples of illegal literal groups, which will result in a
+/// compilation error:
+/// \code{.cpp}
+/// enum class Letter : unsigned { A, B, C, D, E, F };
+/// enum Number : unsigned { Zero, One, Two, Three };
+///
+/// // All integral values but mixed signedness.
+/// static constexpr auto G0 = cgrp::Literals<1, 2u, 3>;
+///
+/// // Integral values mixed with enum values differing in signedness of
+/// // underlying type.
+/// static constexpr auto G1 = cgrp::Literals<One, 2>;
+///
+/// // Different enum types.
+/// static constexpr auto G2 = cgrp::Literals<Zero, Letter::B>;
+/// \endcode
+///
+///
+/// \section future_work Future work
+///
+/// \todo Apply binary search to elements in a `MetaSequenceSet` when the
+/// `size()` is sufficiently large.
+///
+/// \todo Make literal groups iterable ranges.
+///
+/// \todo Implement constexpr set operations (`|`, `&`, `-`) for groups of
+/// literals which results in an optimized representation of the resulting
+/// group.
+template <auto... Values>
+inline constexpr auto Literals = cgrp_detail::makeLiteralGroup<Values...>();
+
+/// Group which implements disjunctive equivalence queries.
+///
+/// \tparam GroupT The type of the group to query for membership.
+template <typename GroupT> class AnyOfGroup : public GroupT {
+ template <typename T> using IsOptional = cgrp_detail::IsStdOptional<T>;
+
+public:
+ using GroupT::GroupT;
+
+ constexpr AnyOfGroup(GroupT const &Group) : GroupT(Group) {}
+ constexpr AnyOfGroup(GroupT &&Group) : GroupT(std::move(Group)) {}
+
+ template <typename U, std::enable_if_t<!IsOptional<U>::value, int> = 0>
+ friend constexpr bool operator==(U const &Single, AnyOfGroup const &Group) {
+ return Group.equalDisj(Single);
+ }
+
+ template <typename U, std::enable_if_t<!IsOptional<U>::value, int> = 0>
+ friend constexpr bool operator==(AnyOfGroup const &Group, U const &Single) {
+ return Group.equalDisj(Single);
+ }
+
+ template <typename U, std::enable_if_t<!IsOptional<U>::value, int> = 0>
+ friend constexpr bool operator!=(U const &Single, AnyOfGroup const &Group) {
+ return !Group.equalDisj(Single);
+ }
+
+ template <typename U, std::enable_if_t<!IsOptional<U>::value, int> = 0>
+ friend constexpr bool operator!=(AnyOfGroup const &Group, U const &Single) {
+ return !Group.equalDisj(Single);
+ }
+};
+
+/// Create a specialized comparison `CondGroup` object which can be compared
+/// against a single value for equality (inclusion) or inequality (exclusion).
+/// \relates AnyOfGroup
+///
+/// \section equality Equality: Inclusion in parameters
+///
+/// An equality comparison against the return result of `cgrp::anyOf()` will
+/// return `true` if the specified value is equal to any parameters passed to
+/// `cgrp::anyOf()`; otherwise it will return `false`. For example:
+///
+/// \code{.cpp}
+/// assert( 1 == cgrp::anyOf(5, 4, 3, 2, 1));
+/// assert( (0 == cgrp::anyOf(5, 4, 3, 2, 1)) == false);
+/// \endcode
+///
+/// An equality comparison of an `cgrp::anyOf()` result logically expands to
+/// the following:
+/// \code{.cpp}
+/// if ( VAL == cgrp::anyOf(A, B, C, D, ...))
+///
+/// ==>
+///
+/// auto TMP = VAL;
+/// if ( TMP == A || TMP == B || TMP == C || TMP == D || ... )
+/// \endcode
+///
+///
+/// \section inequality Inequality: Exclusion from parameters
+///
+/// An inequality comparison against the return result of `cgrp::anyOf()` will
+/// result in `false` if the specified value is equal to any parameters passed
+/// to `cgrp::anyOf()`; otherwise it will return `true`.
+///
+/// \code{.cpp}
+/// assert( (1 != cgrp::anyOf(5, 4, 3, 2, 1)) == false);
+/// assert( 0 != cgrp::anyOf(5, 4, 3, 2, 1));
+/// \endcode
+///
+/// An inequality comparison of an `cgrp::anyOf()` result expands to the
+/// following:
+/// \code{.cpp}
+/// if ( VAL != cgrp::anyOf(A, B, C, D, ...))
+///
+/// ==>
+///
+/// auto TMP = VAL;
+/// if ( TMP != A && TMP != B && TMP != C && TMP != D && ... )
+/// \endcode
+///
+/// Users may also combine simple types with `CondGroup` objects as parameters
+/// to `cgrp::anyOf()`. This will still implement short circuiting logic in the
+/// order that values appear. For example:
+///
+/// \code{.cpp}
+/// auto powersOfTwo = llvm::cgrp::makeGroup(1, 2, 4, 8, 16);
+/// auto multiplesOfThree = llvm::cgrp::makeGroup(3, 6, 9, 12, 15);
+///
+/// // 7 does not exist in either group, nor does it match 10 or 11.
+/// assert(7 != cgrp::anyOf(powersOfTwo, multiplesOfThree, 10, 11));
+/// \endcode
+template <typename... Tn> constexpr auto anyOf(Tn &&...Values) {
+ using FirstT = std::decay_t<PackElementT<0, Tn...>>;
+ if constexpr (sizeof...(Values) == 1 &&
+ cgrp_detail::IsCondGroup<FirstT>::value)
+ // A single group was passed in, so avoid wrapping it in another
+ // CondGroup tuple.
+ return AnyOfGroup<FirstT>(std::forward<Tn>(Values)...);
+ else
+ return AnyOfGroup<CondGroup<std::decay_t<Tn>...>>(
+ std::forward<Tn>(Values)...);
+}
+
+namespace cgrp_detail {
+
+template <auto... Values> constexpr auto anyOfLiterals() {
+ using GroupT = typename cgrp_detail::ChooseGroup<Values...>::type;
+
+ return AnyOfGroup<GroupT>();
+}
+
+} // namespace cgrp_detail
+
+/// Instantiate a disjunctive equivalence comparison group which is strongly
+/// optimized for the literals specified as template parameters.
+///
+/// \tparam Values Literal values to add to the `AnyOf` comparison.
+///
+/// Functionally equivalent to:
+/// \code{.cpp}
+/// cgrp::anyOf(cgrp::Literals<Values...>)
+/// \endcode
+///
+/// All of the representation optimizations employed by `cgrp::Literals` are
+/// applied, and the instantiated group is wrapped in an `AnyOfGroup`.
+///
+/// The `AnyOf` group is a specialized comparison `CondGroup` object which can
+/// be compared against a single value for equality (inclusion) or inequality
+/// (exclusion).
+///
+///
+/// \section equality Equality: Inclusion in parameters
+///
+/// An equality comparison against `cgrp::AnyOf<...>` will return `true` if the
+/// specified value is equal to any of the template parameters passed to
+/// `cgrp::AnyOf<...>`; otherwise it will return `false`.
+///
+/// For example:
+/// \code{.cpp}
+/// assert( 1 == cgrp::AnyOf<5, 4, 3, 2, 1>);
+/// assert( (0 == cgrp::AnyOf<5, 4, 3, 2, 1>) == false);
+/// \endcode
+///
+/// An equality comparison of an `cgrp::AnyOf<>` logically expands to the
+/// following:
+/// \code{.cpp}
+/// if ( VAL == cgrp::AnyOf<A, B, C, D, ...>)
+///
+/// ==>
+///
+/// auto TMP = VAL;
+/// if ( TMP == A || TMP == B || TMP == C || TMP == D || ... )
+/// \endcode
+///
+///
+/// \section inequality Inequality: Exclusion from parameters
+///
+/// An inequality comparison against an `cgrp::AnyOf` instantiation will return
+/// in `false` if the specified value is equal to any of the template
+/// parameters; otherwise it will return `true`.
+///
+/// \code{.cpp}
+/// assert( (1 != cgrp::AnyOf<5, 4, 3, 2, 1>) == false);
+/// assert( 0 != cgrp::AnyOf<5, 4, 3, 2, 1>);
+/// \endcode
+///
+/// An inequality comparison of an `cgrp::AnyOf` logically expands to the
+/// following:
+/// \code{.cpp}
+/// if ( VAL != cgrp::AnyOf<A, B, C, D, ...>)
+///
+/// ==>
+///
+/// auto TMP = VAL;
+/// if ( TMP != A && TMP != B && TMP != C && TMP != D && ... )
+/// \endcode
+///
+///
+/// \section restrictions Literal type restrictions
+///
+/// Extra type restrictions are applied to literal groups because their values
+/// are effectively down-cast to an integral type in order to enable a `MetaSet`
+/// representation. These restrictions are implied in the tuple-like
+/// `CondGroup` because each element of the group retains its original type.
+///
+///
+/// **Literal group formation:**
+///
+/// - Literals may only be integral or enum types.
+/// - Integral and enum types may be mixed in a group definition, but at most
+/// one enum type may be used.
+/// - The signedness of all types must agree (i.e. the signedness of the
+/// underlying_type for an enum).
+///
+/// These group formation restrictions ensure that the group of values can be
+/// represented by a `MetaSet` as well as prevent incompatible enum types from
+/// inadvertently being cast down to an integral type.
+///
+/// The following are examples of permitted literal groups:
+/// \code{.cpp}
+/// enum class Letter : unsigned { A, B, C, D, E, F };
+///
+/// // All integral values of the same type.
+/// static constexpr auto G0 = cgrp::Literals<5, 10, 15>;
+///
+/// // All integral values, mixed types with same sign.
+/// static constexpr auto G1 = cgrp::Literals<5, short(10), long long(15)>;
+///
+/// // All enum values of the same type.
+/// static constexpr auto G2 = cgrp::Literals<Letter::A, Letter::F>;
+///
+/// // Mixed enum and integral values; same signedness.
+/// static constexpr auto G3 = cgrp::Literals<Letter::D, 0u, 1000ull>;
+/// \endcode
+///
+/// The following are examples of illegal literal groups, which will result in a
+/// compilation error:
+/// \code{.cpp}
+/// enum class Letter : unsigned { A, B, C, D, E, F };
+/// enum Number : unsigned { Zero, One, Two, Three };
+///
+/// // All integral values but mixed signedness.
+/// static constexpr auto G0 = cgrp::Literals<1, 2u, 3>;
+///
+/// // Integral values mixed with enum values differing in signedness of
+/// // underlying type.
+/// static constexpr auto G1 = cgrp::Literals<One, 2>;
+///
+/// // Different enum types.
+/// static constexpr auto G2 = cgrp::Literals<Zero, Letter::B>;
+/// \endcode
+///
+///
+/// **Literal group comparisons:**
+///
+/// - The single value compared to the group must be convertible to the
+/// underlying integral type used by the `MetaSet` representation.
+/// - If the literals included an enum type, then the single value compared to a
+/// group must be convertible to that enum type.
+///
+/// The above comparison restrictions are meant to make the `CondGroup`
+/// infrastructure both permissive in the sense that integral values can be
+/// comparered to a group derived from an enum class, but not completely
+/// circumvent the type system by disallowing comparisons across multiple enum
+/// types.
+///
+/// For example, the following comparison is permitted:
+/// \code{.cpp}
+/// enum class Letter { A, B, C, D, E, F };
+///
+/// static_assert(1 == cgrp::AnyOf<Letter::A, Letter::B>);
+/// \endcode
+///
+/// However, the following results in a compilation error:
+/// \code{.cpp}
+/// enum class Letter { A, B, C, D, E, F };
+/// enum Number { Zero, One, Two, Three, Four };
+///
+/// // Compile error: incomptible enum types.
+/// static_assert(Letter::A == cgrp::AnyOf<0, One, 2>);
+/// \endcode
+template <auto... Values>
+inline constexpr auto AnyOf = cgrp_detail::anyOfLiterals<Values...>();
+
+/// Compute the union of two literal condition groups at compile time.
+template <typename SetL, typename SetR>
+constexpr auto operator|(CondGroupMetaSet<SetL>, CondGroupMetaSet<SetR>) {
+ return CondGroupMetaSet<
+ MetaSetUnion<SetL, SetR, cgrp_detail::BitsetBitLimit>>();
+}
+
+/// Compute the intersection of two literal condition groups at compile time.
+template <typename SetL, typename SetR>
+constexpr auto operator&(CondGroupMetaSet<SetL>, CondGroupMetaSet<SetR>) {
+ return CondGroupMetaSet<
+ MetaSetIntersection<SetL, SetR, cgrp_detail::BitsetBitLimit>>();
+}
+
+/// Compute the set resulting from subtracting \p SetR from \p SetL at compile
+/// time.
+template <typename SetL, typename SetR>
+constexpr auto operator-(CondGroupMetaSet<SetL>, CondGroupMetaSet<SetR>) {
+ return CondGroupMetaSet<
+ MetaSetMinus<SetL, SetR, cgrp_detail::BitsetBitLimit>>();
+}
+
+} // namespace llvm::cgrp
+
+#endif // LLVM_ADT_CONDGROUP_H
diff --git a/llvm/include/llvm/ADT/ConstexprUtils.h b/llvm/include/llvm/ADT/ConstexprUtils.h
new file mode 100644
index 0000000000000..c0758f54da7e0
--- /dev/null
+++ b/llvm/include/llvm/ADT/ConstexprUtils.h
@@ -0,0 +1,469 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Implement common constexpr utilities including sorting and integer
+/// sequence manipulation.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_CONSTEXPRUTILS_H
+#define LLVM_ADT_CONSTEXPRUTILS_H
+
+#include "llvm/Support/MathExtras.h"
+
+#include <array>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+namespace llvm {
+
+/// This is a tag class returned by meta-programming to indicate an error.
+///
+/// Users may derive from this class to provide more context on the failure.
+///
+/// Using a tag class allows the infrastructure to `static_assert` at higher,
+/// more relevant scopes and generates much more friendly template errors.
+struct CETypeError {};
+
+namespace constexpr_detail {
+
+template <std::size_t I, typename... Tn> struct PackElement {};
+
+template <std::size_t I, typename T0, typename... Tn>
+struct PackElement<I, T0, Tn...> {
+ static_assert(I <= sizeof...(Tn), "Pack index out of bounds.");
+
+ using type = typename PackElement<I - 1, Tn...>::type;
+};
+
+template <typename T0, typename... Tn> struct PackElement<0, T0, Tn...> {
+ using type = T0;
+};
+
+/// An extraction cannot occur from an empty type pack.
+struct PackElementEmptyPack : CETypeError {};
+
+template <std::size_t I> struct PackElement<I> {
+ using type = PackElementEmptyPack;
+};
+
+} // namespace constexpr_detail
+
+template <std::size_t Idx, typename... Tn>
+using PackElementT = typename constexpr_detail::PackElement<Idx, Tn...>::type;
+
+/// Swap values of two specified references at compile-time.
+template <typename T>
+constexpr void ce_swap(T &L, T &R) // NOLINT (readability-identifier-naming)
+{
+ T Temp = L;
+ L = R;
+ R = Temp;
+}
+
+/// Compute the minimum value from a list of compile-time constants.
+///
+/// \tparam RetT Type to return and `static_cast` each value to for comparison.
+///
+/// \param Val0 First value in the list to compute the minimum of.
+/// \param ValN Remaining values in the list to compute the minimum of.
+template <typename RetT, typename T0, typename... Tn>
+constexpr RetT //
+ce_min(T0 Val0, Tn... ValN) { // NOLINT(readability-identifier-naming)
+ RetT Min = static_cast<RetT>(Val0);
+ ((Min = static_cast<RetT>(ValN) < Min ? static_cast<RetT>(ValN) : Min), ...);
+ return Min;
+}
+
+/// Compute the maximum value from a list of compile-time constants.
+///
+/// \tparam RetT Type to return and `static_cast` each value to for comparison.
+///
+/// \param Val0 First value in the list to compute the maximum of.
+/// \param ValN Remaining values in the list to compute the maximum of.
+template <typename RetT, typename T0, typename... Tn>
+constexpr RetT //
+ce_max(T0 Val0, Tn... ValN) { // NOLINT(readability-identifier-naming)
+ RetT Max = static_cast<RetT>(Val0);
+ ((Max = static_cast<RetT>(ValN) > Max ? static_cast<RetT>(ValN) : Max), ...);
+ return Max;
+}
+
+namespace constexpr_detail {
+
+template <typename T, std::size_t N, std::size_t... I>
+constexpr auto toArray(T const (&Arr)[N], std::index_sequence<I...>) {
+ return std::array<std::remove_cv_t<T>, N>{{Arr[I]...}};
+}
+
+} // namespace constexpr_detail
+
+/// Convert a C array to an equivalent `std::array`.
+template <typename T, std::size_t N>
+constexpr auto
+to_array(T const (&Arr)[N]) // NOLINT (readability-identifier-naming)
+{
+ return constexpr_detail::toArray(Arr, std::make_index_sequence<N>());
+}
+
+/// Convert a `std::integer_sequence` to a `std::array` with the same contents.
+template <typename T, T... Vals>
+constexpr auto to_array( // NOLINT (readability-identifier-naming)
+ std::integer_sequence<T, Vals...>) {
+ return std::array<T, sizeof...(Vals)>{{Vals...}};
+}
+
+namespace constexpr_detail {
+
+template <typename IterT, typename LessT>
+constexpr void bubbleSort(IterT B, IterT E, LessT const &Less) {
+ for (auto Last = E, ELast = std::next(B); Last != ELast; --Last) {
+ bool Swapped = false;
+
+ for (auto I = ELast; I != Last; ++I) {
+ if (Less(*I, *std::prev(I))) {
+ ce_swap(*I, *std::prev(I));
+ Swapped = true;
+ }
+ }
+
+ // Remaining items are in sorted order if no swaps occurred during the
+ // inner loop.
+ if (!Swapped)
+ return;
+ }
+}
+
+template <typename T, std::size_t N, typename LessT>
+constexpr T &quickSortGetPivot(std::array<T, N> &Arr, std::size_t Low,
+ std::size_t High, LessT const &Less) {
+ std::size_t Middle = Low + (((High - Low) + 1) >> 1);
+ // Use "median of three" (repositioned into the 'High' position) as pivot...
+ if (Less(Arr[High], Arr[Low]))
+ ce_swap(Arr[High], Arr[Low]);
+
+ if (Middle != High) {
+ if (Less(Arr[Middle], Arr[Low]))
+ ce_swap(Arr[Middle], Arr[Low]);
+
+ if (Less(Arr[Middle], Arr[High]))
+ ce_swap(Arr[Middle], Arr[High]);
+ }
+ return Arr[High];
+}
+
+template <typename T, std::size_t N, typename LessT>
+constexpr std::size_t quickSortPartition(std::array<T, N> &Arr, std::size_t Low,
+ std::size_t High, LessT const &Less) {
+ auto &Pivot = quickSortGetPivot(Arr, Low, High, Less);
+
+ std::size_t I = Low;
+ for (std::size_t J = Low; J < High; ++J)
+ if (Less(Arr[J], Pivot))
+ ce_swap(Arr[I++], Arr[J]);
+
+ ce_swap(Arr[I], Arr[High]);
+ return I;
+}
+
+struct QuickSortIndexPair {
+ std::size_t Low = 0, High = 0;
+ constexpr void set(std::size_t L, std::size_t H) { Low = L, High = H; }
+};
+
+template <std::size_t N> class QuickSortStack {
+ std::size_t Size = 0;
+ std::array<QuickSortIndexPair, N> Arr;
+
+public:
+ constexpr bool empty() const { return Size == 0; }
+ constexpr void push(std::size_t L, std::size_t H) { Arr[Size++].set(L, H); }
+ constexpr QuickSortIndexPair pop() { return Arr[--Size]; }
+ constexpr std::size_t availableSlots() const { return N - Size; }
+};
+
+// Use bubble sort on partitions smaller than this size. Bubble will be
+// more efficient on small partitions.
+inline constexpr std::size_t MinQuickSortPartitionSize = 10u;
+
+template <typename T, std::size_t N, typename LessT>
+constexpr void quickSort(std::array<T, N> &Arr, LessT const &Less) {
+ if constexpr (N <= 1)
+ return;
+
+ // The maximum stack depth is O(log N) on average. O(N) is possible in
+ // corner-cases, so the partition loop will fall back to bubble sorting
+ // partitions if the stack is exhausted.
+ constexpr std::size_t StackSize = ConstantLog2<NextPowerOf2(N)>() + 1;
+ constexpr_detail::QuickSortStack<StackSize> Stack;
+
+ Stack.push(0, N - 1);
+
+ while (!Stack.empty()) {
+ auto const [Low, High] = Stack.pop();
+
+ if ((High - Low) + 1u < MinQuickSortPartitionSize ||
+ Stack.availableSlots() < 2u) {
+ bubbleSort(Arr.begin() + Low, Arr.begin() + High + 1, Less);
+ continue;
+ }
+
+ std::size_t const PivotIndex =
+ constexpr_detail::quickSortPartition(Arr, Low, High, Less);
+
+ std::size_t PivotIndexLo = PivotIndex - 1;
+ std::size_t PivotIndexHi = PivotIndex + 1;
+
+ // Pivot is in sorted position; so are any adjacent elements which are
+ // equivalent. Scan past elements which are equal to the pivot before
+ // sub-dividing the current range.
+ while (PivotIndexLo > Low && PivotIndexLo < High &&
+ !Less(Arr[PivotIndexLo], Arr[PivotIndex]))
+ --PivotIndexLo;
+ while (PivotIndexHi < High && !Less(Arr[PivotIndex], Arr[PivotIndexHi]))
+ ++PivotIndexHi;
+
+ // Push right sub-array boundaries to the Stack if it exists
+ if (High > PivotIndexHi)
+ Stack.push(PivotIndexHi, High);
+
+ // Push left sub-array boundaries to the Stack if it exists
+ if (Low < PivotIndexLo && PivotIndexLo < N)
+ Stack.push(Low, PivotIndexLo);
+ }
+}
+
+} // namespace constexpr_detail
+
+/// Iterative sort implementation.
+///
+/// \param[in,out] Arr Array of elements to sort in-place.
+/// \param Less Less-than comparison functor which compares two `T`s.
+template <typename T, std::size_t N, typename LessT>
+constexpr void ce_sort_inplace( // NOLINT (readability-identifier-naming)
+ std::array<T, N> &Arr, LessT const &Less) {
+ if constexpr (N >= constexpr_detail::MinQuickSortPartitionSize)
+ constexpr_detail::quickSort(Arr, Less);
+ else if (N > 1)
+ // Avoid instantiating the quickSort stack if we won't even parition
+ // elements once.
+ constexpr_detail::bubbleSort(Arr.begin(), Arr.end(), Less);
+}
+
+/// Iterative sort implementation.
+///
+/// \param[in,out] Arr Array of elements to sort in-place.
+template <typename T, std::size_t N>
+constexpr void
+ce_sort_inplace(std::array<T, N> &Arr) // NOLINT (readability-identifier-naming)
+{
+ constexpr auto Less = [](T const &L, T const &R) constexpr { return L < R; };
+ ce_sort_inplace(Arr, Less);
+}
+
+/// Iterative sort implementation.
+///
+/// The sort implementation is chosen at compile-time based on the size of the
+/// specified array.
+///
+/// \param ArrIn Input sequence to sort.
+/// \param Less Less-than comparison functor which compares two `T`s.
+///
+/// \return A `std::array` of elements in ascending order according to the
+/// specified \p Less comparison.
+template <typename T, std::size_t N, typename LessT>
+constexpr std::array<std::remove_cv_t<T>, N>
+ce_sort( // NOLINT (readability-identifier-naming)
+ T const (&ArrIn)[N], LessT const &Less) {
+ auto Arr = to_array(ArrIn);
+ ce_sort_inplace(Arr, Less);
+ return Arr;
+}
+
+/// Iterative sort implementation.
+///
+/// The sort implementation is chosen at compile-time based on the size of the
+/// specified array.
+///
+/// \param ArrIn Input sequence to sort.
+///
+/// \return A `std::array` of elements in ascending order according based on the
+/// `operator<()` associated with `T`.
+template <typename T, std::size_t N>
+constexpr std::array<std::remove_cv_t<T>, N>
+ce_sort(T const (&ArrIn)[N]) // NOLINT (readability-identifier-naming)
+{
+ auto Arr = to_array(ArrIn);
+ ce_sort_inplace(Arr);
+ return Arr;
+}
+
+namespace constexpr_detail {
+
+template <typename T, T... Is> class SortLiteralsImpl {
+ static constexpr auto makeSortedArray() {
+ std::array<T, sizeof...(Is)> Arr{{Is...}};
+ ce_sort_inplace(Arr);
+ return Arr;
+ }
+
+ // Helper to reconstruct integer_sequence from the sorted array
+ template <std::size_t... Idxs>
+ static constexpr auto makeSortedSequence(std::index_sequence<Idxs...>) {
+ constexpr auto SortedArray = makeSortedArray();
+ return std::integer_sequence<T, SortedArray[Idxs]...>{};
+ }
+
+public:
+ using type =
+ decltype(makeSortedSequence(std::make_index_sequence<sizeof...(Is)>()));
+};
+
+} // namespace constexpr_detail
+
+/// Create a sorted `std::integer_sequence` from a list of literals.
+///
+/// \tparam T Integral type of output `std::integer_sequence` as well as all
+/// the specified literals.
+/// \tparam Is List of literals to sort.
+template <typename T, T... Is>
+using SortLiterals =
+ typename constexpr_detail::SortLiteralsImpl<T, Is...>::type;
+
+namespace constexpr_detail {
+
+template <typename SeqT> class SortSequenceImpl {};
+
+template <typename T, T... Is>
+class SortSequenceImpl<std::integer_sequence<T, Is...>> {
+public:
+ using type = typename SortLiteralsImpl<T, Is...>::type;
+};
+
+} // namespace constexpr_detail
+
+/// Create a sorted `std::integer_sequence` from a specified
+/// `std::integer_sequence`.
+///
+/// \tparam SeqT `std::integer_sequence` which specifies the literals to be
+/// sorted.
+template <typename SeqT>
+using SortSequence = typename constexpr_detail::SortSequenceImpl<SeqT>::type;
+
+namespace constexpr_detail {
+
+template <std::size_t First, typename T, std::size_t N, std::size_t... Idx>
+constexpr auto ce_slice_impl( // NOLINT (readability-identifier-naming)
+ std::array<T, N> const &Arr, std::index_sequence<Idx...>) {
+ return std::array<T, sizeof...(Idx)>{{Arr[First + Idx]...}};
+}
+
+} // namespace constexpr_detail
+
+/// Create a `std::array` representing a slice of an input `std::array`.
+///
+/// \tparam First Index of the first element of the output slice.
+/// \tparam Len Number of elements int the output slice.
+///
+/// \param Arr Array to slice from.
+///
+/// \return A new `std::array` of length `Len` representing the specified slice
+/// of \p Arr.
+template <std::size_t First, std::size_t Len, typename T, std::size_t N>
+constexpr auto
+ce_slice(std::array<T, N> const &Arr) // NOLINT (readability-identifier-naming)
+{
+ static_assert(First + Len <= N, "End of slice is out of bounds.");
+ return constexpr_detail::ce_slice_impl<First>(
+ Arr, std::make_index_sequence<Len>());
+}
+
+namespace constexpr_detail {
+
+template <std::size_t First, std::size_t Len, typename T, T... Is>
+class SliceLiteralsImpl {
+ static_assert(First + Len <= sizeof...(Is), "End of slice is out of bounds.");
+
+ template <std::size_t... Idxs>
+ static constexpr auto makeSlicedSequence(std::index_sequence<Idxs...>) {
+ constexpr std::array<T, sizeof...(Is)> InArr{{Is...}};
+ return std::integer_sequence<T, InArr[First + Idxs]...>{};
+ }
+
+public:
+ using type = decltype(makeSlicedSequence(std::make_index_sequence<Len>()));
+};
+
+template <std::size_t First, typename T, T... Is>
+class SliceLiteralsImpl<First, 0, T, Is...> {
+ static_assert(First <= sizeof...(Is));
+
+public:
+ using type = std::integer_sequence<T>;
+};
+
+} // namespace constexpr_detail
+
+/// Create a `std::index_sequence` representing a slice of list of literals.
+///
+/// \tparam First Index of the first element of the output slice.
+/// \tparam Len Number of elements int the output slice.
+/// \tparam T Type of the literals being sliced.
+/// \tparam Is List of literals to slice.
+template <std::size_t First, std::size_t Len, typename T, T... Is>
+using SliceLiterals =
+ typename constexpr_detail::SliceLiteralsImpl<First, Len, T, Is...>::type;
+
+namespace constexpr_detail {
+
+template <std::size_t First, std::size_t Len, typename SeqT>
+class SliceSequenceImpl {};
+
+template <std::size_t First, std::size_t Len, typename T, T... Is>
+class SliceSequenceImpl<First, Len, std::integer_sequence<T, Is...>> {
+public:
+ using type = SliceLiterals<First, Len, T, Is...>;
+};
+
+} // namespace constexpr_detail
+
+/// Generate a `std::integer_sequence` representing a sub-range of a specified
+/// `std::integer_sequence`.
+///
+/// \tparam First Index of the first element of the output slice.
+/// \tparam Len Number of elements int the output slice.
+/// \tparam SeqT `std::integer_sequence` to slice from.
+template <std::size_t First, std::size_t Len, typename SeqT>
+using SliceSequence =
+ typename constexpr_detail::SliceSequenceImpl<First, Len, SeqT>::type;
+
+namespace constexpr_detail {
+
+template <auto Val, typename SeqT> class PushBackSequenceImpl {};
+
+template <auto Val, typename T, T... Is>
+class PushBackSequenceImpl<Val, std::integer_sequence<T, Is...>> {
+public:
+ using type = std::integer_sequence<T, Is..., T(Val)>;
+};
+
+} // namespace constexpr_detail
+
+/// Add an integer value to the end of an `std::integer_sequence`.
+///
+/// \tparam SeqT `std::integer_sequence` to append to.
+/// \tparam Val Integral value to append to \p SeqT.
+template <typename SeqT, auto Val>
+using PushBackSequence =
+ typename constexpr_detail::PushBackSequenceImpl<Val, SeqT>::type;
+
+} // namespace llvm
+
+#endif // LLVM_ADT_CONSTEXPRUTILS_H
diff --git a/llvm/include/llvm/ADT/MetaSet.h b/llvm/include/llvm/ADT/MetaSet.h
new file mode 100644
index 0000000000000..49d3719511158
--- /dev/null
+++ b/llvm/include/llvm/ADT/MetaSet.h
@@ -0,0 +1,1083 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 template meta-programming set representations for use in
+/// `constexpr` expressions.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ADT_METASET_H
+#define LLVM_ADT_METASET_H
+
+#include "llvm/ADT/ConstexprUtils.h"
+#include "llvm/ADT/bit.h"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+#include <utility>
+
+namespace llvm {
+
+/// Max number of words allowed in a MetaBitset.
+///
+/// This limit prevents unbounded host compile-time / space in .rodata.
+static constexpr size_t MetaBitsetWordLimit = 512;
+
+struct MetaSetTag {};
+struct MetaSetDuplicateElemsTag {};
+struct MetaBitsetTag : MetaSetTag {};
+struct MetaSequenceSetTag : MetaSetTag, MetaSetDuplicateElemsTag {};
+struct MetaSparseBitsetTag : MetaSetTag {};
+
+template <typename T>
+inline constexpr bool IsMetaSet = std::is_base_of_v<MetaSetTag, T>;
+
+template <typename T>
+inline constexpr bool IsMetaBitset = std::is_base_of_v<MetaBitsetTag, T>;
+
+template <typename T>
+inline constexpr bool IsMetaSequenceSet =
+ std::is_base_of_v<MetaSequenceSetTag, T>;
+
+template <typename T>
+inline constexpr bool IsMetaSparseBitset =
+ std::is_base_of_v<MetaSparseBitsetTag, T>;
+
+template <typename T>
+inline constexpr bool IsMetaSetWithDuplicateElements =
+ std::is_base_of_v<MetaSetDuplicateElemsTag, T>;
+
+namespace metaset_detail {
+
+template <typename PosT, PosT Offset> struct TypeInfo {
+ static_assert(std::is_integral_v<PosT>,
+ "The position type of a `MetaBitset` must be integral.");
+
+ using WordT = uint64_t;
+ using UPosT = std::make_unsigned_t<PosT>;
+ using WordIndexT = UPosT;
+
+ static constexpr UPosT WordNBits = 64;
+ static constexpr UPosT IdxShift = 6; // log2(64)
+
+ class NormalizedT {
+ UPosT NormalizedPos;
+
+ constexpr NormalizedT(PosT Pos) : NormalizedPos(UPosT(Pos - Offset)) {}
+
+ // Allow TypeInfo access to the constructor.
+ friend struct TypeInfo;
+
+ public:
+ constexpr UPosT wordIndex() const { return NormalizedPos >> IdxShift; }
+
+ constexpr WordT bitMask() const {
+ constexpr UPosT BitIdxMask = (UPosT(1) << IdxShift) - UPosT(1);
+ return WordT(1) << (NormalizedPos & BitIdxMask);
+ }
+ };
+
+ static constexpr NormalizedT normalizePos(PosT Pos) { return Pos; }
+};
+
+} // namespace metaset_detail
+
+/// Represent a set of integral values using a dense bitset representation.
+///
+/// The `MetaBitset` has no storage on the class itself, but it refers to a
+/// `static constexpr` array of words that represent the elements in the set.
+///
+/// This class must be built with helper type aliases. See `MakeMetaBitset`
+/// and `MakeMetaBitsetFromSequence`. If converting a sorted
+/// `std::integer_sequence` to a `MetaBitset`, use
+/// `MakeMetaBitsetFromSortedSequence` to save some build time.
+///
+/// \tparam PosT Integral or enum type used to represent the position / offset
+/// in the bitset.
+/// \tparam Offset Adjustment applied to all positions in the bitset, thus
+/// enabling efficient representation of clusters of large numbers.
+/// \tparam Words The unsigned words whose bits represent the set.
+template <typename PosT, PosT Offset, uint64_t... Words>
+class MetaBitset : metaset_detail::TypeInfo<PosT, Offset>,
+ public MetaBitsetTag {
+ using WordT = typename MetaBitset::TypeInfo::WordT;
+ using UPosT = typename MetaBitset::TypeInfo::UPosT;
+
+ static constexpr std::array<WordT, sizeof...(Words)> WordArr{{Words...}};
+
+ static_assert(WordArr.size() == 0 || WordArr[0] != 0ULL,
+ "A MetaBitset is malformed if its low-order word is zero.");
+
+public:
+ using value_type = PosT;
+
+public:
+ constexpr MetaBitset() {}
+
+ static constexpr std::size_t count() { return (0u + ... + popcount(Words)); }
+
+ static constexpr bool empty() { return sizeof...(Words) == 0; }
+
+ /// Determine whether \p Pos is equivalent to position with a `1` in the
+ /// bitset.
+ ///
+ /// \return `true` if \p Pos is in the set; `false` otherwise.
+ ///
+ /// \note Complexity = `O(1)`.
+ template <typename T> static constexpr bool contains(T Pos) {
+ auto NPos = MetaBitset::normalizePos(PosT(Pos));
+
+ UPosT Idx = NPos.wordIndex();
+ if (Idx >= sizeof...(Words))
+ return false;
+
+ return WordArr[Idx] & NPos.bitMask();
+ }
+
+ static constexpr auto to_array() // NOLINT (readability-identifier-naming)
+ {
+ return toArrayImpl<count()>();
+ }
+
+ static constexpr auto
+ to_sorted_array() // NOLINT (readability-identifier-naming)
+ {
+ return to_array();
+ }
+
+private:
+ template <std::size_t N> static constexpr std::array<PosT, N> toArrayImpl() {
+ std::array<PosT, N> Arr{{}};
+
+ std::size_t SeqIdx = 0;
+ for (std::size_t I = 0, E = WordArr.size(); I != E; ++I) {
+ uint64_t Word = WordArr[I];
+ std::size_t WordBaseIdx = Offset + (I << 6);
+
+ std::size_t B = countr_zero_constexpr(Word);
+ while (B < MetaBitset::TypeInfo::WordNBits) {
+ Arr[SeqIdx++] = WordBaseIdx + B;
+ Word &= ~(1ULL << B);
+ B = countr_zero_constexpr(Word);
+ }
+ }
+ return Arr;
+ }
+
+ template <std::size_t... I>
+ static constexpr auto toSequenceImpl(std::index_sequence<I...> IdxSeq) {
+ constexpr auto SeqArr = toArrayImpl<IdxSeq.size()>();
+ return std::integer_sequence<PosT, SeqArr[I]...>();
+ }
+
+public:
+ using sequence_type =
+ decltype(toSequenceImpl(std::make_index_sequence<count()>()));
+ using sorted_sequence_type = sequence_type;
+};
+
+/// Compute the number of words a `MetaBitset` would need to represent a set
+/// with the specified \p MinVal and \p MaxVal.
+///
+/// \tparam MinVal The minimum value in the set to be represented as a
+/// `MetaBitset`.
+/// \tparam MaxVal The maximum value in the set to be represented as a
+/// `MetaBitset`.
+template <typename PosT, PosT MinVal, PosT MaxVal>
+inline constexpr size_t MetaBitsetNumWordsDetailed =
+ (size_t(MaxVal - MinVal) + 64U) / 64U;
+
+namespace metaset_detail {
+
+template <typename PosT, PosT MinVal, PosT MaxVal, PosT... Values>
+struct MakeMetaBitsetImpl {
+ using TypeInfo = metaset_detail::TypeInfo<PosT, MinVal>;
+
+ static constexpr size_t NumWords =
+ MetaBitsetNumWordsDetailed<PosT, MinVal, MaxVal>;
+
+ static_assert(NumWords < MetaBitsetWordLimit,
+ "MetaBitset exceeds word limit (llvm::MetaBitsetWordLimit).");
+
+ static constexpr auto makeWordsArray() {
+ constexpr std::size_t NumValues = sizeof...(Values);
+ constexpr std::array<PosT, NumValues> BitPositions{
+ {static_cast<PosT>(Values)...}};
+ std::array<uint64_t, NumWords> Words{};
+ for (PosT BitPos : BitPositions) {
+ auto NPos = TypeInfo::normalizePos(BitPos);
+ Words[NPos.wordIndex()] |= NPos.bitMask();
+ }
+ return Words;
+ }
+
+ template <size_t... Is>
+ static constexpr auto makeBitset(std::index_sequence<Is...>) {
+ constexpr auto WordsArr = makeWordsArray();
+ return MetaBitset<PosT, MinVal, WordsArr[Is]...>();
+ }
+
+public:
+ using type = decltype(makeBitset(std::make_index_sequence<NumWords>()));
+};
+
+template <typename T> struct MetaBitsetNumWordsImpl;
+
+template <typename PosT, PosT Offset, uint64_t... Words>
+struct MetaBitsetNumWordsImpl<MetaBitset<PosT, Offset, Words...>>
+ : std::integral_constant<size_t, sizeof...(Words)> {};
+
+} // namespace metaset_detail
+
+/// Get the number of words used to represent a `MetaBitset`.
+template <typename MetaBitsetT>
+inline constexpr size_t MetaBitsetNumWords =
+ metaset_detail::MetaBitsetNumWordsImpl<MetaBitsetT>::value;
+
+/// Create a `MetaBitset` containing the specified values with the specified
+/// bounds.
+///
+/// \tparam PosT Bit-position type to apply to the `MetaBitset`.
+/// \tparam MinVal The smallest value represented in the `MetaBitset`.
+/// \tparam MaxVal The largest value represnted in the `MetaBitset`.
+/// \tparam Values Integral or enum values to represent in the resulting
+/// `MetaBitset`.
+///
+/// \note The specified values will be cast to `PosT`, so choose `PosT` with
+/// the potential for truncation / sign-extension in mind.
+///
+/// This type alias should be used in contexts where the min and max values are
+/// already known in the parent context to avoid unnecessary duplication of
+/// computation.
+///
+/// \warning Min and max bouds are not validated. This type alias saves
+/// compile-time in contexts where the min and max are already computed.
+template <typename PosT, PosT MinVal, PosT MaxVal, auto... Values>
+using MakeMetaBitsetDetailed = typename metaset_detail::MakeMetaBitsetImpl<
+ PosT, MinVal, MaxVal, static_cast<PosT>(Values)...>::type;
+
+namespace metaset_detail {
+
+template <typename PosT, auto... Values> struct MakeMetaBitsetFromValues;
+
+/// Emtpy value set specialization.
+template <typename PosT> struct MakeMetaBitsetFromValues<PosT> {
+ using type = MetaBitset<PosT, 0>;
+};
+
+/// At least one value specialization.
+template <typename PosT, auto Value0, auto... ValueN>
+struct MakeMetaBitsetFromValues<PosT, Value0, ValueN...> {
+private:
+ static constexpr PosT Min = ce_min<PosT>(Value0, ValueN...);
+ static constexpr PosT Max = ce_max<PosT>(Value0, ValueN...);
+
+ static_assert(MetaBitsetNumWordsDetailed<PosT, Min, Max> <
+ MetaBitsetWordLimit,
+ "MetaBitset exceeds word limit (llvm::MetaBitsetWordLimit).");
+
+public:
+ using type = MakeMetaBitsetDetailed<PosT, Min, Max, Value0, ValueN...>;
+};
+
+} // namespace metaset_detail
+
+/// Create a `MetaBitset` containing the specified values.
+///
+/// \tparam PosT Bit-position type to apply to the `MetaBitset`.
+/// \tparam Values Integral or enum values to represent in the resulting
+/// `MetaBitset`.
+///
+/// \note The specified values will be cast to `PosT`, so choose this type with
+/// the potential for truncation / sign-extension in mind.
+///
+/// The max and min of the specified values will be computed in order to
+/// determine the size and offset of the resulting `MetaBitset`. If this
+/// information is already available, use `MakeMetaBitsetDetailed`.
+template <typename PosT, auto... Values>
+using MakeMetaBitset =
+ typename metaset_detail::MakeMetaBitsetFromValues<PosT, Values...>::type;
+
+namespace metaset_detail {
+
+template <typename SeqT> class MakeMetaBitsetFromSequenceImpl {};
+
+template <typename PosT, PosT... Values>
+class MakeMetaBitsetFromSequenceImpl<std::integer_sequence<PosT, Values...>> {
+public:
+ using type = typename MakeMetaBitsetFromValues<PosT, Values...>::type;
+};
+
+template <typename SortedSeqT> class MakeMetaBitsetFromSortedSequenceImpl {};
+
+/// Empty integer sequence specialization.
+template <typename PosT>
+class MakeMetaBitsetFromSortedSequenceImpl<std::integer_sequence<PosT>> {
+public:
+ using type = MetaBitset<PosT, 0>;
+};
+
+/// One or more values in the integer sequence specialization.
+template <typename PosT, PosT Val0, PosT... ValN>
+class MakeMetaBitsetFromSortedSequenceImpl<
+ std::integer_sequence<PosT, Val0, ValN...>> {
+ // The values in the sequence are sorted, so the first and last are min
+ // and max respectively.
+ static constexpr PosT MinVal = Val0;
+ // A comma-expression evaluates to the sub-expression after the last comma.
+ static constexpr PosT MaxVal = (Val0, ..., ValN);
+
+public:
+ using type = MakeMetaBitsetDetailed<PosT, MinVal, MaxVal, Val0, ValN...>;
+};
+
+} // namespace metaset_detail
+
+/// Create a `MetaBitset` containing values taken from a
+/// `std::integer_sequence`.
+/// \relates MetaBitset
+///
+/// \tparam SeqT `std::integer_sequence` of values to add to the generated
+/// `MetaBitset`.
+template <typename SeqT>
+using MakeMetaBitsetFromSequence =
+ typename metaset_detail::MakeMetaBitsetFromSequenceImpl<SeqT>::type;
+
+/// Create a `MetaBitset` containing values taken from a sorted
+/// `std::integer_sequence`.
+/// \relates MetaBitset
+///
+/// \tparam SortedSeqT Pre-sorted `std::integer_sequence` of values to add to
+/// the generated `MetaBitset` type.
+template <typename SortedSeqT>
+using MakeMetaBitsetFromSortedSequence =
+ typename metaset_detail::MakeMetaBitsetFromSortedSequenceImpl<
+ SortedSeqT>::type;
+
+template <typename SeqT> class MetaSequenceSet {};
+
+/// Represent a set of values with a `std::integer_sequence`.
+///
+/// The elements of the sequence need not be sorted.
+template <typename T, T... Values>
+class MetaSequenceSet<std::integer_sequence<T, Values...>>
+ : public MetaSequenceSetTag {
+public:
+ using value_type = T;
+ using sequence_type = std::integer_sequence<T, Values...>;
+ using sorted_sequence_type = SortSequence<sequence_type>;
+
+public:
+ constexpr MetaSequenceSet() {}
+
+ /// Determine whether \p Val is equivalent to an element in the sequence.
+ ///
+ /// \return `true` if \p Val is in the set; `false` otherwise.
+ ///
+ /// \note Complexity = `O(n)` in sequence length.
+ template <typename U> static constexpr bool contains(U const &Val) {
+ if constexpr (sizeof...(Values) == 0)
+ return false;
+ else {
+ T CastVal(static_cast<T>(Val));
+ return (... || (CastVal == Values));
+ }
+ }
+
+ static constexpr std::size_t count() { return sequence_type::size(); }
+
+ static constexpr bool empty() { return sequence_type::size() == 0; }
+
+ static constexpr auto to_array() // NOLINT (readability-identifier-naming)
+ {
+ return llvm::to_array(sequence_type());
+ }
+
+ static constexpr auto
+ to_sorted_array() // NOLINT (readability-identifier-naming)
+ {
+ auto Arr = to_array();
+ ce_sort_inplace(Arr);
+ return Arr;
+ }
+};
+
+namespace metaset_detail {
+
+template <typename... MetaSetN> struct ToArrayImpl {};
+
+template <typename SeqT, typename... MetaBitsetN>
+struct ToArrayImpl<MetaSequenceSet<SeqT>, MetaBitsetN...> {};
+
+} // namespace metaset_detail
+
+/// Build a `MetaSequenceSet` from a list of integral or enum literals.
+///
+/// \tparam T Integral or enum type of the sequence set to generate.
+/// \tparam Values Literals to add to the sequence set.
+template <typename T, auto... Values>
+using MakeMetaSequenceSet =
+ MetaSequenceSet<std::integer_sequence<T, T(Values)...>>;
+
+/// A meta-programming set composed from some number of `MetaBitset`s and
+/// possibly one `MetaSequenceSet`.
+///
+/// The `MetaSparseSet` is created from a sequence of literal integral or enum
+/// values using the helper type aliases. See `MakeMetaSparseSet` and
+/// `MakeMetaSparseSetFromSequence`.
+///
+/// If created using the helper type aliases, the parititoning algorithm is as
+/// follows:
+/// \code
+/// // Determine the maximal slice length of `SortedSeq` starting at
+/// // `StartIdx` which can be represented by a `MetaBitset` under the max word
+/// // length.
+/// //
+/// // If the slice length is too sort, then return 1 to indicate a singleton
+/// // value at `StartIdx`.
+/// getSliceLen(SortedSeq, StartIdx) -> Integer
+///
+/// // Parition a sequence of values into clusters / slices (MetaBitsets) and
+/// // potentially a set of singleton values (MetaSequenceSet).
+/// partition(Values...):
+/// SortedSeq = sort(Value...)
+/// ValueIndex = 0
+/// while ValueIndex < SortedSequence.size():
+/// Len = getSliceLen(SortedSeq, ValueIndex)
+/// if Len == 1:
+/// // Slice is too small, more efficiently represented as a member of a
+/// // MetaSequenceSet.
+/// Singletons.push_back(SortedSeq[])
+/// else:
+/// // Record a slice to represent the current cluster of values.
+/// Slices.push_back({ValueIndex, Len})
+/// ValueIndex += Len
+/// \endcode
+///
+/// The resulting `MetaSparseBitset` will have its slices / clusters represented
+/// as disjoint `MetaBitset`s in ascending order of start offset followed by a
+/// set of singletons represented by a single `MetaSequenceSet`.
+///
+/// Both the clusters and the trailing set of singletons are optional. If all
+/// values are represented by slices / clusters, then the trailing singletons
+/// set will be omitted. Conversely, if no values can be paritioned into
+/// clusters then only the `MetaSequenceSet` will be present.
+///
+/// \note Elements of the trailing sequence set will always be sorted.
+///
+/// \todo Apply binary search to elements of the sorted trailing sequence set
+/// when `contains()` is instantiated if the sequence is sufficiently large.
+template <typename... MetaSetN>
+class MetaSparseBitset : public MetaSparseBitsetTag {
+ static_assert(sizeof...(MetaSetN) != 0);
+ using MetaSet0 = PackElementT<0, MetaSetN...>;
+
+public:
+ using value_type = typename MetaSet0::value_type;
+
+public:
+ constexpr MetaSparseBitset() {}
+
+ static constexpr std::size_t count() {
+ return (MetaSetN::count() + ... + 0u);
+ }
+
+ static constexpr bool empty() {
+ if constexpr (sizeof...(MetaSetN) == 1)
+ // Expect the empty state of a MetaSparseBitset to be a single empty
+ // MetaSequenceSet.
+ return (MetaSetN::empty(), ...);
+ else
+ // The set is assumed to be non-empty if there are multiple sets in the
+ // type pack.
+ return false;
+ }
+
+ /// Determine whether \p Val is equivalent to an element in the set.
+ ///
+ /// \return `true` if \p Val is in the set; `false` otherwise.
+ ///
+ /// \note Complexity = `O(c + s)`; where c = the number of clusters and s =
+ /// singleton sequence length.
+ template <typename U> static constexpr bool contains(U const &Val) {
+ return (... || MetaSetN::contains(Val));
+ }
+
+ static constexpr auto to_array() // NOLINT (readability-identifier-naming)
+ {
+ return toArrayImpl<count()>();
+ }
+
+ static constexpr auto
+ to_sorted_array() // NOLINT (readability-identifier-naming)
+ {
+ return toSortedArrayImpl<count()>();
+ }
+
+private:
+ /// Merge the sets in the order they appear in the type pack.
+ template <std::size_t N>
+ static constexpr std::array<value_type, N> toArrayImpl() {
+ std::array<value_type, N> MergedArr{};
+
+ auto FillFrom = [](auto Set, auto MergePos) constexpr {
+ for (auto Val : Set.to_array())
+ *MergePos++ = Val;
+ return MergePos;
+ };
+
+ auto I = MergedArr.begin();
+ ((I = FillFrom(MetaSetN(), I)), ...);
+
+ return MergedArr;
+ }
+
+ template <std::size_t N>
+ static constexpr std::array<value_type, N> toSortedArrayImpl() {
+ using LastSetT = PackElementT<sizeof...(MetaSetN) - 1, MetaSetN...>;
+ if constexpr (IsMetaSequenceSet<LastSetT>) {
+ using MetaSeqT = LastSetT;
+ std::array<value_type, N> MergedArr{};
+
+ constexpr auto SeqArr = MetaSeqT::to_array();
+ auto SeqPos = SeqArr.begin();
+ auto SeqE = SeqArr.end();
+
+ auto MergeFrom = [&](auto BitSet, auto MergePos) constexpr {
+ using BitsetT =
+ std::remove_cv_t<std::remove_reference_t<decltype(BitSet)>>;
+ if constexpr (!std::is_same_v<MetaSeqT, BitsetT>) {
+ for (auto Val : BitSet.to_array()) {
+ while (SeqPos != SeqE && *SeqPos < Val)
+ *MergePos++ = *SeqPos++;
+ *MergePos++ = Val;
+ }
+ }
+ return MergePos;
+ };
+
+ auto I = MergedArr.begin();
+ ((I = MergeFrom(MetaSetN(), I)), ...);
+
+ while (SeqPos != SeqE)
+ *I++ = *SeqPos++;
+
+ return MergedArr;
+ } else {
+ // The type pack is an ordered list of MetaBitsets; each containing
+ // elements in sorted order. A simple forward merge results in sorted
+ // elements.
+ return toArrayImpl<N>();
+ }
+ }
+
+ template <std::size_t... I>
+ static constexpr auto toSequenceImpl(std::index_sequence<I...> IdxSeq) {
+ constexpr auto SeqArr = toArrayImpl<IdxSeq.size()>();
+ return std::integer_sequence<value_type, SeqArr[I]...>();
+ }
+
+ template <std::size_t... I>
+ static constexpr auto toSortedSequenceImpl(std::index_sequence<I...> IdxSeq) {
+ constexpr auto SeqArr = toSortedArrayImpl<IdxSeq.size()>();
+ return std::integer_sequence<value_type, SeqArr[I]...>();
+ }
+
+public:
+ using sequence_type =
+ decltype(toSequenceImpl(std::make_index_sequence<count()>()));
+ using sorted_sequence_type =
+ decltype(toSortedSequenceImpl(std::make_index_sequence<count()>()));
+};
+
+namespace metaset_detail {
+
+template <typename SortedSeqT, size_t WordSize> class PartitionSparseBitset {
+ using value_type = typename SortedSeqT::value_type;
+
+ class Partitions {
+ static constexpr size_t MinSliceLen = 3u;
+ static constexpr std::array<value_type, SortedSeqT::size()> SeqArr =
+ to_array(SortedSeqT());
+
+ static constexpr size_t getSliceLen(size_t StartIdx) {
+ auto Bottom = SeqArr[StartIdx];
+ auto SeqStartPos = SeqArr.begin() + StartIdx;
+ auto SeqCheckPos = SeqStartPos + 1u;
+ auto SeqE = SeqArr.end();
+ while (SeqCheckPos != SeqE && size_t(*SeqCheckPos - Bottom) < WordSize)
+ ++SeqCheckPos;
+
+ size_t Len = size_t(SeqCheckPos - SeqStartPos);
+
+ // If slice is too short, return 1 to indicate a singleton at `StartIdx`.
+ return Len < MinSliceLen ? 1u : Len;
+ }
+
+ struct CountsT {
+ size_t NumSingles;
+ size_t NumSlices;
+ };
+
+ static constexpr CountsT buildCounts() {
+ if constexpr (SeqArr.size() == 0u)
+ return {0u, 0u};
+
+ size_t SliceStart = 0;
+ size_t Singles = 0;
+ size_t Slices = 0;
+ while (SliceStart < SeqArr.size()) {
+ size_t Len = getSliceLen(SliceStart);
+ if (Len == 1u)
+ ++Singles;
+ else
+ ++Slices;
+ SliceStart += Len;
+ }
+ return {Singles, Slices};
+ }
+
+ static constexpr auto Counts = buildCounts();
+
+ public:
+ static constexpr size_t numSeqElems() { return Counts.NumSingles; }
+ static constexpr size_t numSlices() { return Counts.NumSlices; }
+
+ private:
+ struct SliceT {
+ size_t Start;
+ size_t Length;
+ };
+
+ struct ParitionSpecT {
+ std::array<value_type, numSeqElems()> Singles;
+ std::array<SliceT, numSlices()> Slices;
+ };
+
+ static constexpr ParitionSpecT buildParitions() {
+ ParitionSpecT PS{};
+
+ size_t SliceIdx = 0u, SinglesIdx = 0u;
+ size_t SliceStart = 0u;
+ while (SliceStart < SeqArr.size()) {
+ size_t Len = getSliceLen(SliceStart);
+ if (Len == 1u)
+ PS.Singles[SinglesIdx++] = SeqArr[SliceStart];
+ else
+ PS.Slices[SliceIdx++] = {SliceStart, Len};
+ SliceStart += Len;
+ }
+ return PS;
+ }
+
+ static constexpr auto PartitionSpec = buildParitions();
+
+ public:
+ static constexpr size_t sliceStart(size_t Idx) {
+ return PartitionSpec.Slices[Idx].Start;
+ }
+
+ static constexpr size_t sliceLen(size_t Idx) {
+ return PartitionSpec.Slices[Idx].Length;
+ }
+
+ static constexpr value_type seqElem(size_t Idx) {
+ return PartitionSpec.Singles[Idx];
+ }
+ };
+
+ template <size_t SliceIdx>
+ using MakeSliceBitset = MakeMetaBitsetFromSortedSequence<
+ SliceSequence<Partitions::sliceStart(SliceIdx),
+ Partitions::sliceLen(SliceIdx), SortedSeqT>>;
+
+ template <size_t... SeqIdx, size_t... SliceIdx>
+ static constexpr auto partition(std::index_sequence<SeqIdx...>,
+ std::index_sequence<SliceIdx...>) {
+
+ if constexpr (Partitions::numSeqElems() == 0u)
+ if constexpr (Partitions::numSlices() == 0u)
+ // Empty input yields and empty sequence set.
+ return MetaSparseBitset<MakeMetaSequenceSet<value_type>>();
+ else
+ // Avoid instantiating the singles sequence set if all values are
+ // represented in bitsets.
+ return MetaSparseBitset<MakeSliceBitset<SliceIdx>...>();
+ else
+ // CondGroupMetaSetParitioned looks for elements in order of the variadic
+ // types. Put the sequence set last in hopes a value hits in one of the
+ // bitsets since this would be cheaper at runtime.
+ return MetaSparseBitset<
+ MakeSliceBitset<SliceIdx>...,
+ MakeMetaSequenceSet<value_type, Partitions::seqElem(SeqIdx)...>>();
+ }
+
+public:
+ using type =
+ decltype(partition(std::make_index_sequence<Partitions::numSeqElems()>(),
+ std::make_index_sequence<Partitions::numSlices()>()));
+};
+
+} // namespace metaset_detail
+
+/// Create a `MetaSparseBitset` containing values taken from a
+/// `std::integer_sequence`.
+///
+/// \tparam SeqT `std::integer_sequence` of values to add to the generated
+/// `MetaBitset`.
+/// \tparam WordSize Maximum number of bits allowed in a `MetaBitset` sub-word
+/// within the sparse bitset.
+///
+/// \p SeqT may be an unsorted sequence. While repeat elements are tolerated,
+/// they do throw off heuristics and may result in an inefficient paritioning.
+template <typename SeqT, size_t WordSize>
+using MakeMetaSparseBitsetFromSequence =
+ typename metaset_detail::PartitionSparseBitset<SortSequence<SeqT>,
+ WordSize>::type;
+
+/// Create a `MetaSparseBitset` containing the specified values.
+///
+/// \tparam PosT Integral or enum type used to represent the position / offset
+/// in the bitset.
+/// \tparam WordSize Maximum number of bits allowed in a `MetaBitset` sub-word
+/// within the sparse bitset.
+/// \tparam Values Values to represent in the resulting `MetaSparseBitset`.
+///
+/// \p Values may be an unsorted sequence. While repeat elements are tolerated,
+/// they do throw off heuristics and may result in an inefficient paritioning.
+template <typename PosT, size_t WordSize, auto... Values>
+using MakeMetaSparseBitset = typename metaset_detail::PartitionSparseBitset<
+ SortLiterals<PosT, PosT(Values)...>, WordSize>::type;
+
+/// Provide a container interface to a MetaSet type.
+///
+/// This class can be used as a mixin for any class accepting a `MetaSet` type
+/// as a template parameter. For example:
+/// \code{.cpp}
+/// template <typename FooSetT>
+/// class Foo : public MetaSetSortedContainer<FooSetT> {
+/// static_assert(IsMetaSet<FooSetT>);
+/// };
+/// \endcode
+template <typename MetaSetT> class MetaSetSortedContainer {
+ static_assert(IsMetaSet<MetaSetT>);
+
+ /// Use the instantiated type cache to ensure the array is only instantiated
+ /// if one of the container-like member functions are called.
+ template <typename T> struct ArrayInstantiator {
+ static constexpr auto Array = T::to_sorted_array();
+ };
+
+public:
+ using size_type = std::size_t;
+ using value_type = typename MetaSetT::value_type;
+
+private:
+ using ArrayT = std::array<value_type, MetaSetT::count()>;
+
+public:
+ using reference = value_type const &;
+ using const_reference = reference;
+ using iterator = typename ArrayT::const_iterator;
+ using const_iterator = iterator;
+ using reverse_iterator = typename ArrayT::const_reverse_iterator;
+ using const_reverse_iterator = reverse_iterator;
+
+public:
+ constexpr MetaSetSortedContainer() {}
+
+ constexpr const_iterator begin() const {
+ return ArrayInstantiator<MetaSetT>().Array.begin();
+ }
+
+ constexpr const_iterator end() const {
+ return ArrayInstantiator<MetaSetT>().Array.end();
+ }
+
+ constexpr const_iterator cbegin() const {
+ return ArrayInstantiator<MetaSetT>().Array.cbegin();
+ }
+
+ constexpr const_iterator cend() const {
+ return ArrayInstantiator<MetaSetT>().Array.cend();
+ }
+
+ constexpr const_reverse_iterator rbegin() const {
+ return ArrayInstantiator<MetaSetT>().Array.rbegin();
+ }
+
+ constexpr const_reverse_iterator rend() const {
+ return ArrayInstantiator<MetaSetT>().Array.rend();
+ }
+
+ constexpr const_reverse_iterator crbegin() const {
+ return ArrayInstantiator<MetaSetT>().Array.crbegin();
+ }
+
+ constexpr const_reverse_iterator crend() const {
+ return ArrayInstantiator<MetaSetT>().Array.crend();
+ }
+
+ constexpr size_type size() const {
+ return ArrayInstantiator<MetaSetT>().Array.size();
+ }
+
+ constexpr const_reference operator[](size_type Idx) const {
+ static_assert(!MetaSetT::empty());
+ return ArrayInstantiator<MetaSetT>().Array[Idx];
+ }
+
+ constexpr const_reference front() const {
+ static_assert(!MetaSetT::empty());
+ return ArrayInstantiator<MetaSetT>().Array.front();
+ }
+
+ constexpr const_reference back() const {
+ static_assert(!MetaSetT::empty());
+ return ArrayInstantiator<MetaSetT>().Array.back();
+ }
+
+ constexpr bool empty() const { return MetaSetT::empty(); }
+};
+
+namespace metaset_detail {
+
+/// Count the number of increments applied; drop any assignments to the
+/// dereferenced type.
+///
+/// This fake iterator is used by the set operation algorithms to determine the
+/// required size of the array to generate at compile-time.
+class CountIterator {
+ std::size_t Count = 0;
+
+ struct FakeReference {
+ template <typename T> constexpr FakeReference &operator=(T const &) {
+ return *this;
+ }
+ };
+
+public:
+ constexpr CountIterator() {}
+
+ constexpr FakeReference operator*() { return {}; }
+
+ constexpr CountIterator &operator++() {
+ ++Count;
+ return *this;
+ }
+
+ constexpr CountIterator operator++(int) {
+ CountIterator Ret(*this);
+ operator++();
+ return Ret;
+ }
+
+ constexpr std::size_t count() const { return Count; }
+};
+
+template <typename Algorithm, std::size_t WordSize> class SetOperation {
+ using value_type = typename Algorithm::value_type;
+ using LeftSetT = typename Algorithm::LeftSetT;
+ using RightSetT = typename Algorithm::RightSetT;
+
+ static constexpr std::size_t ResultSize =
+ Algorithm()(metaset_detail::CountIterator()).count();
+
+ static constexpr auto makeResultArr() {
+ std::array<value_type, ResultSize> ResultArr{};
+ Algorithm()(ResultArr.begin());
+ return ResultArr;
+ }
+
+ template <std::size_t... Idx>
+ static constexpr auto makeResultSequence(std::index_sequence<Idx...>) {
+ constexpr auto ResultArr = makeResultArr();
+ return std::integer_sequence<value_type, ResultArr[Idx]...>();
+ }
+
+ using ResultSequence =
+ decltype(makeResultSequence(std::make_index_sequence<ResultSize>()));
+
+public:
+ using type = typename PartitionSparseBitset<ResultSequence, WordSize>::type;
+};
+
+template <typename L, typename R> struct AlgoBase {
+ using value_type = typename L::value_type;
+ static_assert(
+ std::is_same_v<value_type, typename R::value_type>,
+ "MetaSet operations are only valid on sets with the same `value_type`.");
+
+ using LeftSetT = L;
+ using RightSetT = R;
+
+ static constexpr auto ArrL = L::to_sorted_array();
+ static constexpr auto ArrR = R::to_sorted_array();
+};
+
+template <typename L, typename R> struct UnionAlgo : AlgoBase<L, R> {
+ using UnionAlgo::AlgoBase::ArrL;
+ using UnionAlgo::AlgoBase::ArrR;
+
+ template <typename OutIt> constexpr OutIt operator()(OutIt Out) const {
+ auto I0 = ArrL.begin(), E0 = ArrL.end();
+ auto I1 = ArrR.begin(), E1 = ArrR.end();
+
+ while (I0 != E0) {
+ if (I1 != E1) {
+ if (*I0 == *I1) {
+ *Out++ = *I0++;
+ ++I1;
+ } else if (*I0 < *I1) {
+ *Out++ = *I0++;
+ } else {
+ *Out++ = *I1++;
+ }
+ } else {
+ *Out++ = *I0++;
+ }
+ }
+
+ while (I1 != E1)
+ *Out++ = *I1++;
+
+ return Out;
+ }
+};
+
+template <typename L, typename R> struct IntersectionAlgo : AlgoBase<L, R> {
+ using IntersectionAlgo::AlgoBase::ArrL;
+ using IntersectionAlgo::AlgoBase::ArrR;
+
+ template <typename OutIt> constexpr OutIt operator()(OutIt Out) const {
+ auto I0 = ArrL.begin(), E0 = ArrL.end();
+ auto I1 = ArrR.begin(), E1 = ArrR.end();
+
+ while (I0 != E0 && I1 != E1) {
+ if (*I0 == *I1) {
+ *Out++ = *I0++;
+ ++I1;
+ } else if (*I0 < *I1) {
+ ++I0;
+ } else {
+ ++I1;
+ }
+ }
+
+ return Out;
+ }
+};
+
+template <typename L, typename R> struct MinusAlgo : AlgoBase<L, R> {
+ using MinusAlgo::AlgoBase::ArrL;
+ using MinusAlgo::AlgoBase::ArrR;
+
+ template <typename OutIt> constexpr OutIt operator()(OutIt Out) const {
+ auto I0 = ArrL.begin(), E0 = ArrL.end();
+ auto I1 = ArrR.begin(), E1 = ArrR.end();
+
+ while (I0 != E0 && I1 != E1) {
+ if (*I0 == *I1) {
+ ++I0;
+ ++I1;
+ } else if (*I0 < *I1) {
+ *Out++ = *I0++;
+ } else {
+ ++I1;
+ }
+ }
+
+ // All remaining elements in I0's range do not exist in I1's range.
+ while (I0 != E0)
+ *Out++ = *I0++;
+
+ return Out;
+ }
+};
+
+inline constexpr std::size_t SetOpDefaultWordSize = 512;
+
+} // namespace metaset_detail
+
+/// Compute type resulting from a union of two meta set types.
+///
+/// The resulting type will always be expressed as a `MetaSparseBitset`.
+///
+/// \tparam L Left-hand type to compute the union of.
+/// \tparam R Right-hand type to compute the union of.
+/// \tparam WordSize Sub-word size limit applied to the resulting
+/// `MetaSparseBitset`.
+///
+/// Example usage:
+/// \code{.cpp}
+/// using EvensT = MakeMetaBitset<int, 2, 4, 6, 8, 10>;
+/// using OddsT = MakeMetaBitset<int, 1, 3, 5, 7, 9>;
+///
+/// // Compute the union of `EvensT` and `OddsT`, limiting bitset word-size to
+/// // 128 bits in the output `MetaSparseBitset`.
+/// using Union = MetaSetUnion<EvensT, OddsT, 128>;
+/// \endcode
+template <typename L, typename R,
+ std::size_t WordSize = metaset_detail::SetOpDefaultWordSize>
+using MetaSetUnion =
+ typename metaset_detail::SetOperation<metaset_detail::UnionAlgo<L, R>,
+ WordSize>::type;
+
+/// Compute the type resulting from an intersection of two meta set types.
+///
+/// The resulting type will always be expressed as a `MetaSparseBitset`.
+///
+/// \tparam L Left-hand type to compute the intersection of.
+/// \tparam R Right-hand type to compute the intersection of.
+/// \tparam WordSize Sub-word size limit applied to the resulting
+/// `MetaSparseBitset`.
+///
+/// Example usage:
+/// \code{.cpp}
+/// using EvensT = MakeMetaBitset<int, 2, 4, 6, 8, 10>;
+/// using OneToFiveT = MakeMetaBitset<int, 1, 2, 3, 4, 5>;
+///
+/// // Compute the intersection of `EvensT` and `OneToFiveT`, limiting bitset
+/// // word-size to 128 bits in the output `MetaSparseBitset`.
+/// using Intersection = MetaSetIntersection<EvensT, OneToFiveT, 128>;
+/// \endcode
+template <typename L, typename R,
+ std::size_t WordSize = metaset_detail::SetOpDefaultWordSize>
+using MetaSetIntersection = typename metaset_detail::SetOperation<
+ metaset_detail::IntersectionAlgo<L, R>, WordSize>::type;
+
+/// Compute the type resulting from a subtraction of two meta set types.
+///
+/// The resulting type will always be expressed as a `MetaSparseBitset`.
+///
+/// \tparam L Left-hand type to subtract from.
+/// \tparam R Right-hand type use as the subtrahend.
+/// \tparam WordSize Sub-word size limit applied to the resulting
+/// `MetaSparseBitset`.
+///
+/// Example usage:
+/// \code{.cpp}
+/// using EvensT = MakeMetaBitset<int, 2, 4, 6, 8, 10>;
+/// using OneToFiveT = MakeMetaBitset<int, 1, 2, 3, 4, 5>;
+///
+/// // Compute the difference by subtracting `OneToFiveT` from `EvensT` and,
+/// // limiting bitset word-size to 128 bits in the output `MetaSparseBitset`.
+/// using Difference = MetaSetMinus<EvensT, OneToFiveT, 128>;
+/// \endcode
+template <typename L, typename R,
+ std::size_t WordSize = metaset_detail::SetOpDefaultWordSize>
+using MetaSetMinus =
+ typename metaset_detail::SetOperation<metaset_detail::MinusAlgo<L, R>,
+ WordSize>::type;
+
+} // namespace llvm
+
+#endif // LLVM_ADT_METASET_H
diff --git a/llvm/unittests/ADT/CMakeLists.txt b/llvm/unittests/ADT/CMakeLists.txt
index af503d9b82843..be865a433b654 100644
--- a/llvm/unittests/ADT/CMakeLists.txt
+++ b/llvm/unittests/ADT/CMakeLists.txt
@@ -19,6 +19,8 @@ add_llvm_unittest(ADTTests
CoalescingBitVectorTest.cpp
CombinationGeneratorTest.cpp
ConcurrentHashtableTest.cpp
+ CondGroupTest.cpp
+ ConstexprUtilsTest.cpp
CountCopyAndMove.cpp
DAGDeltaAlgorithmTest.cpp
DeltaAlgorithmTest.cpp
@@ -55,6 +57,7 @@ add_llvm_unittest(ADTTests
LazyAtomicPointerTest.cpp
MappedIteratorTest.cpp
MapVectorTest.cpp
+ MetaSetTest.cpp
PackedVectorTest.cpp
PagedVectorTest.cpp
PointerEmbeddedIntTest.cpp
diff --git a/llvm/unittests/ADT/CondGroupTest.cpp b/llvm/unittests/ADT/CondGroupTest.cpp
new file mode 100644
index 0000000000000..ad1767cdbd7d3
--- /dev/null
+++ b/llvm/unittests/ADT/CondGroupTest.cpp
@@ -0,0 +1,947 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains testing for the condition group infrastructure.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/CondGroup.h"
+#include "gtest/gtest.h"
+
+#include <cstdlib>
+#include <optional>
+#include <type_traits>
+
+using namespace llvm;
+using namespace cgrp;
+
+namespace {
+
+template <typename Iter0, typename Iter1>
+constexpr bool ce_equal( // NOLINT (readability-identifier-naming)
+ Iter0 B0, Iter0 E0, Iter1 B1, Iter1 E1) {
+ for (; B0 != E0; ++B0, ++B1) {
+ if (B1 == E1 || *B0 != *B1)
+ return false;
+ }
+ return B1 == E1;
+}
+
+template <typename R1, typename R2>
+constexpr bool ce_equal( // NOLINT (readability-identifier-naming)
+ R1 const &L, R2 const &R) {
+ return ce_equal(adl_begin(L), adl_end(L), adl_begin(R), adl_end(R));
+}
+
+template <typename Range1, typename Range2>
+constexpr bool ce_requal( // NOLINT (readability-identifier-naming)
+ Range1 const &L, Range2 const &R) {
+ return ce_equal(adl_rbegin(L), adl_rend(L), adl_rbegin(R), adl_rend(R));
+}
+
+namespace cexpr {
+
+template <int Val, bool = (Val == anyOf(1, 2, 3))>
+class OneTwoThreeSimple : public std::false_type {};
+
+template <int Val>
+class OneTwoThreeSimple<Val, true> : public std::true_type {};
+
+template <int Val, bool = (Val == anyOf(makeGroup(1, 2), makeGroup(3)))>
+class OneTwoThreeNested : public std::false_type {};
+
+template <int Val>
+class OneTwoThreeNested<Val, true> : public std::true_type {};
+} // namespace cexpr
+
+enum class Number : int {
+ Zero,
+ One,
+ Two,
+ Three,
+ Four,
+ Five,
+ Six,
+ Seven,
+ Eight,
+ Nine,
+ Ten
+};
+
+enum class Letter : int { A, B, C, D, E, F, G, H, I, J };
+
+enum LetterNonClass : int { A, B, C, D, E, F, G, H, I, J };
+
+enum NumberNonClass : int {
+ Zero,
+ One,
+ Two,
+ Three,
+ Four,
+ Five,
+ Six,
+ Seven,
+ Eight,
+ Nine,
+ Ten
+};
+
+TEST(CondGroupTest, ConstExpr) {
+ using namespace cexpr;
+
+ // Ensure anyOf() can be called in the context of a `static_assert`
+ // condition.
+ static_assert(1 == anyOf(1, 2, 3), "1 is in group {1,2,3}");
+ static_assert(4 != anyOf(1, 2, 3), "4 is not in group {1,2,3}");
+
+ static_assert(1 == anyOf(makeGroup(1, 2, 3)), "1 is in group {1,2,3}");
+ static_assert(9 != anyOf(makeGroup(1, 2, 3)), "9 is not in group {1,2,3}");
+
+ static_assert(1 == anyOf(makeGroup(1, 2, 3)), "1 is in group {1,2,3}");
+ static_assert(9 != anyOf(makeGroup(1, 2, 3)), "9 is not in group {1,2,3}");
+
+ // Ensure that we can instantiate a template which calls anyOf() to derive
+ // the value of an anonymous template parameter.
+ static_assert(OneTwoThreeSimple<0>::value == false,
+ "0 is not in group {1,2,3}");
+ static_assert(OneTwoThreeNested<0>::value == false,
+ "0 is not in group {1,2,3}");
+ static_assert(OneTwoThreeSimple<2>::value == true, "2 is in group {1,2,3}");
+ static_assert(OneTwoThreeNested<1>::value == true, "1 is in group {1,2,3}");
+}
+
+TEST(CondGroupTest, LiteralRepresentation) {
+ // Single MetaBitset representation.
+ {
+ 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>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Fives)>,
+ CondGroupMetaSet<MetaBitset<int, 5, 0x1084210842108421,
+ 0x84210842>>>);
+ static_assert(35 == anyOf(Fives));
+ }
+
+ // Multiple MetaBitsets for clusters (no singletons).
+ {
+ static constexpr auto Clusters =
+ cgrp::Literals<10000, 10002, 10004, 10006, 10008, 1000, 1002, 1004,
+ 1006, 1008>;
+
+ static_assert(
+ std::is_same_v<
+ std::remove_cv_t<decltype(Clusters)>,
+ CondGroupMetaSet<MetaSparseBitset<MetaBitset<int, 1000, 0x155>,
+ MetaBitset<int, 10000, 0x155>>>>);
+ }
+
+ // Multiple MetaBitsets for clusters + MetaSequneceSet for singletons.
+ {
+ static constexpr auto ClustersAndSingletons =
+ 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(ClustersAndSingletons)>,
+ CondGroupMetaSet<MetaSparseBitset<
+ MetaBitset<int, 1000, 0x155>, MetaBitset<int, 10000, 0x155>,
+ MakeMetaSequenceSet<int, 5000, 8000>>>>);
+ }
+
+ // No clusters, all singletons; MetaSequenceSet for all values.
+ {
+ static constexpr auto Singletons =
+ cgrp::Literals<10000, 5000, 8000, 20000, 90000>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Singletons)>,
+ CondGroupMetaSet<MetaSparseBitset<MakeMetaSequenceSet<
+ int, 5000, 8000, 10000, 20000, 90000>>>>);
+ }
+
+ // Small set, no paritioning attempted; MetaSequenceSet with no sorting.
+ {
+ static constexpr auto SmallSet = cgrp::Literals<3, 1>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(SmallSet)>,
+ CondGroupMetaSet<
+ MetaSequenceSet<std::integer_sequence<int, 3, 1>>>>);
+ }
+}
+
+TEST(CondGroupTest, Integers) {
+ static constexpr auto Evens = makeGroup(2, 4, 6, 8, 10);
+ static constexpr auto Odds = makeGroup(1, 3, 5, 7, 9);
+
+ std::decay_t<decltype(Evens)> EvensC(Evens);
+
+ static constexpr auto EvensL = Literals<2, 4, 6, 8, 10>;
+ static_assert(10 == anyOf(EvensL));
+
+ static constexpr auto LargeGap = Literals<128, 129, 131, 381, 382, 383>;
+
+ static_assert(128 == anyOf(LargeGap));
+ static_assert(129 == anyOf(LargeGap));
+ static_assert(130 != anyOf(LargeGap));
+ static_assert(383 == anyOf(LargeGap));
+ static_assert(191 != anyOf(LargeGap));
+ static_assert(192 != anyOf(LargeGap));
+ static_assert(193 != anyOf(LargeGap));
+ static_assert(255 != anyOf(LargeGap));
+ static_assert(256 != anyOf(LargeGap));
+ static_assert(257 != anyOf(LargeGap));
+
+ // Empty group.
+ EXPECT_TRUE(0 != anyOf());
+ EXPECT_TRUE(anyOf() != 0);
+
+ // Mixed group + value compare.
+ EXPECT_TRUE(1 == anyOf(EvensC, 1));
+ EXPECT_TRUE(anyOf(Evens, 1) == 1);
+
+ // Multiple groups.
+ EXPECT_TRUE(1 == anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) == 1);
+
+ // Negative comparison.
+ EXPECT_TRUE(0 != anyOf(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ EXPECT_TRUE(anyOf(1, 2, 3, 4, 5, 6, 7, 8, 9) != 0);
+
+ // Negative comparison of multiple groups.
+ EXPECT_TRUE(0 != anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) != 0);
+}
+
+TEST(CondGroupTest, OptionalIntegers) {
+ using Opt = std::optional<int>;
+
+ auto Evens = makeGroup(Opt(), Opt(2), Opt(4), Opt(6), Opt(8), Opt(10));
+
+ auto Odds = makeGroup(Opt(), Opt(1), Opt(3), Opt(5), Opt(7), Opt(9));
+
+ auto OddsNoNullopt = makeGroup(Opt(1), Opt(3), Opt(5), Opt(7), Opt(9));
+
+ // Empty group.
+ EXPECT_TRUE(Opt(0) != anyOf());
+ EXPECT_TRUE(anyOf() != Opt(0));
+
+ // Mixed group + value compare.
+ EXPECT_TRUE(1 == anyOf(Evens, 1));
+ EXPECT_TRUE(anyOf(Evens, 1) == 1);
+
+ // Multiple groups.
+ EXPECT_TRUE(1 == anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) == 1);
+
+ // Negative comparison.
+ EXPECT_TRUE(0 != anyOf(1, 2, 3, 4, 5, 6, 7, 8, 9));
+ EXPECT_TRUE(anyOf(1, 2, 3, 4, 5, 6, 7, 8, 9) != 0);
+
+ // Negative comparison of multiple groups.
+ EXPECT_TRUE(0 != anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) != 0);
+
+ // std::nullopt should match the default-constructed Opt() in the Evens group.
+ EXPECT_TRUE(std::nullopt == anyOf(Evens));
+
+ // Should be able to compare an Optional to a group containing a std::nullopt.
+ EXPECT_TRUE(Opt(2) == anyOf(std::nullopt, 2));
+ EXPECT_TRUE(anyOf(std::nullopt, 2) == Opt(2));
+
+ EXPECT_FALSE(std::nullopt == anyOf(OddsNoNullopt));
+ EXPECT_FALSE(anyOf(OddsNoNullopt) == std::nullopt);
+
+ EXPECT_TRUE(std::nullopt != anyOf(OddsNoNullopt));
+ EXPECT_TRUE(anyOf(OddsNoNullopt) != std::nullopt);
+}
+
+TEST(CondGroupTest, LiteralsTuple) {
+ EXPECT_TRUE(1268 == (AnyOf<1, 5, 9, 1600, 905, 1268, 2402, 5732>));
+ EXPECT_TRUE(1268 == (anyOf(Literals<1, 5, 9, 1600, 905, 1268, 2402, 5732>)));
+
+ static constexpr auto Group = Literals<1, 5, 9, 1600, 905, 1268, 2402, 5732>;
+ EXPECT_TRUE(1268 == anyOf(Group));
+ EXPECT_FALSE(12 == anyOf(Group));
+
+ {
+ constexpr auto G =
+ Literals<9971U, 9972U, 9973U, 9974U, 9975U, 9976U, 9977U, 9978U, 9985U,
+ 9986U, 9987U, 9988U, 9989U, 9990U, 9991U, 9992U, 9993U, 9994U,
+ 9995U, 10002U, 10003U, 10004U, 10005U, 10006U, 10007U, 10008U,
+ 10009U, 10010U, 10011U, 10012U, 10013U, 9133U, 9134U>;
+
+ static_assert(10009U == anyOf(G));
+ }
+}
+
+namespace implicit_conversion {
+
+struct Foo {
+ // Support implicit conversions from int, unsigned or short.
+ constexpr Foo(int V) : Val(V) {}
+ constexpr Foo(unsigned V) : Val(static_cast<int>(V)) {}
+ constexpr Foo(short V) : Val(static_cast<int>(V)) {}
+
+ constexpr operator int() const { return Val; }
+
+ int Val;
+};
+
+} // namespace implicit_conversion
+
+TEST(CondGroupTest, ImplicitConversions) {
+ using namespace implicit_conversion;
+
+ static constexpr auto Evens =
+ makeGroup(Foo(2), Foo(4U), Foo(short(6)), 8, 10);
+ static constexpr auto Odds =
+ makeGroup(Foo(1), Foo(3), Foo(5U), Foo(short(7)), 9);
+
+ auto NegGroup =
+ makeGroup(1, Foo(short(2)), 3, Foo(4U), short(5), Foo(6), 7, Foo(8), 9);
+
+ //
+ // Single value is `Foo` type.
+ //
+
+ // Empty group.
+ EXPECT_TRUE(Foo(0) != anyOf());
+ EXPECT_TRUE(anyOf() != Foo(0));
+
+ // Mixed group + value compare.
+ EXPECT_TRUE(Foo(1) == anyOf(Evens, 1));
+ EXPECT_TRUE(anyOf(Evens, 1) == Foo(1));
+
+ // Multiple groups.
+ EXPECT_TRUE(Foo(1) == anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) == Foo(1));
+
+ // Negative comparison.
+ EXPECT_TRUE(Foo(0) != anyOf(NegGroup));
+ EXPECT_TRUE(anyOf(NegGroup) != Foo(0));
+
+ // Negative comparison of multiple groups.
+ EXPECT_TRUE(Foo(0) != anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) != Foo(0));
+
+ //
+ // Single value is integral type.
+ //
+
+ // Empty group.
+ EXPECT_TRUE(0 != anyOf());
+ EXPECT_TRUE(anyOf() != 0);
+
+ // Mixed group + value compare.
+ EXPECT_TRUE(1 == anyOf(Evens, 1));
+ EXPECT_TRUE(anyOf(Evens, 1) == 1);
+
+ // Multiple groups.
+ EXPECT_TRUE(1 == anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) == 1);
+
+ // Negative comparison.
+ EXPECT_TRUE(0 != anyOf(NegGroup));
+ EXPECT_TRUE(anyOf(NegGroup) != 0);
+
+ // Negative comparison of multiple groups.
+ EXPECT_TRUE(0 != anyOf(Evens, Odds));
+ EXPECT_TRUE(anyOf(Evens, Odds) != 0);
+}
+
+TEST(CondGroupTest, EnumLiterals) {
+ using namespace implicit_conversion;
+
+ //
+ // Sequence set test cases.
+ //
+
+ // All enum group ; non-class (sequence).
+ {
+ static constexpr auto Letters = Literals<A, B, C>;
+
+ static_assert(0 == anyOf(Letters));
+ static_assert(A == anyOf(Letters));
+ static_assert(1u == anyOf(Letters));
+ static_assert(B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(Letter::D != anyOf(Letters));
+ static_assert(E != anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<MakeMetaSequenceSet<int, A, B, C>>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // All enum group ; class (sequence).
+ {
+ static constexpr auto Letters = Literals<Letter::A, Letter::B, Letter::C>;
+
+ static_assert(short(0) == anyOf(Letters));
+ static_assert(Letter::A == anyOf(Letters));
+ static_assert(1ul == anyOf(Letters));
+ static_assert(Letter::B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(Letter::D != anyOf(Letters));
+ static_assert(Letter::E != anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<
+ MakeMetaSequenceSet<int, Letter::A, Letter::B, Letter::C>, Letter>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // Mixed enum + integral group ; non-class (sequence).
+ {
+ static constexpr auto Letters = Literals<0, B, Two>;
+
+ static_assert(0ull == anyOf(Letters));
+ static_assert(A == anyOf(Letters));
+ static_assert(1 == anyOf(Letters));
+ static_assert(B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(D != anyOf(Letters));
+ static_assert(E != anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<MakeMetaSequenceSet<int, 0, B, 2>>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // Mixed group ; enum class (sequence).
+ {
+ static constexpr auto Letters = Literals<A, char(1), Letter::C>;
+
+ static_assert(0 == anyOf(Letters));
+ static_assert(Letter::A == anyOf(Letters));
+ static_assert(1 == anyOf(Letters));
+ static_assert(Letter::B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(C == anyOf(Letters));
+ static_assert(Letter::D != anyOf(Letters));
+
+ using ExpectedT =
+ CondGroupMetaSet<MakeMetaSequenceSet<int, A, char(1), Letter::C>,
+ Letter>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ //
+ // Bitset test cases.
+ //
+
+ // All enum group ; non-class (bitset).
+ {
+ static constexpr auto Letters = Literals<A, B, C, D>;
+
+ static_assert(0 == anyOf(Letters));
+ static_assert(A == anyOf(Letters));
+ static_assert(1u == anyOf(Letters));
+ static_assert(B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(D == anyOf(Letters));
+ static_assert(E != anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<MakeMetaBitset<int, A, B, C, D>>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // All enum group ; class (bitset).
+ {
+ static constexpr auto Letters =
+ Literals<Letter::A, Letter::B, Letter::C, Letter::D>;
+
+ static_assert(short(0) == anyOf(Letters));
+ static_assert(Letter::A == anyOf(Letters));
+ static_assert(1ul == anyOf(Letters));
+ static_assert(Letter::B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(Letter::D == anyOf(Letters));
+ static_assert(Letter::E != anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<
+ MakeMetaBitset<int, Letter::A, Letter::B, Letter::C, Letter::D>,
+ Letter>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // Mixed enum + integral group ; non-class (bitset).
+ {
+ static constexpr auto Letters = Literals<0, B, 2, D>;
+
+ static_assert(0ull == anyOf(Letters));
+ static_assert(A == anyOf(Letters));
+ static_assert(1 == anyOf(Letters));
+ static_assert(B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(D == anyOf(Letters));
+ static_assert(E != anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<MakeMetaBitset<int, 0, B, 2, D>>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // Mixed group ; enum class (bitset).
+ {
+ static constexpr auto Letters = Literals<0, char(1), Letter::C, Letter::D>;
+
+ static_assert(0 == anyOf(Letters));
+ static_assert(Letter::A == anyOf(Letters));
+ static_assert(1 == anyOf(Letters));
+ static_assert(Letter::B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(Letter::D == anyOf(Letters));
+ static_assert(Letter::E != anyOf(Letters));
+
+ using ExpectedT =
+ CondGroupMetaSet<MakeMetaBitset<int, 0, char(1), Letter::C, Letter::D>,
+ Letter>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ //
+ // Sparse bitset test cases.
+ //
+
+ // Mixed enum + integral group ; non-class (sparse bitset).
+ {
+ static constexpr auto Letters = Literals<0, B, 2, D, 1000>;
+
+ static_assert(0ull == anyOf(Letters));
+ static_assert(A == anyOf(Letters));
+ static_assert(1 == anyOf(Letters));
+ static_assert(B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(D == anyOf(Letters));
+ static_assert(E != anyOf(Letters));
+ static_assert(1000 == anyOf(Letters));
+
+ using ExpectedT =
+ CondGroupMetaSet<MetaSparseBitset<MakeMetaBitset<int, 0, B, 2, D>,
+ MakeMetaSequenceSet<int, 1000>>>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+ // Mixed group ; enum class (sparse bitset).
+ {
+ static constexpr auto Letters =
+ Literals<0, char(1), Letter::C, Letter::D, 5000>;
+
+ static_assert(0 == anyOf(Letters));
+ static_assert(Letter::A == anyOf(Letters));
+ static_assert(1 == anyOf(Letters));
+ static_assert(Letter::B == anyOf(Letters));
+ static_assert(Foo(C) == anyOf(Letters));
+ static_assert(Letter::D == anyOf(Letters));
+ static_assert(Letter::E != anyOf(Letters));
+ static_assert(4999 != anyOf(Letters));
+ static_assert(5000 == anyOf(Letters));
+ static_assert(5001 != anyOf(Letters));
+ static_assert(Foo(1) == anyOf(Letters));
+
+ using ExpectedT = CondGroupMetaSet<
+ MetaSparseBitset<MakeMetaBitset<int, 0, char(1), Letter::C, Letter::D>,
+ MakeMetaSequenceSet<int, 5000>>,
+ Letter>;
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Letters)>, ExpectedT>);
+ }
+
+#if 0 // compile errors
+ {
+ // Mixing signedness.
+ [[maybe_unused]] static constexpr auto MixSignedness =
+ Literals<1, 2u, 3, 4u>;
+ }
+
+ {
+ // Multiple enum types: class + class
+ [[maybe_unused]] static constexpr auto MalformedLetters =
+ Literals<Number::Zero, Letter::B, Letter::C, Letter::D>;
+ }
+
+ {
+ // enum class group.
+ static constexpr auto Letters =
+ Literals<Letter::A, Letter::B, Letter::C, Letter::D>;
+
+ // Incompatible comparisons:
+ EXPECT_TRUE(Number::One == anyOf(Letters));
+ }
+#endif
+}
+
+TEST(CondGroupTest, AllEmptyTuple) {
+ auto Evens = Literals<2, 4, 6, 8, 10>;
+ auto Odds = Literals<1, 3, 5, 7, 9>;
+
+ static_assert(std::is_empty_v<decltype(Evens)> &&
+ std::is_empty_v<decltype(Odds)>);
+
+ // Create a tuple-representation group of two empty classes.
+ auto G = makeGroup(Evens, Odds);
+ EXPECT_TRUE(1 == anyOf(G));
+}
+
+TEST(CondGroupTest, LiteralsContainer) {
+
+ // Empty set.
+ {
+ static constexpr auto Test = Literals<>;
+
+ static constexpr std::array<int, 0> Expected{};
+
+ static_assert(Test.empty() == true);
+ static_assert(Test.size() == Expected.size());
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Single value set.
+ {
+ static constexpr auto Test = Literals<5000>;
+
+ static constexpr int Expected[]{5000};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[0] == Expected[0]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Single bitset.
+ {
+ static constexpr auto Test = Literals<5, 10, 15, 20, 25>;
+
+ static constexpr int Expected[]{5, 10, 15, 20, 25};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Single sequence.
+ {
+ static constexpr auto Test = Literals<0, 10000, 50000, 100000>;
+
+ static constexpr int Expected[]{0, 10000, 50000, 100000};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Multiple clusters.
+ {
+ static constexpr auto Test =
+ Literals<1005, 1004, 1003, 1002, 1001, 1000, 5, 4, 3, 2, 1, 0>;
+
+ static constexpr int Expected[]{0, 1, 2, 3, 4, 5,
+ 1000, 1001, 1002, 1003, 1004, 1005};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Cluster + sequence.
+ {
+ static constexpr auto Test = Literals<1004, 1005, 5, 4, 3, 2, 1, 0>;
+
+ static constexpr int Expected[]{0, 1, 2, 3, 4, 5, 1004, 1005};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Duplicate values (raw sequence).
+ {
+ static constexpr auto Test = Literals<3, 2, 3>;
+
+ static constexpr int Expected[]{2, 3, 3};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ // Duplicate values (bitset representation).
+ {
+ static constexpr auto Test = Literals<3, 2, 3, 11, 6>;
+
+ static constexpr int Expected[]{2, 3, 6, 11};
+
+ static_assert(Test.empty() == false);
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+}
+
+TEST(CondGroupTest, LiteralsUnion) {
+
+ // Identity
+ {
+ constexpr auto Group = cgrp::Literals<5, 10, 15, 65, 70, 75>;
+
+ static constexpr int Expected[]{5, 10, 15, 65, 70, 75};
+
+ static_assert(ce_equal(Group | Group, Expected));
+ }
+
+ // Identity (signed)
+ {
+ constexpr auto Group = cgrp::Literals<75, -70, -65, 5, 10, 64>;
+
+ static constexpr int Expected[]{-70, -65, 5, 10, 64, 75};
+
+ static_assert(ce_equal(Group | Group, Expected));
+ }
+
+ // Disjoint
+ {
+ constexpr auto L = cgrp::Literals<1, 3, 5, 7, 9>;
+ constexpr auto R = cgrp::Literals<2, 4, 6, 8, 10>;
+
+ static constexpr int Expected[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ static_assert(ce_equal(L | R, Expected));
+ }
+
+ // Disjoint (signed)
+ {
+ constexpr auto L = cgrp::Literals<-5, -3, -1, 1, 3, 5>;
+ constexpr auto R = cgrp::Literals<-4, -2, 0, 2, 4>;
+
+ static constexpr int Expected[]{-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5};
+
+ static_assert(ce_equal(L | R, Expected));
+ }
+
+ // Intersecting.
+ {
+ constexpr auto L = cgrp::Literals<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+ constexpr auto R = cgrp::Literals<2, 4, 6, 8, 10>;
+
+ static constexpr int Expected[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ static_assert(ce_equal(L | R, Expected));
+ }
+
+ // Intersecting (signed)
+ {
+ constexpr auto L = cgrp::Literals<-6, -4, -2, 0, 2, 4, 6>;
+ constexpr auto R = cgrp::Literals<-4, -3, -2, -1, 0, 1, 2, 3, 4>;
+
+ static constexpr int Expected[]{-6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6};
+
+ static_assert(ce_equal(L | R, Expected));
+ }
+
+ // Big gap.
+ {
+ constexpr auto L = cgrp::Literals<10000, 5000, 8000>;
+ constexpr auto R = cgrp::Literals<10, 20, 30, 40, 50, 60>;
+
+ static constexpr int Expected[]{10, 20, 30, 40, 50, 60, 5000, 8000, 10000};
+
+ static_assert(ce_equal(L | R, Expected));
+ }
+}
+
+TEST(CondGroupTest, LiteralsIntersection) {
+
+ // Identity
+ {
+ constexpr auto Group = cgrp::Literals<5, 10, 15, 65, 70, 75>;
+
+ static constexpr int Expected[]{5, 10, 15, 65, 70, 75};
+
+ static_assert(ce_equal(Group & Group, Expected));
+ }
+
+ // Identity (signed)
+ {
+ constexpr auto Group = cgrp::Literals<75, -70, -65, 5, 10, 64>;
+
+ static constexpr int Expected[]{-70, -65, 5, 10, 64, 75};
+
+ static_assert(ce_equal(Group & Group, Expected));
+ }
+
+ // Disjoint
+ {
+ constexpr auto L = cgrp::Literals<1, 3, 5, 7, 9>;
+ constexpr auto R = cgrp::Literals<2, 4, 6, 8, 10>;
+
+ static constexpr std::array<int, 0> Expected{};
+
+ static_assert(ce_equal(L & R, Expected));
+ }
+
+ // Disjoint (signed)
+ {
+ constexpr auto L = cgrp::Literals<-5, -3, -1, 1, 3, 5>;
+ constexpr auto R = cgrp::Literals<-4, -2, 0, 2, 4>;
+
+ static constexpr std::array<int, 0> Expected{};
+
+ static_assert(ce_equal(L & R, Expected));
+ }
+
+ // Intersecting.
+ {
+ constexpr auto L = cgrp::Literals<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+ constexpr auto R = cgrp::Literals<2, 4, 6, 8, 10>;
+
+ static constexpr int Expected[]{2, 4, 6, 8, 10};
+
+ static_assert(ce_equal(L & R, Expected));
+ }
+
+ // Intersecting (signed)
+ {
+ constexpr auto L = cgrp::Literals<-6, -4, -2, 0, 2, 4, 6>;
+ constexpr auto R = cgrp::Literals<-4, -3, -2, -1, 0, 1, 2, 3, 4>;
+
+ static constexpr int Expected[]{-4, -2, 0, 2, 4};
+
+ static_assert(ce_equal(L & R, Expected));
+ }
+
+ // Big gap.
+ {
+ constexpr auto L = cgrp::Literals<10000, 5000, 8000>;
+ constexpr auto R = cgrp::Literals<10, 20, 30, 40, 50, 60>;
+
+ static constexpr std::array<int, 0> Expected{};
+
+ static_assert(ce_equal(L & R, Expected));
+ }
+}
+
+TEST(CondGroupTest, LiteralsMinus) {
+
+ // Same operands
+ {
+ constexpr auto Group = cgrp::Literals<5, 10, 15, 65, 70, 75>;
+
+ static constexpr std::array<int, 0> Expected{};
+
+ static_assert(ce_equal(Group - Group, Expected));
+ }
+
+ // Same operands (signed)
+ {
+ constexpr auto Group = cgrp::Literals<75, -70, -65, 5, 10, 64>;
+
+ static constexpr std::array<int, 0> Expected{};
+
+ static_assert(ce_equal(Group - Group, Expected));
+ }
+
+ // Disjoint
+ {
+ constexpr auto L = cgrp::Literals<1, 3, 5, 7, 9>;
+ constexpr auto R = cgrp::Literals<2, 4, 6, 8, 10>;
+
+ static constexpr int Expected[]{1, 3, 5, 7, 9};
+
+ static_assert(ce_equal(L - R, Expected));
+ }
+
+ // Disjoint (signed)
+ {
+ constexpr auto L = cgrp::Literals<-5, -3, -1, 1, 3, 5>;
+ constexpr auto R = cgrp::Literals<-4, -2, 0, 2, 4>;
+
+ static constexpr int Expected[]{-5, -3, -1, 1, 3, 5};
+
+ static_assert(ce_equal(L - R, Expected));
+ }
+
+ // Intersecting.
+ {
+ constexpr auto L = cgrp::Literals<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+ constexpr auto R = cgrp::Literals<2, 4, 6, 8, 10>;
+
+ static constexpr int Expected[]{1, 3, 5, 7, 9};
+
+ static_assert(ce_equal(L - R, Expected));
+ }
+
+ // Intersecting (signed)
+ {
+ constexpr auto L = cgrp::Literals<-6, -4, -2, 0, 2, 4, 6>;
+ constexpr auto R = cgrp::Literals<-4, -3, -2, -1, 0, 1, 2, 3, 4>;
+
+ static constexpr int Expected[]{-6, 6};
+
+ static_assert(ce_equal(L - R, Expected));
+ }
+
+ // Big gap.
+ {
+ constexpr auto L = cgrp::Literals<10000, 5000, 8000>;
+ constexpr auto R = cgrp::Literals<10, 20, 30, 40, 50, 60>;
+
+ static constexpr int Expected[]{5000, 8000, 10000};
+
+ static_assert(ce_equal(L - R, Expected));
+ }
+}
+
+} // namespace
diff --git a/llvm/unittests/ADT/ConstexprUtilsTest.cpp b/llvm/unittests/ADT/ConstexprUtilsTest.cpp
new file mode 100644
index 0000000000000..b2ed246f58f73
--- /dev/null
+++ b/llvm/unittests/ADT/ConstexprUtilsTest.cpp
@@ -0,0 +1,973 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains testing for constexpr utilities.
+///
+/// Utilities include sorting, various other algorithms and integer sequence
+/// manipulation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/ConstexprUtils.h"
+#include "llvm/ADT/STLExtras.h"
+#include "gtest/gtest.h"
+#include <cstdlib>
+#include <type_traits>
+#include <utility>
+
+using namespace llvm;
+
+static constexpr auto Greater = [](auto L, auto R) constexpr { return L > R; };
+
+template <typename T> struct Type {
+ using type = T;
+};
+
+// clang-format off
+using IntTypes =
+ ::testing::Types<
+ signed char,
+ short,
+ int,
+ long,
+ long long,
+ unsigned char,
+ unsigned short,
+ unsigned,
+ unsigned long,
+ unsigned long long
+ >;
+// clang-format on
+
+template <typename T> class ConstexprUtilsTest : public testing::Test {
+public:
+ using IntT = T;
+
+ enum class Number : IntT {
+ Zero,
+ One,
+ Two,
+ Three,
+ Four,
+ Five,
+ Six,
+ Seven,
+ Eight,
+ Nine,
+ };
+
+ static constexpr IntT min() { return std::numeric_limits<IntT>::min(); }
+ static constexpr IntT max() { return std::numeric_limits<IntT>::max(); }
+};
+
+#define EXPOSE_TYPE(type) \
+ using type = typename remove_cvref_t<decltype(*this)>::type
+
+TYPED_TEST_SUITE(ConstexprUtilsTest, IntTypes, );
+
+TYPED_TEST(ConstexprUtilsTest, CompileTimeSwap) {
+ EXPOSE_TYPE(IntT);
+ EXPOSE_TYPE(Number);
+
+ // Swap pair elements.
+ {
+ constexpr auto P1 = []() {
+ auto Ret = std::pair(IntT(1), IntT(2));
+ ce_swap(Ret.first, Ret.second);
+ return Ret;
+ }();
+
+ static_assert(P1.first == 2);
+ static_assert(P1.second == 1);
+
+ constexpr auto P2 = [](auto PIn) {
+ ce_swap(PIn.first, PIn.second);
+ return PIn;
+ }(P1);
+
+ static_assert(P2.first == 1);
+ static_assert(P2.second == 2);
+ }
+
+ // Swap pair elements (enum).
+ {
+ constexpr auto P1 = []() {
+ auto Ret = std::pair(Number::One, Number::Two);
+ ce_swap(Ret.first, Ret.second);
+ return Ret;
+ }();
+
+ static_assert(P1.first == Number::Two);
+ static_assert(P1.second == Number::One);
+
+ constexpr auto P2 = [](auto PIn) {
+ ce_swap(PIn.first, PIn.second);
+ return PIn;
+ }(P1);
+
+ static_assert(P2.first == Number::One);
+ static_assert(P2.second == Number::Two);
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, MinMaxCompiletime) {
+ EXPOSE_TYPE(IntT);
+ EXPOSE_TYPE(Number);
+ using BaseT = remove_cvref_t<decltype(*this)>;
+
+ if constexpr (BaseT::max() > 4723) {
+ static_assert(ce_max<IntT>(192, 273, 4723, 20, 3709) == IntT(4723));
+ static_assert(ce_min<IntT>(192, 273, 4723, 20, 3709) == IntT(20));
+ }
+
+ static_assert(ce_max<IntT>(20, 120, 124, 57) == IntT(124));
+ static_assert(ce_min<IntT>(20, 120, 124, 57) == IntT(20));
+
+ if constexpr (std::is_signed_v<IntT>) {
+ if constexpr (BaseT::max() > 4723 && BaseT::min() < -2873) {
+ static_assert(ce_max<IntT>(192, 273, -2873, 4723, 20, 3709) ==
+ IntT(4723));
+ static_assert(ce_min<IntT>(192, 273, -2873, 4723, 20, 3709) ==
+ IntT(-2873));
+ }
+ }
+
+ if constexpr (std::is_signed_v<IntT>) {
+ static_assert(ce_max<IntT>(127, 0, 20, -20, -128, -90, 103) == IntT(127));
+ static_assert(ce_min<IntT>(127, 0, 20, -20, -128, -90, 103) == IntT(-128));
+ }
+
+ static_assert(ce_max<Number>(Number::Four, Number::Two, Number::Six,
+ Number::Three, Number::Four) == Number::Six);
+
+ static_assert(ce_min<Number>(Number::Four, Number::Two, Number::Six,
+ Number::Three, Number::Four) == Number::Two);
+}
+
+TYPED_TEST(ConstexprUtilsTest, ToArray) {
+ EXPOSE_TYPE(IntT);
+ EXPOSE_TYPE(Number);
+
+ // int array conversion.
+ {
+ static constexpr IntT CArr[]{1, 3, 5, 7, 9};
+ static constexpr auto Arr = to_array(CArr);
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Arr)>, std::array<IntT, 5>>);
+
+ static_assert(Arr.size() == std::size(CArr));
+ static_assert(Arr[0] == IntT(1));
+ static_assert(Arr[1] == IntT(3));
+ static_assert(Arr[2] == IntT(5));
+ static_assert(Arr[3] == IntT(7));
+ static_assert(Arr[4] == IntT(9));
+
+ EXPECT_TRUE(equal(CArr, Arr));
+ }
+
+ // enum array conversion.
+ {
+ static constexpr Number CArr[]{Number::One, Number::Three, Number::Five,
+ Number::Seven, Number::Nine};
+ static constexpr auto Arr = to_array(CArr);
+
+ static_assert(
+ std::is_same_v<std::remove_cv_t<decltype(Arr)>, std::array<Number, 5>>);
+
+ static_assert(Arr.size() == std::size(CArr));
+ static_assert(Arr[0] == Number::One);
+ static_assert(Arr[1] == Number::Three);
+ static_assert(Arr[2] == Number::Five);
+ static_assert(Arr[3] == Number::Seven);
+ static_assert(Arr[4] == Number::Nine);
+
+ EXPECT_TRUE(equal(CArr, Arr));
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, SliceArray) {
+ EXPOSE_TYPE(IntT);
+ EXPOSE_TYPE(Number);
+
+ // int array slice.
+ {
+ static constexpr std::array<IntT, 5> Arr{{1, 3, 5, 7, 9}};
+
+ // Front slice.
+ {
+ static constexpr auto Slice = ce_slice<0, 2>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 2>>);
+
+ static_assert(Slice[0] == 1);
+ static_assert(Slice[1] == 3);
+ }
+
+ // Back slice.
+ {
+ static constexpr auto Slice = ce_slice<1, 4>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 4>>);
+
+ static_assert(Slice[0] == 3);
+ static_assert(Slice[1] == 5);
+ static_assert(Slice[2] == 7);
+ static_assert(Slice[3] == 9);
+ }
+
+ // Middle slice.
+ {
+ static constexpr auto Slice = ce_slice<2, 2>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 2>>);
+
+ static_assert(Slice[0] == 5);
+ static_assert(Slice[1] == 7);
+ }
+
+ // Zero-length slice (begin).
+ {
+ static constexpr auto Slice = ce_slice<0, 0>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 0>>);
+ }
+
+ // Zero-length slice (last)
+ {
+ static constexpr auto Slice = ce_slice<4, 0>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 0>>);
+ }
+
+ // Zero-length slice (end)
+ {
+ static constexpr auto Slice = ce_slice<5, 0>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 0>>);
+ }
+
+ // Complete slice.
+ {
+ static constexpr auto Slice = ce_slice<0, 5>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<IntT, 5>>);
+
+ static_assert(Slice[0] == 1);
+ static_assert(Slice[1] == 3);
+ static_assert(Slice[2] == 5);
+ static_assert(Slice[3] == 7);
+ static_assert(Slice[4] == 9);
+ }
+ }
+
+ // enum array slice.
+ {
+ static constexpr std::array<Number, 5> Arr{{Number::One, Number::Three,
+ Number::Five, Number::Seven,
+ Number::Nine}};
+
+ // Front slice.
+ {
+ static constexpr auto Slice = ce_slice<0, 2>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 2>>);
+
+ static_assert(Slice[0] == Number::One);
+ static_assert(Slice[1] == Number::Three);
+ }
+
+ // Back slice.
+ {
+ static constexpr auto Slice = ce_slice<1, 4>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 4>>);
+
+ static_assert(Slice[0] == Number::Three);
+ static_assert(Slice[1] == Number::Five);
+ static_assert(Slice[2] == Number::Seven);
+ static_assert(Slice[3] == Number::Nine);
+ }
+
+ // Middle slice.
+ {
+ static constexpr auto Slice = ce_slice<2, 2>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 2>>);
+
+ static_assert(Slice[0] == Number::Five);
+ static_assert(Slice[1] == Number::Seven);
+ }
+
+ // Zero-length slice (begin).
+ {
+ static constexpr auto Slice = ce_slice<0, 0>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 0>>);
+ }
+
+ // Zero-length slice (last)
+ {
+ static constexpr auto Slice = ce_slice<4, 0>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 0>>);
+ }
+
+ // Zero-length slice (end)
+ {
+ static constexpr auto Slice = ce_slice<5, 0>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 0>>);
+ }
+
+ // Complete slice.
+ {
+ static constexpr auto Slice = ce_slice<0, 5>(Arr);
+
+ static_assert(std::is_same_v<std::remove_cv_t<decltype(Slice)>,
+ std::array<Number, 5>>);
+
+ static_assert(Slice[0] == Number::One);
+ static_assert(Slice[1] == Number::Three);
+ static_assert(Slice[2] == Number::Five);
+ static_assert(Slice[3] == Number::Seven);
+ static_assert(Slice[4] == Number::Nine);
+ }
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, SliceLiterals) {
+ EXPOSE_TYPE(IntT);
+
+ // Front slice.
+ {
+ using Slice = SliceLiterals<0, 2, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT, 5, 10>>);
+ }
+
+ // Back slice.
+ {
+ using Slice = SliceLiterals<5, 3, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(
+ std::is_same_v<Slice, std::integer_sequence<IntT, 30, 35, 40>>);
+ }
+
+ // Middle slice.
+ {
+ using Slice = SliceLiterals<2, 4, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(
+ std::is_same_v<Slice, std::integer_sequence<IntT, 15, 20, 25, 30>>);
+ }
+
+ // Zero-length slice (begin).
+ {
+ using Slice = SliceLiterals<0, 0, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT>>);
+ }
+
+ // Zero-length slice (last)
+ {
+ using Slice = SliceLiterals<7, 0, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT>>);
+ }
+
+ // Zero-length slice (end)
+ {
+ using Slice = SliceLiterals<8, 0, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT>>);
+ }
+
+ // Complete slice.
+ {
+ using Slice = SliceLiterals<0, 8, IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+ static_assert(
+ std::is_same_v<
+ Slice, std::integer_sequence<IntT, 5, 10, 15, 20, 25, 30, 35, 40>>);
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, SliceSequence) {
+ EXPOSE_TYPE(IntT);
+
+ using Seq = std::integer_sequence<IntT, 5, 10, 15, 20, 25, 30, 35, 40>;
+
+ // Front slice.
+ {
+ using Slice = SliceSequence<0, 2, Seq>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT, 5, 10>>);
+ }
+
+ // Back slice.
+ {
+ using Slice = SliceSequence<5, 3, Seq>;
+ static_assert(
+ std::is_same_v<Slice, std::integer_sequence<IntT, 30, 35, 40>>);
+ }
+
+ // Middle slice.
+ {
+ using Slice = SliceSequence<2, 4, Seq>;
+ static_assert(
+ std::is_same_v<Slice, std::integer_sequence<IntT, 15, 20, 25, 30>>);
+ }
+
+ // Zero-length slice (begin).
+ {
+ using Slice = SliceSequence<0, 0, Seq>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT>>);
+ }
+
+ // Zero-length slice (last)
+ {
+ using Slice = SliceSequence<Seq().size() - 1, 0, Seq>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT>>);
+ }
+
+ // Zero-length slice (end)
+ {
+ using Slice = SliceSequence<Seq().size(), 0, Seq>;
+ static_assert(std::is_same_v<Slice, std::integer_sequence<IntT>>);
+ }
+
+ // Complete slice.
+ {
+ using Slice = SliceSequence<0, Seq().size(), Seq>;
+ static_assert(
+ std::is_same_v<
+ Slice, std::integer_sequence<IntT, 5, 10, 15, 20, 25, 30, 35, 40>>);
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, PushBackSequence) {
+ EXPOSE_TYPE(IntT);
+
+ using Empty = std::integer_sequence<IntT>;
+
+ using Seq1 = PushBackSequence<Empty, 10>;
+ static_assert(std::is_same_v<Seq1, std::integer_sequence<IntT, 10>>);
+
+ using Seq2 = PushBackSequence<Seq1, 20>;
+ static_assert(std::is_same_v<Seq2, std::integer_sequence<IntT, 10, 20>>);
+
+ using Seq3 = PushBackSequence<Seq2, 30>;
+ static_assert(std::is_same_v<Seq3, std::integer_sequence<IntT, 10, 20, 30>>);
+
+ using Seq4 = PushBackSequence<Seq3, 30>;
+ static_assert(
+ std::is_same_v<Seq4, std::integer_sequence<IntT, 10, 20, 30, 30>>);
+
+ using Seq5 = PushBackSequence<Seq4, 20>;
+ static_assert(
+ std::is_same_v<Seq5, std::integer_sequence<IntT, 10, 20, 30, 30, 20>>);
+
+ using Seq6 = PushBackSequence<Seq5, 10>;
+ static_assert(
+ std::is_same_v<Seq6,
+ std::integer_sequence<IntT, 10, 20, 30, 30, 20, 10>>);
+}
+
+TYPED_TEST(ConstexprUtilsTest, CompileTimeBubbleSortLiterals) {
+ EXPOSE_TYPE(IntT);
+
+ // Pre-sorted
+ {
+ using Sorted = SortLiterals<IntT, 1, 2, 3, 4, 5, 6, 7, 8>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 2, 3, 4, 5, 6, 7, 8>>);
+ }
+
+ // Pre-sorted + Duplicate elements
+ {
+ using Sorted = SortLiterals<IntT, 1, 1, 2, 2, 3, 3, 4, 4>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 1, 2, 2, 3, 3, 4, 4>>);
+ }
+
+ // Scrambled
+ {
+ using Sorted = SortLiterals<IntT, 4, 1, 3, 2>;
+
+ static_assert(
+ std::is_same_v<Sorted, std::integer_sequence<IntT, 1, 2, 3, 4>>);
+ }
+
+ // Scrambled + Duplicate elements
+ {
+ using Sorted = SortLiterals<IntT, 1, 4, 1, 2, 4, 3, 2>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 1, 2, 2, 3, 4, 4>>);
+ }
+
+ // Negative values.
+ if constexpr (std::is_signed_v<IntT>) {
+ using Sorted = SortLiterals<IntT, -1, 4, 1, 2, -4, 3, -2>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, -4, -2, -1, 1, 2, 3, 4>>);
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, CompileTimeQuickSortLiterals) {
+ EXPOSE_TYPE(IntT);
+
+ // Quick Sort engages when the number of elements is >= 20
+
+ // Pre-sorted
+ {
+ using Sorted = SortLiterals<IntT, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20>;
+
+ static_assert(
+ std::is_same_v<Sorted, std::integer_sequence<IntT, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20>>);
+ }
+
+ // Pre-sorted + Duplicate elements
+ {
+ using Sorted = SortLiterals<IntT, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7,
+ 8, 8, 9, 9, 10, 10>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10>>);
+ }
+
+ // Scrambled
+ {
+ using Sorted = SortLiterals<IntT, 1, 20, 3, 18, 5, 16, 7, 14, 9, 12, 11, 10,
+ 13, 8, 15, 6, 17, 4, 19, 2>;
+
+ static_assert(
+ std::is_same_v<Sorted, std::integer_sequence<IntT, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20>>);
+ }
+
+ // Scrambled + Duplicate elements
+ {
+ using Sorted = SortLiterals<IntT, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6, 6, 5, 7, 4,
+ 8, 3, 9, 2, 10, 1>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10>>);
+ }
+
+ // Negative values
+ if constexpr (std::is_signed_v<IntT>) {
+ using Sorted = SortLiterals<IntT, 1, -10, 2, -9, 3, -8, 4, -7, 5, -6, 6, -5,
+ 7, -4, 8, -3, 9, -2, 10, -1, 0>;
+
+ static_assert(
+ std::is_same_v<Sorted, std::integer_sequence<
+ IntT, -10, -9, -8, -7, -6, -5, -4, -3, -2,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>);
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, CompileTimeBubbleSortSequences) {
+ EXPOSE_TYPE(IntT);
+
+ // Pre-sorted
+ {
+ using Seq = std::integer_sequence<IntT, 1, 2, 3, 4, 5, 6, 7, 8>;
+ using Sorted = SortSequence<Seq>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 2, 3, 4, 5, 6, 7, 8>>);
+ }
+
+ // Pre-sorted + Duplicate elements
+ {
+ using Seq = std::integer_sequence<IntT, 1, 1, 2, 2, 3, 3, 4, 4>;
+ using Sorted = SortSequence<Seq>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 1, 2, 2, 3, 3, 4, 4>>);
+ }
+
+ // Scrambled
+ {
+ using Seq = std::integer_sequence<IntT, 4, 1, 3, 2>;
+ using Sorted = SortSequence<Seq>;
+
+ static_assert(
+ std::is_same_v<Sorted, std::integer_sequence<IntT, 1, 2, 3, 4>>);
+ }
+
+ // Scrambled + Duplicate elements
+ {
+ using Seq = std::integer_sequence<IntT, 1, 4, 1, 2, 4, 3, 2>;
+ using Sorted = SortSequence<Seq>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, 1, 1, 2, 2, 3, 4, 4>>);
+ }
+
+ // Negative values.
+ if constexpr (std::is_signed_v<IntT>) {
+ using Seq = std::integer_sequence<IntT, -1, 4, 1, 2, -4, 3, -2>;
+ using Sorted = SortSequence<Seq>;
+
+ static_assert(
+ std::is_same_v<Sorted,
+ std::integer_sequence<IntT, -4, -2, -1, 1, 2, 3, 4>>);
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, BubbleSortArrays) {
+ EXPOSE_TYPE(IntT);
+ EXPOSE_TYPE(Number);
+
+ // Pre-sorted
+ {
+ static constexpr IntT Seq[]{1, 2, 3, 4, 5, 6, 7, 8};
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+
+ EXPECT_TRUE(equal(Seq, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Seq, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{8, 7, 6, 5, 4, 3, 2, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Pre-sorted + Duplicate elements
+ {
+ static constexpr IntT Seq[]{1, 1, 2, 2, 3, 3, 4, 4};
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+
+ EXPECT_TRUE(equal(Seq, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Seq, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{4, 4, 3, 3, 2, 2, 1, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Pre-sorted (enum)
+ {
+ constexpr Number Seq[]{Number::One, Number::Two, Number::Three,
+ Number::Four};
+ static constexpr std::array<Number, std::size(Seq)> Sorted = ce_sort(Seq);
+
+ EXPECT_TRUE(equal(Seq, Sorted));
+
+ // Run-time sort in-place.
+ std::array<Number, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Seq, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<Number, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const Number ExpectedG[std::size(Seq)]{Number::Four, Number::Three,
+ Number::Two, Number::One};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Pre-sorted (enum) + Duplicate elements
+ {
+ constexpr Number Seq[]{Number::One, Number::One, Number::Two,
+ Number::Two, Number::Three, Number::Four,
+ Number::Four};
+ static constexpr std::array<Number, std::size(Seq)> Sorted = ce_sort(Seq);
+
+ EXPECT_TRUE(equal(Seq, Sorted));
+
+ // Run-time sort in-place.
+ std::array<Number, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Seq, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<Number, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const Number ExpectedG[std::size(Seq)]{
+ Number::Four, Number::Four, Number::Three, Number::Two,
+ Number::Two, Number::One, Number::One};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Scrambled
+ {
+ static constexpr IntT Seq[]{8, 4, 1, 6, 2, 3, 7, 5};
+
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const IntT Expected[std::size(Seq)]{1, 2, 3, 4, 5, 6, 7, 8};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{8, 7, 6, 5, 4, 3, 2, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Scrambled + Duplicate elements
+ {
+ static constexpr IntT Seq[]{2, 4, 1, 3, 1, 3, 4, 2};
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static constexpr IntT Expected[]{1, 1, 2, 2, 3, 3, 4, 4};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{4, 4, 3, 3, 2, 2, 1, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Scrambled (enum)
+ {
+ static constexpr Number Seq[]{Number::Four, Number::One, Number::Three,
+ Number::Two};
+ static constexpr std::array<Number, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const Number Expected[std::size(Seq)]{Number::One, Number::Two,
+ Number::Three, Number::Four};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<Number, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<Number, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const Number ExpectedG[std::size(Seq)]{Number::Four, Number::Three,
+ Number::Two, Number::One};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Scrambled (enum) + Duplicate elements
+ {
+ static constexpr Number Seq[]{Number::One, Number::Four, Number::One,
+ Number::Two, Number::Four, Number::Three,
+ Number::Two};
+ static constexpr std::array<Number, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const Number Expected[std::size(Seq)]{
+ Number::One, Number::One, Number::Two, Number::Two,
+ Number::Three, Number::Four, Number::Four};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<Number, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<Number, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const Number ExpectedG[std::size(Seq)]{
+ Number::Four, Number::Four, Number::Three, Number::Two,
+ Number::Two, Number::One, Number::One};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Negative values
+ if constexpr (std::is_signed_v<IntT>) {
+ static constexpr IntT Seq[]{-2, 4, 1, -3, -1, 3, -4, 2};
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static constexpr IntT Expected[]{-4, -3, -2, -1, 1, 2, 3, 4};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{4, 3, 2, 1, -1, -2, -3, -4};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+}
+
+TYPED_TEST(ConstexprUtilsTest, QuickSortArrays) {
+ EXPOSE_TYPE(IntT);
+
+ // Quick Sort engages when the number of elements is >= 20
+
+ // Pre-sorted
+ {
+ static constexpr IntT Seq[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const IntT Expected[std::size(Seq)]{
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{
+ 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Pre-sorted + Duplicate elements
+ {
+ static constexpr IntT Seq[]{1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10};
+
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const IntT Expected[std::size(Seq)]{1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{10, 10, 9, 9, 8, 8, 7, 7, 6, 6,
+ 5, 5, 4, 4, 3, 3, 2, 2, 1, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Scrambled
+ {
+ static constexpr IntT Seq[]{1, 20, 3, 18, 5, 16, 7, 14, 9, 12,
+ 11, 10, 13, 8, 15, 6, 17, 4, 19, 2};
+
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const IntT Expected[std::size(Seq)]{
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{
+ 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+
+ // Scrambled + Duplicate elements
+ {
+ static constexpr IntT Seq[]{1, 10, 2, 9, 3, 8, 4, 7, 5, 6,
+ 6, 5, 7, 4, 8, 3, 9, 2, 10, 1};
+
+ static constexpr std::array<IntT, std::size(Seq)> Sorted = ce_sort(Seq);
+ static const IntT Expected[std::size(Seq)]{1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+ 6, 6, 7, 7, 8, 8, 9, 9, 10, 10};
+
+ EXPECT_TRUE(equal(Expected, Sorted));
+
+ // Run-time sort in-place.
+ std::array<IntT, std::size(Seq)> Arr = to_array(Seq);
+ ce_sort_inplace(Arr);
+
+ EXPECT_TRUE(equal(Expected, Arr));
+
+ // Custom comparison object.
+ static constexpr std::array<IntT, std::size(Seq)> SortedG =
+ ce_sort(Seq, Greater);
+ static const IntT ExpectedG[std::size(Seq)]{10, 10, 9, 9, 8, 8, 7, 7, 6, 6,
+ 5, 5, 4, 4, 3, 3, 2, 2, 1, 1};
+
+ EXPECT_TRUE(equal(ExpectedG, SortedG));
+ }
+}
\ No newline at end of file
diff --git a/llvm/unittests/ADT/MetaSetTest.cpp b/llvm/unittests/ADT/MetaSetTest.cpp
new file mode 100644
index 0000000000000..fcce5728f4562
--- /dev/null
+++ b/llvm/unittests/ADT/MetaSetTest.cpp
@@ -0,0 +1,1267 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains testing for the MetaSet infrastructure.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/MetaSet.h"
+#include "llvm/ADT/ConstexprUtils.h"
+
+#include "gtest/gtest.h"
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+using namespace llvm;
+
+namespace {
+
+template <typename Iter0, typename Iter1>
+constexpr bool ce_equal( // NOLINT (readability-identifier-naming)
+ Iter0 B0, Iter0 E0, Iter1 B1, Iter1 E1) {
+ for (; B0 != E0; ++B0, ++B1) {
+ if (B1 == E1 || *B0 != *B1)
+ return false;
+ }
+ return B1 == E1;
+}
+
+template <typename R1, typename R2>
+constexpr bool ce_equal( // NOLINT (readability-identifier-naming)
+ R1 const &L, R2 const &R) {
+ return ce_equal(adl_begin(L), adl_end(L), adl_begin(R), adl_end(R));
+}
+
+template <typename Range1, typename Range2>
+constexpr bool ce_requal( // NOLINT (readability-identifier-naming)
+ Range1 const &L, Range2 const &R) {
+ return ce_equal(adl_rbegin(L), adl_rend(L), adl_rbegin(R), adl_rend(R));
+}
+
+template <typename PosT, auto... Values>
+inline constexpr size_t ComputeNumWords =
+ MetaBitsetNumWordsDetailed<PosT, ce_min<PosT>(Values...),
+ ce_max<PosT>(Values...)>;
+
+enum class Kind { Bitset, Sequence, SparseBitset };
+template <typename T> struct BuildBitset {
+ using PosT = T;
+
+ static constexpr bool IsBitset = true;
+ static constexpr bool IsSparseBitset = false;
+
+ template <auto... Vals> static auto makeSet() {
+ return MakeMetaBitset<PosT, Vals...>();
+ }
+
+ template <Kind K, typename QueryT, typename ExpectedT> struct CheckType {
+ static_assert(K != Kind::Bitset || std::is_same_v<QueryT, ExpectedT>);
+ };
+};
+
+template <typename T> struct BuildBitsetSorted {
+ using PosT = T;
+
+ static constexpr bool IsBitset = true;
+ static constexpr bool IsSparseBitset = false;
+
+ template <auto... Vals> static auto makeSet() {
+ using Sorted = SortLiterals<PosT, PosT(Vals)...>;
+ return MakeMetaBitsetFromSortedSequence<Sorted>();
+ }
+
+ template <Kind K, typename QueryT, typename ExpectedT> struct CheckType {
+ static_assert(K != Kind::Bitset || std::is_same_v<QueryT, ExpectedT>);
+ };
+};
+
+template <typename T> struct BuildSequenceSet {
+ using PosT = T;
+
+ static constexpr bool IsBitset = false;
+ static constexpr bool IsSparseBitset = false;
+
+ template <auto... Vals> static auto makeSet() {
+ return MakeMetaSequenceSet<PosT, Vals...>();
+ }
+
+ template <Kind K, typename QueryT, typename ExpectedT> struct CheckType {
+ static_assert(K != Kind::Sequence || std::is_same_v<QueryT, ExpectedT>);
+ };
+};
+
+template <typename T> struct BuildSparseBitset {
+ using PosT = T;
+
+ static constexpr bool IsBitset = false;
+ static constexpr bool IsSparseBitset = true;
+
+ template <auto... Vals> static auto makeSet() {
+ return MakeMetaSparseBitset<PosT, 512, Vals...>();
+ }
+
+ template <Kind K, typename QueryT, typename ExpectedT> struct CheckType {
+ static_assert(K != Kind::SparseBitset || std::is_same_v<QueryT, ExpectedT>);
+ };
+};
+
+template <typename BuilderT, auto... Vals>
+using MakeMetaSet = decltype(BuilderT::template makeSet<Vals...>());
+
+template <typename T> class MetaSetTest : public testing::Test {
+protected:
+ using Builder = T;
+ using PosT = typename T::PosT;
+ using ThisT = MetaSetTest;
+
+ enum class E : PosT { A, B, C, D, E };
+
+ static constexpr int64_t min() { return std::numeric_limits<PosT>::min(); }
+ static constexpr uint64_t max() { return std::numeric_limits<PosT>::max(); }
+};
+
+#define EXPOSE_TYPE(type) \
+ using type = typename remove_cvref_t<decltype(*this)>::type
+
+// clang-format off
+using TestTypes =
+ ::testing::Types<
+ // MetaBitset
+ BuildBitset<signed char>,
+ BuildBitset<short>,
+ BuildBitset<int>,
+ BuildBitset<long>,
+ BuildBitset<long long>,
+ BuildBitset<unsigned char>,
+ BuildBitset<unsigned short>,
+ BuildBitset<unsigned>,
+ BuildBitset<unsigned long>,
+ BuildBitset<unsigned long long>,
+ // MetaBitset (sorted first)
+ BuildBitsetSorted<signed char>,
+ BuildBitsetSorted<short>,
+ BuildBitsetSorted<int>,
+ BuildBitsetSorted<long>,
+ BuildBitsetSorted<long long>,
+ BuildBitsetSorted<unsigned char>,
+ BuildBitsetSorted<unsigned short>,
+ BuildBitsetSorted<unsigned>,
+ BuildBitsetSorted<unsigned long>,
+ BuildBitsetSorted<unsigned long long>,
+ // MetaSequenceSet
+ BuildSequenceSet<signed char>,
+ BuildSequenceSet<short>,
+ BuildSequenceSet<int>,
+ BuildSequenceSet<long>,
+ BuildSequenceSet<long long>,
+ BuildSequenceSet<unsigned char>,
+ BuildSequenceSet<unsigned short>,
+ BuildSequenceSet<unsigned>,
+ BuildSequenceSet<unsigned long>,
+ BuildSequenceSet<unsigned long long>,
+ // MetaSparseBitset
+ BuildSparseBitset<signed char>,
+ BuildSparseBitset<short>,
+ BuildSparseBitset<int>,
+ BuildSparseBitset<long>,
+ BuildSparseBitset<long long>,
+ BuildSparseBitset<unsigned char>,
+ BuildSparseBitset<unsigned short>,
+ BuildSparseBitset<unsigned>,
+ BuildSparseBitset<unsigned long>,
+ BuildSparseBitset<unsigned long long>
+ >;
+// clang-format on
+
+TYPED_TEST_SUITE(MetaSetTest, TestTypes, );
+
+TYPED_TEST(MetaSetTest, BitsetBuilderCompiletime) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(Builder);
+
+ if constexpr (!Builder::IsBitset)
+ return;
+
+ static constexpr PosT MIN = std::numeric_limits<PosT>::min();
+ static constexpr PosT MAX = std::numeric_limits<PosT>::max();
+
+ using Empty = MakeMetaBitset<PosT>;
+ static_assert(MetaBitsetNumWords<Empty> == 0);
+ static_assert(std::is_same_v<Empty, MetaBitset<PosT, 0>>);
+
+ using ZeroOffset = MakeMetaBitset<PosT, 0, 1, 2, 3>;
+ using ZeroOffsetD = MakeMetaBitsetDetailed<PosT, 0, 3, 0, 1, 2, 3>;
+ static_assert(MetaBitsetNumWords<ZeroOffset> == 1);
+ static_assert(ComputeNumWords<PosT, 0, 1, 2, 3> == 1);
+ static_assert(std::is_same_v<ZeroOffset, ZeroOffsetD>);
+ static_assert(std::is_same_v<ZeroOffset, MetaBitset<PosT, 0, 0xf>>);
+
+ using PositiveOffset = MakeMetaBitset<PosT, 6, 5, 7>;
+ using PositiveOffsetD = MakeMetaBitsetDetailed<PosT, 5, 7, 6, 5, 7>;
+ static_assert(MetaBitsetNumWords<PositiveOffset> == 1);
+ static_assert(ComputeNumWords<PosT, 6, 5, 7> == 1);
+ static_assert(std::is_same_v<PositiveOffset, PositiveOffsetD>);
+ static_assert(std::is_same_v<PositiveOffset, MetaBitset<PosT, 5, 0x7>>);
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using NegativeOffset = MakeMetaBitset<PosT, 0, -1, -2, -3, 1>;
+ using NegativeOffsetD =
+ MakeMetaBitsetDetailed<PosT, -3, 1, 0, -1, -2, -3, 1>;
+ static_assert(MetaBitsetNumWords<NegativeOffset> == 1);
+ static_assert(ComputeNumWords<PosT, -3, 0> == 1);
+ static_assert(std::is_same_v<NegativeOffset, NegativeOffsetD>);
+ static_assert(std::is_same_v<NegativeOffset, MetaBitset<PosT, -3, 0x1f>>);
+
+ using NegativeOffset2 = MakeMetaBitset<PosT, -128, 127>;
+ using NegativeOffset2D = MakeMetaBitset<PosT, -128, 127, -128, 127>;
+ static_assert(MetaBitsetNumWords<NegativeOffset2> == 4);
+ static_assert(ComputeNumWords<PosT, -128, 127> == 4);
+ static_assert(std::is_same_v<NegativeOffset2, NegativeOffset2D>);
+ static_assert(
+ std::is_same_v<NegativeOffset2,
+ MetaBitset<PosT, -128, 1ULL, 0ULL, 0ULL, (1ULL << 63)>>);
+ }
+
+ using DuplicateElements = MakeMetaBitset<PosT, 0, 1, 1, 2, 2, 3, 3>;
+ using DuplicateElementsD =
+ MakeMetaBitsetDetailed<PosT, 0, 3, 0, 1, 1, 2, 2, 3, 3>;
+ static_assert(MetaBitsetNumWords<DuplicateElements> == 1);
+ static_assert(ComputeNumWords<PosT, 0, 3> == 1);
+ static_assert(std::is_same_v<DuplicateElements, DuplicateElementsD>);
+ static_assert(std::is_same_v<DuplicateElements, MetaBitset<PosT, 0, 0xf>>);
+
+ using WordBoundary1 = MakeMetaBitset<PosT, 0, 63>;
+ using WordBoundary1D = MakeMetaBitsetDetailed<PosT, 0, 63, 0, 63>;
+ static_assert(ComputeNumWords<PosT, 0, 63> == 1);
+ static_assert(MetaBitsetNumWords<WordBoundary1> == 1);
+ static_assert(std::is_same_v<WordBoundary1, WordBoundary1D>);
+ static_assert(std::is_same_v<WordBoundary1,
+ MetaBitset<PosT, 0, (1ULL << 0 | 1ULL << 63)>>);
+
+ using WordBoundary2 = MakeMetaBitset<PosT, 0, 64>;
+ using WordBoundary2D = MakeMetaBitsetDetailed<PosT, 0, 64, 0, 64>;
+ static_assert(ComputeNumWords<PosT, 0, 64> == 2);
+ static_assert(MetaBitsetNumWords<WordBoundary2> == 2);
+ static_assert(std::is_same_v<WordBoundary2, WordBoundary2D>);
+ static_assert(std::is_same_v<WordBoundary2, MetaBitset<PosT, 0, 1ULL, 1ULL>>);
+
+ using WordBoundary3 = MakeMetaBitset<PosT, 0, 127>;
+ using WordBoundary3D = MakeMetaBitset<PosT, 0, 127>;
+ static_assert(ComputeNumWords<PosT, 0, 127> == 2);
+ static_assert(MetaBitsetNumWords<WordBoundary3> == 2);
+ static_assert(std::is_same_v<WordBoundary3, WordBoundary3D>);
+ static_assert(
+ std::is_same_v<WordBoundary3, MetaBitset<PosT, 0, 1ULL, (1ULL << 63)>>);
+
+ if constexpr (MAX > 128) {
+ using WordBoundary4 = MakeMetaBitset<PosT, 0, 128>;
+ using WordBoundary4D = MakeMetaBitsetDetailed<PosT, 0, 128, 0, 128>;
+ static_assert(ComputeNumWords<PosT, 0, 128> == 3);
+ static_assert(MetaBitsetNumWords<WordBoundary4> == 3);
+ static_assert(std::is_same_v<WordBoundary4, WordBoundary4D>);
+ static_assert(
+ std::is_same_v<WordBoundary4, MetaBitset<PosT, 0, 1ULL, 0ULL, 1ULL>>);
+ }
+
+ if constexpr (MAX > 511) {
+ using BigGap = MakeMetaBitset<PosT, 0, 511>;
+ using BigGapD = MakeMetaBitsetDetailed<PosT, 0, 511, 0, 511>;
+ static_assert(ComputeNumWords<PosT, 0, 511> == 8);
+ static_assert(MetaBitsetNumWords<BigGap> == 8);
+ static_assert(std::is_same_v<BigGap, BigGapD>);
+ using BigGapExp = MetaBitset<PosT, 0, 1ULL, 0ULL, 0ULL, 0ULL, 0ULL, 0ULL,
+ 0ULL, (1ULL << 63)>;
+ static_assert(std::is_same_v<BigGap, BigGapExp>);
+ } else {
+ // 8-bit integer types.
+ using BigGap = MakeMetaBitset<PosT, 0, MAX>;
+ using BigGapD = MakeMetaBitsetDetailed<PosT, 0, MAX, 0, MAX>;
+ static_assert(std::is_same_v<BigGap, BigGapD>);
+ if constexpr (std::is_unsigned_v<PosT>) {
+ static_assert(ComputeNumWords<PosT, 0, MAX> == 4);
+ static_assert(MetaBitsetNumWords<BigGap> == 4);
+ using BigGapExp = MetaBitset<PosT, 0, 1ULL, 0ULL, 0ULL, (1ULL << 63)>;
+ static_assert(std::is_same_v<BigGap, BigGapExp>);
+ } else {
+ static_assert(ComputeNumWords<PosT, 0, MAX> == 2);
+ static_assert(MetaBitsetNumWords<BigGap> == 2);
+ using BigGapExp = MetaBitset<PosT, 0, 1ULL, (1ULL << 63)>;
+ static_assert(std::is_same_v<BigGap, BigGapExp>);
+ }
+ }
+
+ using LargeOffset = MakeMetaBitset<PosT, MAX - 63, MAX>;
+ using LargeOffsetD = MakeMetaBitset<PosT, MAX - 63, MAX, (MAX - 63), MAX>;
+ static_assert(ComputeNumWords<PosT, MAX - 63, MAX> == 1);
+ static_assert(MetaBitsetNumWords<LargeOffset> == 1);
+ static_assert(std::is_same_v<LargeOffset, LargeOffsetD>);
+ static_assert(
+ std::is_same_v<LargeOffset,
+ MetaBitset<PosT, MAX - 63, (1ULL << 0 | 1ULL << 63)>>);
+
+ if constexpr (std::is_signed_v<PosT>) {
+ if constexpr (MIN < -36729) {
+ using LargeNegativeOffset = MakeMetaBitset<PosT, -36729, -36729 + 64>;
+ using LargeNegativeOffsetD =
+ MakeMetaBitset<PosT, -36729, -36729 + 64, -36729, -36729 + 64>;
+ static_assert(ComputeNumWords<PosT, -36729, -36729 + 64> == 2);
+ static_assert(MetaBitsetNumWords<LargeNegativeOffset> == 2);
+ static_assert(std::is_same_v<LargeNegativeOffset, LargeNegativeOffsetD>);
+ static_assert(std::is_same_v<LargeNegativeOffset,
+ MetaBitset<PosT, -36729, 1ULL, 1ULL>>);
+ }
+ }
+}
+
+template <typename BuilderT> class CheckMetaTypes {
+public:
+ template <typename QueryT, typename ExpectedBitsetT,
+ typename ExpectedSequenceT, typename ExpectedSparseBitsetT>
+ static constexpr void check() {
+ typename BuilderT::template CheckType<Kind::Bitset, QueryT,
+ ExpectedBitsetT>();
+
+ typename BuilderT::template CheckType<Kind::Sequence, QueryT,
+ ExpectedSequenceT>();
+
+ typename BuilderT::template CheckType<Kind::SparseBitset, QueryT,
+ ExpectedSparseBitsetT>();
+ }
+};
+
+TYPED_TEST(MetaSetTest, BuiltTypeVerify) {
+ EXPOSE_TYPE(PosT);
+ // EXPOSE_TYPE(E);
+ EXPOSE_TYPE(Builder);
+ using ThisT = std::remove_cv_t<std::remove_reference_t<decltype(*this)>>;
+
+ using Checker = CheckMetaTypes<Builder>;
+
+ // All Set types.
+ {
+ // Sorted values, small range
+ {
+ using BS = MakeMetaSet<Builder, PosT(1), PosT(2), PosT(3), PosT(4)>;
+
+ using ExpectedBitset = MetaBitset<PosT, 1, 0xf>;
+
+ using ExpectedSequence =
+ MetaSequenceSet<std::integer_sequence<PosT, 1, 2, 3, 4>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<ExpectedBitset>;
+
+ Checker::template check<BS, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Reverse sorted values, small range.
+ {
+ using BS = MakeMetaSet<Builder, PosT(4), PosT(3), PosT(2), PosT(1)>;
+
+ using ExpectedBitset = MetaBitset<PosT, 1, 0xf>;
+
+ using ExpectedSequence =
+ MetaSequenceSet<std::integer_sequence<PosT, 4, 3, 2, 1>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<ExpectedBitset>;
+
+ Checker::template check<BS, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Duplicate values, small range.
+ {
+ using BS = MakeMetaSet<Builder, 1, 3, 5, 7, 9, 3, 5, 7>;
+
+ using ExpectedBitset = MetaBitset<PosT, 1, 0x155>;
+
+ using ExpectedSequence =
+ MetaSequenceSet<std::integer_sequence<PosT, 1, 3, 5, 7, 9, 3, 5, 7>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<ExpectedBitset>;
+
+ Checker::template check<BS, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Large offset from 0.
+ {
+ using BS = MakeMetaSet<Builder, 101, 103, 105, 107, 109>;
+
+ using ExpectedBitset = MetaBitset<PosT, 101, 0x155>;
+
+ using ExpectedSequence =
+ MetaSequenceSet<std::integer_sequence<PosT, 101, 103, 105, 107, 109>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<ExpectedBitset>;
+
+ Checker::template check<BS, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Larger offset from 0.
+ if constexpr (ThisT::max() > 10009u) {
+ using BS = MakeMetaSet<Builder, 10001, 10003, 10005, 10007, 10009>;
+
+ using ExpectedBitset = MetaBitset<PosT, 10001, 0x155>;
+
+ using ExpectedSequence = MetaSequenceSet<
+ std::integer_sequence<PosT, 10001, 10003, 10005, 10007, 10009>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<ExpectedBitset>;
+
+ Checker::template check<BS, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Negative offset.
+ if constexpr (ThisT::min() < -1) {
+ using BS = MakeMetaSet<Builder, -101, -103, -105, -107, -109>;
+
+ using ExpectedBitset = MetaBitset<PosT, -109, 0x155>;
+
+ using ExpectedSequence = MetaSequenceSet<
+ std::integer_sequence<PosT, -101, -103, -105, -107, -109>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<ExpectedBitset>;
+
+ Checker::template check<BS, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+ }
+
+ // MetaSparseBitset + MetaSequenceSet validation.
+ if constexpr (!Builder::IsBitset) {
+ using ExpectedBitset = void;
+
+ // Single cluster; no singletons
+ {
+ using Fives = MakeMetaSet<Builder, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95,
+ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100>;
+
+ using ExpectedSequence = MetaSequenceSet<
+ std::integer_sequence<PosT, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95, 10,
+ 20, 30, 40, 50, 60, 70, 80, 90, 100>>;
+ using ExpectedSparseBitset =
+ MetaSparseBitset<MetaBitset<PosT, 5, 0x1084210842108421, 0x84210842>>;
+
+ Checker::template check<Fives, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Multiple clusters; no singletons.
+ if constexpr (ThisT::max() >= 10008u) {
+ using Clusters = MakeMetaSet<Builder, 10000, 10002, 10004, 10006, 10008,
+ 1000, 1002, 1004, 1006, 1008>;
+
+ using ExpectedSequence = MetaSequenceSet<
+ std::integer_sequence<PosT, 10000, 10002, 10004, 10006, 10008, 1000,
+ 1002, 1004, 1006, 1008>>;
+
+ using ExpectedSparseBitset =
+ MetaSparseBitset<MetaBitset<PosT, 1000, 0x155>,
+ MetaBitset<PosT, 10000, 0x155>>;
+
+ Checker::template check<Clusters, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+
+ // Multiple clusters; with singletons.
+ if constexpr (ThisT::max() >= 10008u) {
+ using ClustersAndSingletons =
+ MakeMetaSet<Builder, 10000, 10002, 10004, 10006, 10008, 1000, 1002,
+ 1004, 1006, 1008, 8000, 5000>;
+
+ using ExpectedSequence = MetaSequenceSet<
+ std::integer_sequence<PosT, 10000, 10002, 10004, 10006, 10008, 1000,
+ 1002, 1004, 1006, 1008, 8000, 5000>>;
+ using ExpectedSparseBitset =
+ MetaSparseBitset<MetaBitset<PosT, 1000, 0x155>,
+ MetaBitset<PosT, 10000, 0x155>,
+ MakeMetaSequenceSet<PosT, 5000, 8000>>;
+
+ Checker::template check<ClustersAndSingletons, ExpectedBitset,
+ ExpectedSequence, ExpectedSparseBitset>();
+ }
+
+ // No clusters; all singletons.
+ if constexpr (ThisT::max() >= 90000u) {
+ using Singletons = MakeMetaSet<Builder, 10000, 5000, 8000, 20000, 90000>;
+
+ using ExpectedSequence = MetaSequenceSet<
+ std::integer_sequence<PosT, 10000, 5000, 8000, 20000, 90000>>;
+
+ using ExpectedSparseBitset = MetaSparseBitset<
+ MakeMetaSequenceSet<PosT, 5000, 8000, 10000, 20000, 90000>>;
+
+ Checker::template check<Singletons, ExpectedBitset, ExpectedSequence,
+ ExpectedSparseBitset>();
+ }
+ }
+
+ // MetaSparseBitset validation (varied word sizes)
+ if constexpr (Builder::IsSparseBitset) {
+ // Single cluster; no singletons
+ {
+ using Fives =
+ MakeMetaSparseBitset<PosT, 128, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95,
+ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100>;
+
+ using Expected =
+ MetaSparseBitset<MetaBitset<PosT, 5, 0x1084210842108421, 0x84210842>>;
+
+ static_assert(std::is_same_v<Fives, Expected>);
+ }
+
+ // Multiple clusters; no singletons.
+ if constexpr (ThisT::max() >= 10008u) {
+ using Clusters =
+ MakeMetaSparseBitset<PosT, 256, 10000, 10002, 10004, 10006, 10008,
+ 1000, 1002, 1004, 1006, 1008>;
+
+ using Expected = MetaSparseBitset<MetaBitset<PosT, 1000, 0x155>,
+ MetaBitset<PosT, 10000, 0x155>>;
+
+ static_assert(std::is_same_v<Clusters, Expected>);
+ }
+
+ // Multiple clusters; with singletons.
+ if constexpr (ThisT::max() >= 10008u) {
+ using ClustersAndSingletons =
+ MakeMetaSparseBitset<PosT, 64, 10000, 10002, 10004, 10006, 10008,
+ 1000, 1002, 1004, 1006, 1008, 8000, 5000>;
+
+ using Expected = MetaSparseBitset<MetaBitset<PosT, 1000, 0x155>,
+ MetaBitset<PosT, 10000, 0x155>,
+ MakeMetaSequenceSet<PosT, 5000, 8000>>;
+
+ static_assert(std::is_same_v<ClustersAndSingletons, Expected>);
+ }
+
+ // No clusters; all singletons.
+ if constexpr (ThisT::max() >= 90000u) {
+ using Singletons =
+ MakeMetaSparseBitset<PosT, 64, 10000, 5000, 8000, 20000, 90000>;
+
+ using Expected = MetaSparseBitset<
+ MakeMetaSequenceSet<PosT, 5000, 8000, 10000, 20000, 90000>>;
+
+ static_assert(std::is_same_v<Singletons, Expected>);
+ }
+
+ // Word size too small resulting in singletons
+ if constexpr (ThisT::max() > 192u) {
+ using WordSizeTooSmall = MakeMetaSparseBitset<PosT, 64, 5, 4, 3, 2, 1, 0,
+ 64, 65, 130, 129, 128, 192>;
+
+ using Expected = MetaSparseBitset<
+ MetaBitset<PosT, 0, 0x3f>, MetaBitset<PosT, 128, 0x7>,
+ MetaSequenceSet<std::integer_sequence<PosT, 64, 65, 192>>>;
+
+ static_assert(std::is_same_v<WordSizeTooSmall, Expected>);
+ }
+
+ // Larger word size; no singletons
+ if constexpr (ThisT::max() > 192u) {
+ using WordSizeTooSmall = MakeMetaSparseBitset<PosT, 128, 5, 4, 3, 2, 1, 0,
+ 64, 65, 130, 129, 128, 192>;
+
+ using Expected = MetaSparseBitset<MetaBitset<PosT, 0, 0x3f, 0x3>,
+ MetaBitset<PosT, 128, 0x7, 0x1>>;
+
+ static_assert(std::is_same_v<WordSizeTooSmall, Expected>);
+ }
+
+ // Even larger word size; coalesce MetaBitsets
+ if constexpr (ThisT::max() > 192u) {
+ using WordSizeTooSmall = MakeMetaSparseBitset<PosT, 256, 5, 4, 3, 2, 1, 0,
+ 64, 65, 130, 129, 128, 192>;
+
+ using Expected =
+ MetaSparseBitset<MetaBitset<PosT, 0, 0x3f, 0x3, 0x7, 0x1>>;
+
+ static_assert(std::is_same_v<WordSizeTooSmall, Expected>);
+ }
+ }
+}
+
+TYPED_TEST(MetaSetTest, SortedSequence) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(Builder);
+ EXPOSE_TYPE(ThisT);
+
+ {
+ using Empty = MakeMetaSet<Builder>;
+
+ using Expected = std::integer_sequence<PosT>;
+
+ static_assert(
+ std::is_same_v<typename Empty::sorted_sequence_type, Expected>);
+ }
+
+ {
+ using ZeroOffset = MakeMetaSet<Builder, 0, 1, 2, 3>;
+
+ using Expected = std::integer_sequence<PosT, 0, 1, 2, 3>;
+ static_assert(
+ std::is_same_v<typename ZeroOffset::sorted_sequence_type, Expected>);
+ }
+
+ {
+ using PositiveOffset = MakeMetaSet<Builder, 6, 5, 7>;
+
+ using Expected = std::integer_sequence<PosT, 5, 6, 7>;
+ static_assert(std::is_same_v<typename PositiveOffset::sorted_sequence_type,
+ Expected>);
+ }
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using NegativeOffset = MakeMetaSet<Builder, 0, -1, -2, -3, 1>;
+
+ using Expected = std::integer_sequence<PosT, -3, -2, -1, 0, 1>;
+ static_assert(std::is_same_v<typename NegativeOffset::sorted_sequence_type,
+ Expected>);
+ }
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using NegativeOffset2 = MakeMetaSet<Builder, -128, 127>;
+
+ using Expected = std::integer_sequence<PosT, -128, 127>;
+ static_assert(std::is_same_v<typename NegativeOffset2::sorted_sequence_type,
+ Expected>);
+ }
+
+ {
+ using DuplicateElements = MakeMetaSet<Builder, 0, 1, 1, 2, 2, 3, 3>;
+
+ using Expected =
+ std::conditional_t<IsMetaSetWithDuplicateElements<DuplicateElements>,
+ std::integer_sequence<PosT, 0, 1, 1, 2, 2, 3, 3>,
+ std::integer_sequence<PosT, 0, 1, 2, 3>>;
+ static_assert(
+ std::is_same_v<typename DuplicateElements::sorted_sequence_type,
+ Expected>);
+ }
+
+ {
+ using WordBoundary1 = MakeMetaSet<Builder, 0, 63>;
+
+ using Expected = std::integer_sequence<PosT, 0, 63>;
+ static_assert(
+ std::is_same_v<typename WordBoundary1::sorted_sequence_type, Expected>);
+ }
+
+ {
+ using WordBoundary2 = MakeMetaSet<Builder, 0, 64>;
+
+ using Expected = std::integer_sequence<PosT, 0, 64>;
+ static_assert(
+ std::is_same_v<typename WordBoundary2::sorted_sequence_type, Expected>);
+ }
+
+ {
+ using WordBoundary3 = MakeMetaSet<Builder, 0, 127>;
+
+ using Expected = std::integer_sequence<PosT, 0, 127>;
+ static_assert(
+ std::is_same_v<typename WordBoundary3::sorted_sequence_type, Expected>);
+ }
+
+ if constexpr (ThisT::max() > 128) {
+ using WordBoundary4 = MakeMetaSet<Builder, 0, 128>;
+
+ using Expected = std::integer_sequence<PosT, 0, 128>;
+ static_assert(
+ std::is_same_v<typename WordBoundary4::sorted_sequence_type, Expected>);
+ }
+
+ if constexpr (ThisT::max() > 511) {
+ using BigGap = MakeMetaSet<Builder, 0, 511>;
+
+ using Expected = std::integer_sequence<PosT, 0, 511>;
+ static_assert(
+ std::is_same_v<typename BigGap::sorted_sequence_type, Expected>);
+ } else {
+ // 8-bit integer types.
+ using BigGap = MakeMetaSet<Builder, 0, ThisT::max()>;
+
+ using Expected = std::integer_sequence<PosT, 0, ThisT::max()>;
+ static_assert(
+ std::is_same_v<typename BigGap::sorted_sequence_type, Expected>);
+ }
+
+ {
+ using LargeOffset = MakeMetaSet<Builder, ThisT::max() - 63, ThisT::max()>;
+
+ using Expected =
+ std::integer_sequence<PosT, ThisT::max() - 63, ThisT::max()>;
+ static_assert(
+ std::is_same_v<typename LargeOffset::sorted_sequence_type, Expected>);
+ }
+
+ if constexpr (std::is_signed_v<PosT> && ThisT::min() < -36729) {
+ using LargeNegativeOffset = MakeMetaSet<Builder, -36729, -36729 + 64>;
+
+ using Expected = std::integer_sequence<PosT, -36729, -36729 + 64>;
+ static_assert(
+ std::is_same_v<typename LargeNegativeOffset::sorted_sequence_type,
+ Expected>);
+ }
+}
+
+TYPED_TEST(MetaSetTest, Container) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(Builder);
+ EXPOSE_TYPE(ThisT);
+
+ {
+ using Empty = MakeMetaSet<Builder>;
+
+ MetaSetSortedContainer<Empty> Test;
+ static_assert(Test.size() == 0);
+ static_assert(Test.empty() == true);
+ }
+
+ {
+ using OneElem = MakeMetaSet<Builder, 127>;
+
+ MetaSetSortedContainer<OneElem> Test;
+ static constexpr PosT Expected[]{127};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[0] == Expected[0]);
+
+ static_assert(ce_equal(Test, Expected));
+ static_assert(ce_requal(Test, Expected));
+ }
+
+ {
+ using ZeroOffset = MakeMetaSet<Builder, 0, 1, 2, 3>;
+
+ MetaSetSortedContainer<ZeroOffset> Test;
+ static constexpr PosT Expected[]{0, 1, 2, 3};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ {
+ using PositiveOffset = MakeMetaSet<Builder, 6, 5, 7>;
+
+ MetaSetSortedContainer<PositiveOffset> Test;
+ static constexpr PosT Expected[]{5, 6, 7};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using NegativeOffset = MakeMetaSet<Builder, 0, -1, -2, -3, 1>;
+
+ MetaSetSortedContainer<NegativeOffset> Test;
+ static constexpr PosT Expected[]{-3, -2, -1, 0, 1};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using NegativeOffset2 = MakeMetaSet<Builder, -128, 127>;
+
+ MetaSetSortedContainer<NegativeOffset2> Test;
+ static constexpr PosT Expected[]{-128, 127};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ {
+ using DuplicateElements = MakeMetaSet<Builder, 0, 1, 1, 2, 2, 3, 3>;
+
+ MetaSetSortedContainer<DuplicateElements> Test;
+
+ auto MakeExpected = []() constexpr {
+ if constexpr (IsMetaSetWithDuplicateElements<DuplicateElements>)
+ return std::integer_sequence<PosT, 0, 1, 1, 2, 2, 3, 3>();
+ else
+ return std::integer_sequence<PosT, 0, 1, 2, 3>();
+ };
+
+ static constexpr auto Expected = to_array(MakeExpected());
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ if constexpr (ThisT::max() > 511) {
+ using BigGap = MakeMetaSet<Builder, 0, 511>;
+
+ MetaSetSortedContainer<BigGap> Test;
+ static constexpr PosT Expected[]{0, 511};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ } else {
+ // 8-bit integer types.
+ using BigGap = MakeMetaSet<Builder, 0, ThisT::max()>;
+
+ MetaSetSortedContainer<BigGap> Test;
+ static constexpr PosT Expected[]{0, ThisT::max()};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ {
+ using LargeOffset = MakeMetaSet<Builder, ThisT::max() - 63, ThisT::max()>;
+
+ MetaSetSortedContainer<LargeOffset> Test;
+ static constexpr PosT Expected[]{ThisT::max() - 63, ThisT::max()};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+
+ if constexpr (std::is_signed_v<PosT> && ThisT::min() < -36729) {
+ using LargeNegativeOffset = MakeMetaSet<Builder, -36729, -36729 + 64>;
+
+ MetaSetSortedContainer<LargeNegativeOffset> Test;
+ static constexpr PosT Expected[]{-36729, -36729 + 64};
+
+ static_assert(Test.size() == std::size(Expected));
+ static_assert(Test.empty() == false);
+ static_assert(Test.front() == Expected[0]);
+ static_assert(Test.back() == Expected[std::size(Expected) - 1]);
+ static_assert(Test[1] == Expected[1]);
+
+ EXPECT_TRUE(ce_equal(Test, Expected));
+ EXPECT_TRUE(ce_requal(Test, Expected));
+ }
+}
+
+TYPED_TEST(MetaSetTest, ContainsCompiletime) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(E);
+ EXPOSE_TYPE(Builder);
+
+ {
+ using BS = MakeMetaSet<Builder, PosT(1), PosT(2), PosT(3), PosT(4)>;
+ static_assert(BS::contains(1));
+ static_assert(BS::contains(2));
+ static_assert(BS::contains(3));
+ static_assert(BS::contains(4));
+
+ static_assert(!BS::contains(0));
+ static_assert(!BS::contains(-1));
+ static_assert(!BS::contains(1000000));
+ }
+
+ {
+ using BS = MakeMetaSet<Builder, E::A, E::C, E::D>;
+ static_assert(BS::contains(E::A));
+ static_assert(BS::contains(0));
+ static_assert(BS::contains(E::C));
+ static_assert(BS::contains(2));
+ static_assert(BS::contains(E::D));
+ static_assert(BS::contains(3));
+
+ static_assert(!BS::contains(E::B));
+ static_assert(!BS::contains(1));
+ static_assert(!BS::contains(E::E));
+ static_assert(!BS::contains(4));
+ }
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using BS = MakeMetaSet<Builder, 0, -126, 127>;
+
+ if constexpr (Builder::IsBitset) {
+ static_assert(
+ std::is_same_v<BS, MetaBitset<PosT, -126, 1ULL, (1ULL << 62), 0ULL,
+ (1ULL << 61)>>);
+ }
+
+ static_assert(BS::contains(0));
+ static_assert(BS::contains(-126));
+ static_assert(BS::contains(127));
+
+ static_assert(!BS::contains(-127));
+ static_assert(!BS::contains(128));
+ static_assert(!BS::contains(-125));
+ static_assert(!BS::contains(126));
+ static_assert(!BS::contains(-1));
+ static_assert(!BS::contains(1));
+ }
+}
+
+TYPED_TEST(MetaSetTest, ContainsRuntime) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(E);
+ EXPOSE_TYPE(Builder);
+
+ {
+ using BS = MakeMetaSet<Builder, PosT(1), PosT(2), PosT(3), PosT(4)>;
+ EXPECT_TRUE(BS::contains(1));
+ EXPECT_TRUE(BS::contains(2));
+ EXPECT_TRUE(BS::contains(3));
+ EXPECT_TRUE(BS::contains(4));
+
+ EXPECT_FALSE(BS::contains(0));
+ EXPECT_FALSE(BS::contains(-1));
+ EXPECT_FALSE(BS::contains(1000000));
+ }
+
+ {
+ using BS = MakeMetaSet<Builder, E::A, E::C, E::D>;
+ EXPECT_TRUE(BS::contains(E::A));
+ EXPECT_TRUE(BS::contains(0));
+ EXPECT_TRUE(BS::contains(E::C));
+ EXPECT_TRUE(BS::contains(2));
+ EXPECT_TRUE(BS::contains(E::D));
+ EXPECT_TRUE(BS::contains(3));
+
+ EXPECT_FALSE(BS::contains(E::B));
+ EXPECT_FALSE(BS::contains(1));
+ EXPECT_FALSE(BS::contains(E::E));
+ EXPECT_FALSE(BS::contains(4));
+ }
+
+ if constexpr (std::is_signed_v<PosT>) {
+ using BS = MakeMetaSet<Builder, 0, -126, 127>;
+ EXPECT_TRUE(BS::contains(0));
+ EXPECT_TRUE(BS::contains(-126));
+ EXPECT_TRUE(BS::contains(127));
+
+ EXPECT_FALSE(BS::contains(-127));
+ EXPECT_FALSE(BS::contains(128));
+ EXPECT_FALSE(BS::contains(-125));
+ EXPECT_FALSE(BS::contains(126));
+ EXPECT_FALSE(BS::contains(-1));
+ EXPECT_FALSE(BS::contains(1));
+ }
+}
+
+TYPED_TEST(MetaSetTest, Union) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(Builder);
+ EXPOSE_TYPE(ThisT);
+
+ // Identity
+ {
+ using S = MakeMetaSet<Builder, 5, 10, 15, 65, 70, 75>;
+
+ using Union = MetaSetUnion<S, S, 64>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 64, 5, 10, 15, 65, 70, 75>;
+
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+
+ // Identity (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using S = MakeMetaSet<Builder, -75, -70, -65, 5, 10>;
+
+ using Union = MetaSetUnion<S, S, 64>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 64, -75, -70, -65, 5, 10>;
+
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+
+ // Disjoint
+ {
+ using L = MakeMetaSet<Builder, 1, 3, 5, 7, 9>;
+ using R = MakeMetaSet<Builder, 2, 4, 6, 8, 10>;
+
+ using Union = MetaSetUnion<L, R, 512>;
+
+ using ExpectedT =
+ MakeMetaSparseBitset<PosT, 512, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+
+ // Disjoint (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using L = MakeMetaSet<Builder, -5, -3, -1, 1, 3, 5>;
+ using R = MakeMetaSet<Builder, -4, -2, 0, 2, 4>;
+
+ using Union = MetaSetUnion<L, R, 512>;
+
+ using ExpectedT =
+ MakeMetaSparseBitset<PosT, 512, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5>;
+
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+
+ // Intersecting.
+ {
+ using L = MakeMetaSet<Builder, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+ using R = MakeMetaSet<Builder, 2, 4, 6, 8, 10>;
+
+ using Union = MetaSetUnion<L, R, 512>;
+
+ using ExpectedT =
+ MakeMetaSparseBitset<PosT, 512, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+
+ // Intersecting (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using L = MakeMetaSet<Builder, -6, -4, -2, 0, 2, 4, 6>;
+ using R = MakeMetaSet<Builder, -4, -3, -2, -1, 0, 1, 2, 3, 4>;
+
+ using Union = MetaSetUnion<L, R, 512>;
+
+ using ExpectedT =
+ MakeMetaSparseBitset<PosT, 512, -6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6>;
+
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+
+ // Big gap.
+ if constexpr (ThisT::max() >= 10000) {
+ using Singletons = MakeMetaSet<Builder, 10000, 5000, 8000>;
+ using SmallVals = MakeMetaSet<Builder, 10, 20, 30, 40, 50, 60>;
+
+ using Union = MetaSetUnion<Singletons, SmallVals>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, 10, 20, 30, 40, 50, 60,
+ 5000, 8000, 10000>;
+ static_assert(std::is_same_v<Union, ExpectedT>);
+ }
+}
+
+TYPED_TEST(MetaSetTest, Intersect) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(Builder);
+ EXPOSE_TYPE(ThisT);
+
+ // Identity
+ {
+ using S = MakeMetaSet<Builder, 5, 10, 15, 65, 70, 75>;
+
+ using Intersection = MetaSetIntersection<S, S, 64>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 64, 5, 10, 15, 65, 70, 75>;
+
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+
+ // Identity (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using S = MakeMetaSet<Builder, -75, -70, -65, 5, 10>;
+
+ using Intersection = MetaSetIntersection<S, S, 64>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 64, -75, -70, -65, 5, 10>;
+
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+
+ // Disjoint
+ {
+ using L = MakeMetaSet<Builder, 1, 3, 5, 7, 9>;
+ using R = MakeMetaSet<Builder, 2, 4, 6, 8, 10>;
+
+ using Intersection = MetaSetIntersection<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512>;
+
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+
+ // Disjoint (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using L = MakeMetaSet<Builder, -5, -3, -1, 1, 3, 5>;
+ using R = MakeMetaSet<Builder, -4, -2, 0, 2, 4>;
+
+ using Intersection = MetaSetIntersection<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512>;
+
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+
+ // Intersecting.
+ {
+ using L = MakeMetaSet<Builder, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+ using R = MakeMetaSet<Builder, 2, 4, 6, 8, 10>;
+
+ using Intersection = MetaSetIntersection<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, 2, 4, 6, 8, 10>;
+
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+
+ // Intersecting (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using L = MakeMetaSet<Builder, -6, -4, -2, 0, 2, 4, 6>;
+ using R = MakeMetaSet<Builder, -4, -3, -2, -1, 0, 1, 2, 3, 4>;
+
+ using Intersection = MetaSetIntersection<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, -4, -2, 0, 2, 4>;
+
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+
+ // Big gap.
+ if constexpr (ThisT::max() >= 10000) {
+ using Singletons = MakeMetaSet<Builder, 10000, 5000, 8000>;
+ using SmallVals = MakeMetaSet<Builder, 10, 20, 30, 40, 50, 60>;
+
+ using Intersection = MetaSetIntersection<Singletons, SmallVals>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512>;
+ static_assert(std::is_same_v<Intersection, ExpectedT>);
+ }
+}
+
+TYPED_TEST(MetaSetTest, Minus) {
+ EXPOSE_TYPE(PosT);
+ EXPOSE_TYPE(Builder);
+ EXPOSE_TYPE(ThisT);
+
+ // Identity
+ {
+ using S = MakeMetaSet<Builder, 5, 10, 15, 65, 70, 75>;
+
+ using Minus = MetaSetMinus<S, S, 64>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 64>;
+
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+
+ // Identity (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using S = MakeMetaSet<Builder, -75, -70, -65, 5, 10>;
+
+ using Minus = MetaSetMinus<S, S, 64>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 64>;
+
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+
+ // Disjoint
+ {
+ using L = MakeMetaSet<Builder, 1, 3, 5, 7, 9>;
+ using R = MakeMetaSet<Builder, 2, 4, 6, 8, 10>;
+
+ using Minus = MetaSetMinus<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, 1, 3, 5, 7, 9>;
+
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+
+ // Disjoint (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using L = MakeMetaSet<Builder, -5, -3, -1, 1, 3, 5>;
+ using R = MakeMetaSet<Builder, -4, -2, 0, 2, 4>;
+
+ using Minus = MetaSetMinus<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, -5, -3, -1, 1, 3, 5>;
+
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+
+ // Intersecting.
+ {
+ using L = MakeMetaSet<Builder, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>;
+ using R = MakeMetaSet<Builder, 2, 4, 6, 8, 10>;
+
+ using Minus = MetaSetMinus<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, 1, 3, 5, 7, 9>;
+
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+
+ // Intersecting (signed)
+ if constexpr (std::is_signed_v<PosT>) {
+ using L = MakeMetaSet<Builder, -6, -4, -2, 0, 2, 4, 6>;
+ using R = MakeMetaSet<Builder, -4, -3, -2, -1, 0, 1, 2, 3, 4>;
+
+ using Minus = MetaSetMinus<L, R, 512>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, -6, 6>;
+
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+
+ // Big gap.
+ if constexpr (ThisT::max() >= 10000) {
+ using Singletons = MakeMetaSet<Builder, 10000, 5000, 8000>;
+ using SmallVals = MakeMetaSet<Builder, 10, 20, 30, 40, 50, 60>;
+
+ using Minus = MetaSetMinus<Singletons, SmallVals>;
+
+ using ExpectedT = MakeMetaSparseBitset<PosT, 512, 10000, 5000, 8000>;
+ static_assert(std::is_same_v<Minus, ExpectedT>);
+ }
+}
+
+} // namespace
More information about the llvm-commits
mailing list