[utils]: Add EnumSet

Simple container for sets of enum values.

Change-Id: I9efe5fc15e9f179cb63b64f3783235a92013a2b3
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53083
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 6c91770..c39e38b 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -573,6 +573,7 @@
     "transform/var_for_dynamic_index.h",
     "transform/vertex_pulling.cc",
     "transform/vertex_pulling.h",
+    "utils/enum_set.h",
     "utils/get_or_create.h",
     "utils/hash.h",
     "utils/math.h",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9066b18..7c6ed45 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -335,6 +335,7 @@
   sem/vector_type.h
   sem/void_type.cc
   sem/void_type.h
+  utils/enum_set.h
   utils/get_or_create.h
   utils/hash.h
   utils/math.h
@@ -620,6 +621,7 @@
     sem/type_manager_test.cc
     sem/u32_type_test.cc
     sem/vector_type_test.cc
+    utils/enum_set_test.cc
     utils/get_or_create_test.cc
     utils/hash_test.cc
     utils/io/command_test.cc
diff --git a/src/utils/enum_set.h b/src/utils/enum_set.h
new file mode 100644
index 0000000..396e911
--- /dev/null
+++ b/src/utils/enum_set.h
@@ -0,0 +1,79 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_UTILS_ENUM_SET_H_
+#define SRC_UTILS_ENUM_SET_H_
+
+#include <cstdint>
+#include <type_traits>
+
+namespace tint {
+namespace utils {
+
+/// EnumSet is a set of enum values.
+/// @note As the EnumSet is backed by a single uint64_t value, it can only hold
+/// enum values in the range [0 .. 63].
+template <typename ENUM>
+struct EnumSet {
+ public:
+  /// Enum is the enum type this EnumSet wraps
+  using Enum = ENUM;
+
+  /// Constructor. Initializes the EnumSet with zero.
+  constexpr EnumSet() = default;
+
+  /// Constructor. Initializes the EnumSet with the given values.
+  /// @param values the enumerator values to construct the set with
+  template <typename... VALUES>
+  explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
+
+  /// Adds e to this set
+  /// @param e the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& Add(Enum e) {
+    set |= Bit(e);
+    return *this;
+  }
+
+  /// Removes e from this set
+  /// @param e the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& Remove(Enum e) {
+    set &= ~Bit(e);
+    return *this;
+  }
+
+  /// @param e the enum value
+  /// @return true if the set contains `e`
+  inline bool Contains(Enum e) { return (set & Bit(e)) != 0; }
+
+ private:
+  static constexpr uint64_t Bit(Enum value) {
+    return static_cast<uint64_t>(1) << static_cast<uint64_t>(value);
+  }
+
+  static constexpr uint64_t Union() { return 0; }
+
+  template <typename FIRST, typename... VALUES>
+  static constexpr uint64_t Union(FIRST first, VALUES... values) {
+    return Bit(first) | Union(values...);
+  }
+
+  uint64_t set = 0;
+};
+
+}  // namespace utils
+}  // namespace tint
+
+#endif  // SRC_UTILS_ENUM_SET_H_
diff --git a/src/utils/enum_set_test.cc b/src/utils/enum_set_test.cc
new file mode 100644
index 0000000..d53b834
--- /dev/null
+++ b/src/utils/enum_set_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/utils/enum_set.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace utils {
+namespace {
+
+enum class E { A, B, C };
+
+TEST(EnumSetTest, ConstructEmpty) {
+  EnumSet<E> set;
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, ConstructWithSingle) {
+  EnumSet<E> set(E::B);
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, ConstructWithMultiple) {
+  EnumSet<E> set(E::A, E::C);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, Add) {
+  EnumSet<E> set;
+  set.Add(E::B);
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, Remove) {
+  EnumSet<E> set(E::A, E::B);
+  set.Remove(E::B);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace tint
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 12a5fa5..ef868a6 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -287,6 +287,7 @@
     "../src/transform/transform_test.cc",
     "../src/transform/var_for_dynamic_index_test.cc",
     "../src/transform/vertex_pulling_test.cc",
+    "../src/utils/enum_set_test.cc",
     "../src/utils/get_or_create_test.cc",
     "../src/utils/hash_test.cc",
     "../src/utils/io/command_test.cc",