[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