[llvm] 18baeb8 - [ADT] Alternative way to declare enum type as bitmask
Serge Pavlov via llvm-commits
llvm-commits at lists.llvm.org
Mon Feb 20 22:45:48 PST 2023
Author: Serge Pavlov
Date: 2023-02-21T13:44:24+07:00
New Revision: 18baeb89b566a1c0011c6a9960f402bd151224dd
URL: https://github.com/llvm/llvm-project/commit/18baeb89b566a1c0011c6a9960f402bd151224dd
DIFF: https://github.com/llvm/llvm-project/commit/18baeb89b566a1c0011c6a9960f402bd151224dd.diff
LOG: [ADT] Alternative way to declare enum type as bitmask
If an enumeration represents a set of bit flags, using the macro
LLVM_MARK_AS_BITMASK_ENUM can make operations with such enumeration more
convenient. It however brings problems if the enumeration is non-scoped.
As the macro adds an item LLVM_BITMASK_LARGEST_ENUMERATOR to the
enumeration type, only one such type may be declared as bitmask. This
problem could be solved by convertion of the enumeration to scoped, but
it requires static_casts in new places and the convenience can be
eliminated.
This change introduces a new macro LLVM_DECLARE_ENUM_AS_BITMASK, which
allows non-invasive convertion of an enumeration into bitmask. It
provides specialization to trait classes, which previously were built
based on presence of LLVM_BITMASK_LARGEST_ENUMERATOR in the enumeration.
The macro must be specified in global or llvm namespace because the
trait classes are declared in llvm namespace.
Differential Revision: https://reviews.llvm.org/D144202
Added:
Modified:
llvm/include/llvm/ADT/BitmaskEnum.h
llvm/unittests/ADT/BitmaskEnumTest.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/ADT/BitmaskEnum.h b/llvm/include/llvm/ADT/BitmaskEnum.h
index 205da1240d44e..1510d017b1e39 100644
--- a/llvm/include/llvm/ADT/BitmaskEnum.h
+++ b/llvm/include/llvm/ADT/BitmaskEnum.h
@@ -41,6 +41,33 @@
#define LLVM_MARK_AS_BITMASK_ENUM(LargestValue) \
LLVM_BITMASK_LARGEST_ENUMERATOR = LargestValue
+/// LLVM_DECLARE_ENUM_AS_BITMASK can be used to declare an enum type as a bit
+/// set, so that bitwise operation on such enum does not require static_cast.
+///
+/// \code
+/// enum MyEnum { E1 = 1, E2 = 2, E3 = 4, E4 = 8 };
+/// LLVM_DECLARE_ENUM_AS_BITMASK(MyEnum, E4);
+///
+/// void Foo() {
+/// MyEnum A = (E1 | E2) & E3 ^ ~E4; // No static_cast
+/// }
+/// \endcode
+///
+/// The second parameter to LLVM_DECLARE_ENUM_AS_BITMASK specifies the largest
+/// bit value of the enum type.
+///
+/// LLVM_DECLARE_ENUM_AS_BITMASK should be used in global or llvm namespace.
+///
+/// This a non-intrusive alternative for LLVM_MARK_AS_BITMASK_ENUM. It allows
+/// declaring more than one non-scoped enumerations as bitmask types in the same
+/// scope. Otherwise it provides the same functionality as
+/// LLVM_MARK_AS_BITMASK_ENUM.
+#define LLVM_DECLARE_ENUM_AS_BITMASK(Enum, LargestValue) \
+ template <> struct llvm::is_bitmask_enum<Enum> : std::true_type {}; \
+ template <> struct llvm::largest_bitmask_enum_bit<Enum> { \
+ static constexpr std::underlying_type_t<Enum> value = LargestValue; \
+ }
+
/// LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE() pulls the operator overloads used
/// by LLVM_MARK_AS_BITMASK_ENUM into the current namespace.
///
@@ -73,6 +100,18 @@ template <typename E>
struct is_bitmask_enum<
E, std::enable_if_t<sizeof(E::LLVM_BITMASK_LARGEST_ENUMERATOR) >= 0>>
: std::true_type {};
+
+/// Trait class to determine bitmask enumeration largest bit.
+template <typename E, typename Enable = void> struct largest_bitmask_enum_bit;
+
+template <typename E>
+struct largest_bitmask_enum_bit<
+ E, std::enable_if_t<sizeof(E::LLVM_BITMASK_LARGEST_ENUMERATOR) >= 0>> {
+ using UnderlyingTy = std::underlying_type_t<E>;
+ static constexpr UnderlyingTy value =
+ static_cast<UnderlyingTy>(E::LLVM_BITMASK_LARGEST_ENUMERATOR);
+};
+
namespace BitmaskEnumDetail {
/// Get a bitmask with 1s in all places up to the high-order bit of E's largest
@@ -80,9 +119,7 @@ namespace BitmaskEnumDetail {
template <typename E> constexpr std::underlying_type_t<E> Mask() {
// On overflow, NextPowerOf2 returns zero with the type uint64_t, so
// subtracting 1 gives us the mask with all bits set, like we want.
- return NextPowerOf2(static_cast<std::underlying_type_t<E>>(
- E::LLVM_BITMASK_LARGEST_ENUMERATOR)) -
- 1;
+ return NextPowerOf2(largest_bitmask_enum_bit<E>::value) - 1;
}
/// Check that Val is in range for E, and return Val cast to E's underlying
diff --git a/llvm/unittests/ADT/BitmaskEnumTest.cpp b/llvm/unittests/ADT/BitmaskEnumTest.cpp
index 266a3f6310a31..54af9fad4b88f 100644
--- a/llvm/unittests/ADT/BitmaskEnumTest.cpp
+++ b/llvm/unittests/ADT/BitmaskEnumTest.cpp
@@ -21,12 +21,30 @@ enum Flags {
LLVM_MARK_AS_BITMASK_ENUM(F4)
};
+static_assert(is_bitmask_enum<Flags>::value != 0);
+static_assert(largest_bitmask_enum_bit<Flags>::value == Flags::F4);
+
+enum Flags2 { V0 = 0, V1 = 1, V2 = 2, V3 = 4, V4 = 8 };
+} // namespace
+
+LLVM_DECLARE_ENUM_AS_BITMASK(Flags2, V4);
+
+static_assert(is_bitmask_enum<Flags>::value != 0);
+static_assert(largest_bitmask_enum_bit<Flags>::value == Flags::F4);
+
+namespace {
TEST(BitmaskEnumTest, BitwiseOr) {
Flags f = F1 | F2;
EXPECT_EQ(3, f);
f = f | F3;
EXPECT_EQ(7, f);
+
+ Flags2 f2 = V1 | V2;
+ EXPECT_EQ(3, f2);
+
+ f2 = f2 | V3;
+ EXPECT_EQ(7, f2);
}
TEST(BitmaskEnumTest, BitwiseOrEquals) {
@@ -38,6 +56,14 @@ TEST(BitmaskEnumTest, BitwiseOrEquals) {
f = F2;
(f |= F3) = F1;
EXPECT_EQ(F1, f);
+
+ Flags2 f2 = V1;
+ f2 |= V3;
+ EXPECT_EQ(5, f2);
+
+ f2 = V2;
+ (f2 |= V3) = V1;
+ EXPECT_EQ(V1, f2);
}
TEST(BitmaskEnumTest, BitwiseAnd) {
@@ -46,6 +72,12 @@ TEST(BitmaskEnumTest, BitwiseAnd) {
f = (f | F3) & (F1 | F2 | F3);
EXPECT_EQ(6, f);
+
+ Flags2 f2 = static_cast<Flags2>(3) & V2;
+ EXPECT_EQ(V2, f2);
+
+ f2 = (f2 | V3) & (V1 | V2 | V3);
+ EXPECT_EQ(6, f2);
}
TEST(BitmaskEnumTest, BitwiseAndEquals) {
@@ -56,6 +88,13 @@ TEST(BitmaskEnumTest, BitwiseAndEquals) {
// &= should return a reference to the LHS.
(f &= F1) = F3;
EXPECT_EQ(F3, f);
+
+ Flags2 f2 = V1 | V2 | V3;
+ f2 &= V1 | V2;
+ EXPECT_EQ(3, f2);
+
+ (f2 &= V1) = V3;
+ EXPECT_EQ(V3, f2);
}
TEST(BitmaskEnumTest, BitwiseXor) {
@@ -64,6 +103,12 @@ TEST(BitmaskEnumTest, BitwiseXor) {
f = f ^ F1;
EXPECT_EQ(4, f);
+
+ Flags2 f2 = (V1 | V2) ^ (V2 | V3);
+ EXPECT_EQ(5, f2);
+
+ f2 = f2 ^ V1;
+ EXPECT_EQ(4, f2);
}
TEST(BitmaskEnumTest, BitwiseXorEquals) {
@@ -74,6 +119,13 @@ TEST(BitmaskEnumTest, BitwiseXorEquals) {
// ^= should return a reference to the LHS.
(f ^= F4) = F3;
EXPECT_EQ(F3, f);
+
+ Flags2 f2 = (V1 | V2);
+ f2 ^= (V2 | V4);
+ EXPECT_EQ(9, f2);
+
+ (f2 ^= V4) = V3;
+ EXPECT_EQ(V3, f2);
}
TEST(BitmaskEnumTest, ConstantExpression) {
@@ -85,12 +137,25 @@ TEST(BitmaskEnumTest, ConstantExpression) {
EXPECT_EQ(f2, F1 | F2);
EXPECT_EQ(f3, F1 & F2);
EXPECT_EQ(f4, F1 ^ F2);
+
+ constexpr Flags2 f21 = ~V1;
+ constexpr Flags2 f22 = V1 | V2;
+ constexpr Flags2 f23 = V1 & V2;
+ constexpr Flags2 f24 = V1 ^ V2;
+ EXPECT_EQ(f21, ~V1);
+ EXPECT_EQ(f22, V1 | V2);
+ EXPECT_EQ(f23, V1 & V2);
+ EXPECT_EQ(f24, V1 ^ V2);
}
TEST(BitmaskEnumTest, BitwiseNot) {
Flags f = ~F1;
EXPECT_EQ(14, f); // Largest value for f is 15.
EXPECT_EQ(15, ~F0);
+
+ Flags2 f2 = ~V1;
+ EXPECT_EQ(14, f2);
+ EXPECT_EQ(15, ~V0);
}
enum class FlagsClass {
More information about the llvm-commits
mailing list