Add TypedInteger

This CL adds a TypedInteger helper which provides additional type
safety in Dawn. It is a compile-time restriction that prevents integers
of different types from being used interchangably in Debug builds.

It also adds ityp::{array,bitset,span} as helper classes to wrap std::
versions (not span). These accept a template paramter as the Index type
so that typed integers, or enum classes, may be used as a type-safe
index.

For now, bind group layout binding indices use TypedInteger. Future
CLs will convert other indices to be type-safe as well.

Bug: dawn:442
Change-Id: I5b63b1e4f6154322db0227a7788a4e9b8303410e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/19902
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn
index 244baeb..59f6ae4 100644
--- a/src/common/BUILD.gn
+++ b/src/common/BUILD.gn
@@ -167,6 +167,10 @@
       "SwapChainUtils.h",
       "SystemUtils.cpp",
       "SystemUtils.h",
+      "TypedInteger.h",
+      "ityp_array.h",
+      "ityp_bitset.h",
+      "ityp_span.h",
       "vulkan_platform.h",
       "windows_with_undefs.h",
       "xlib_with_undefs.h",
diff --git a/src/common/BitSetIterator.h b/src/common/BitSetIterator.h
index 1a7fd60..d35bc8a 100644
--- a/src/common/BitSetIterator.h
+++ b/src/common/BitSetIterator.h
@@ -17,6 +17,7 @@
 
 #include "common/Assert.h"
 #include "common/Math.h"
+#include "common/UnderlyingType.h"
 
 #include <bitset>
 #include <limits>
@@ -44,8 +45,11 @@
 
         bool operator==(const Iterator& other) const;
         bool operator!=(const Iterator& other) const;
+
         T operator*() const {
-            return static_cast<T>(mCurrentBit);
+            using U = UnderlyingType<T>;
+            ASSERT(mCurrentBit <= std::numeric_limits<U>::max());
+            return static_cast<T>(static_cast<U>(mCurrentBit));
         }
 
       private:
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 2e909b3..1ab2023 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -44,6 +44,11 @@
     "SwapChainUtils.h"
     "SystemUtils.cpp"
     "SystemUtils.h"
+    "TypedInteger.h"
+    "UnderlyingType.h"
+    "ityp_array.h"
+    "ityp_bitset.h"
+    "ityp_span.h"
     "vulkan_platform.h"
     "windows_with_undefs.h"
     "xlib_with_undefs.h"
diff --git a/src/common/TypedInteger.h b/src/common/TypedInteger.h
new file mode 100644
index 0000000..05450ec
--- /dev/null
+++ b/src/common/TypedInteger.h
@@ -0,0 +1,217 @@
+// Copyright 2020 The Dawn 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 COMMON_TYPEDINTEGER_H_
+#define COMMON_TYPEDINTEGER_H_
+
+#include "common/Assert.h"
+
+#include <limits>
+#include <type_traits>
+
+// TypedInteger is helper class that provides additional type safety in Debug.
+//  - Integers of different (Tag, BaseIntegerType) may not be used interoperably
+//  - Allows casts only to the underlying type.
+//  - Integers of the same (Tag, BaseIntegerType) may be compared or assigned.
+// This class helps ensure that the many types of indices in Dawn aren't mixed up and used
+// interchangably.
+// In Release builds, when DAWN_ENABLE_ASSERTS is not defined, TypedInteger is a passthrough
+// typedef of the underlying type.
+//
+// Example:
+//     using UintA = TypedInteger<struct TypeA, uint32_t>;
+//     using UintB = TypedInteger<struct TypeB, uint32_t>;
+//
+//  in Release:
+//     using UintA = uint32_t;
+//     using UintB = uint32_t;
+//
+//  in Debug:
+//     using UintA = detail::TypedIntegerImpl<struct TypeA, uint32_t>;
+//     using UintB = detail::TypedIntegerImpl<struct TypeB, uint32_t>;
+//
+//     Assignment, construction, comparison, and arithmetic with TypedIntegerImpl are allowed
+//     only for typed integers of exactly the same type. Further, they must be
+//     created / cast explicitly; there is no implicit conversion.
+//
+//     UintA a(2);
+//     uint32_t aValue = static_cast<uint32_t>(a);
+//
+namespace detail {
+    template <typename Tag, typename T>
+    class TypedIntegerImpl;
+}  // namespace detail
+
+template <typename Tag, typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
+#if defined(DAWN_ENABLE_ASSERTS)
+using TypedInteger = detail::TypedIntegerImpl<Tag, T>;
+#else
+using TypedInteger = T;
+#endif
+
+namespace detail {
+    template <typename Tag, typename T>
+    class TypedIntegerImpl {
+        static_assert(std::is_integral<T>::value, "TypedInteger must be integral");
+        T mValue;
+
+      public:
+        constexpr TypedIntegerImpl() : mValue(0) {
+        }
+
+        // Construction from non-narrowing integral types.
+        template <typename I,
+                  typename = std::enable_if_t<
+                      std::is_integral<I>::value &&
+                      std::numeric_limits<I>::max() <= std::numeric_limits<T>::max() &&
+                      std::numeric_limits<I>::min() >= std::numeric_limits<T>::min()>>
+        explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast<T>(rhs)) {
+        }
+
+        // Allow explicit casts only to the underlying type. If you're casting out of an
+        // TypedInteger, you should know what what you're doing, and exactly what type you
+        // expect.
+        explicit constexpr operator T() const {
+            return static_cast<T>(this->mValue);
+        }
+
+// Same-tag TypedInteger comparison operators
+#define TYPED_COMPARISON(op)                                        \
+    constexpr bool operator op(const TypedIntegerImpl& rhs) const { \
+        return mValue op rhs.mValue;                                \
+    }
+        TYPED_COMPARISON(<)
+        TYPED_COMPARISON(<=)
+        TYPED_COMPARISON(>)
+        TYPED_COMPARISON(>=)
+        TYPED_COMPARISON(==)
+        TYPED_COMPARISON(!=)
+#undef TYPED_COMPARISON
+
+        // Increment / decrement operators for for-loop iteration
+        constexpr TypedIntegerImpl& operator++() {
+            ASSERT(this->mValue < std::numeric_limits<T>::max());
+            ++this->mValue;
+            return *this;
+        }
+
+        constexpr TypedIntegerImpl operator++(int) {
+            TypedIntegerImpl ret = *this;
+
+            ASSERT(this->mValue < std::numeric_limits<T>::max());
+            ++this->mValue;
+            return ret;
+        }
+
+        constexpr TypedIntegerImpl& operator--() {
+            assert(this->mValue > std::numeric_limits<T>::min());
+            --this->mValue;
+            return *this;
+        }
+
+        constexpr TypedIntegerImpl operator--(int) {
+            TypedIntegerImpl ret = *this;
+
+            ASSERT(this->mValue > std::numeric_limits<T>::min());
+            --this->mValue;
+            return ret;
+        }
+
+        template <typename T2 = T>
+        constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator-() const {
+            static_assert(std::is_same<T, T2>::value, "");
+            // The negation of the most negative value cannot be represented.
+            ASSERT(this->mValue != std::numeric_limits<T>::min());
+            return TypedIntegerImpl(-this->mValue);
+        }
+
+        template <typename T2 = T>
+        constexpr std::enable_if_t<std::is_unsigned<T2>::value, TypedIntegerImpl> operator+(
+            TypedIntegerImpl rhs) const {
+            static_assert(std::is_same<T, T2>::value, "");
+            // Overflow would wrap around
+            ASSERT(this->mValue + rhs.mValue >= this->mValue);
+
+            return TypedIntegerImpl(this->mValue + rhs.mValue);
+        }
+
+        template <typename T2 = T>
+        constexpr std::enable_if_t<std::is_unsigned<T2>::value, TypedIntegerImpl> operator-(
+            TypedIntegerImpl rhs) const {
+            static_assert(std::is_same<T, T2>::value, "");
+            // Overflow would wrap around
+            ASSERT(this->mValue - rhs.mValue <= this->mValue);
+            return TypedIntegerImpl(this->mValue - rhs.mValue);
+        }
+
+        template <typename T2 = T>
+        constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator+(
+            TypedIntegerImpl rhs) const {
+            static_assert(std::is_same<T, T2>::value, "");
+            if (this->mValue > 0) {
+                // rhs is positive: |rhs| is at most the distance between max and |this|.
+                // rhs is negative: (positive + negative) won't overflow
+                ASSERT(rhs.mValue <= std::numeric_limits<T>::max() - this->mValue);
+            } else {
+                // rhs is postive: (negative + positive) won't underflow
+                // rhs is negative: |rhs| isn't less than the (negative) distance between min
+                // and |this|
+                ASSERT(rhs.mValue >= std::numeric_limits<T>::min() - this->mValue);
+            }
+            return TypedIntegerImpl(this->mValue + rhs.mValue);
+        }
+
+        template <typename T2 = T>
+        constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator-(
+            TypedIntegerImpl rhs) const {
+            static_assert(std::is_same<T, T2>::value, "");
+            if (this->mValue > 0) {
+                // rhs is positive: positive minus positive won't overflow
+                // rhs is negative: |rhs| isn't less than the (negative) distance between |this|
+                // and max.
+                ASSERT(rhs.mValue >= this->mValue - std::numeric_limits<T>::max());
+            } else {
+                // rhs is positive: |rhs| is at most the distance between min and |this|
+                // rhs is negative: negative minus negative won't overflow
+                ASSERT(rhs.mValue <= this->mValue - std::numeric_limits<T>::min());
+            }
+            return TypedIntegerImpl(this->mValue - rhs.mValue);
+        }
+    };
+
+}  // namespace detail
+
+namespace std {
+    template <typename Tag, typename T>
+    class numeric_limits<detail::TypedIntegerImpl<Tag, T>> : public numeric_limits<T> {
+      public:
+        static detail::TypedIntegerImpl<Tag, T> max() noexcept {
+            return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::max());
+        }
+        static detail::TypedIntegerImpl<Tag, T> min() noexcept {
+            return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::min());
+        }
+    };
+
+    template <typename Tag, typename T>
+    class hash<detail::TypedIntegerImpl<Tag, T>> : private hash<T> {
+      public:
+        size_t operator()(detail::TypedIntegerImpl<Tag, T> value) const {
+            return hash<T>::operator()(static_cast<T>(value));
+        }
+    };
+
+}  // namespace std
+
+#endif  // COMMON_TYPEDINTEGER_H_
diff --git a/src/common/UnderlyingType.h b/src/common/UnderlyingType.h
new file mode 100644
index 0000000..09c72c0
--- /dev/null
+++ b/src/common/UnderlyingType.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Dawn 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 COMMON_UNDERLYINGTYPE_H_
+#define COMMON_UNDERLYINGTYPE_H_
+
+#include <type_traits>
+
+// UnderlyingType is similar to std::underlying_type_t. It is a passthrough for already
+// integer types which simplifies getting the underlying primitive type for an arbitrary
+// template parameter. It includes a specialization for detail::TypedIntegerImpl which yields
+// the wrapped integer type.
+namespace detail {
+    template <typename T, typename Enable = void>
+    struct UnderlyingTypeImpl;
+
+    template <typename I>
+    struct UnderlyingTypeImpl<I, typename std::enable_if_t<std::is_integral<I>::value>> {
+        using type = I;
+    };
+
+    template <typename E>
+    struct UnderlyingTypeImpl<E, typename std::enable_if_t<std::is_enum<E>::value>> {
+        using type = std::underlying_type_t<E>;
+    };
+
+    // Forward declare the TypedInteger impl.
+    template <typename Tag, typename T>
+    class TypedIntegerImpl;
+
+    template <typename Tag, typename I>
+    struct UnderlyingTypeImpl<TypedIntegerImpl<Tag, I>> {
+        using type = typename UnderlyingTypeImpl<I>::type;
+    };
+}  // namespace detail
+
+template <typename T>
+using UnderlyingType = typename detail::UnderlyingTypeImpl<T>::type;
+
+#endif  // COMMON_UNDERLYINGTYPE_H_
diff --git a/src/common/ityp_array.h b/src/common/ityp_array.h
new file mode 100644
index 0000000..d413ebc
--- /dev/null
+++ b/src/common/ityp_array.h
@@ -0,0 +1,96 @@
+// Copyright 2020 The Dawn 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 COMMON_ITYP_ARRAY_H_
+#define COMMON_ITYP_ARRAY_H_
+
+#include "common/TypedInteger.h"
+#include "common/UnderlyingType.h"
+
+#include <array>
+#include <type_traits>
+
+namespace ityp {
+
+    // ityp::array is a helper class that wraps std::array with the restriction that
+    // indices must be a particular type |Index|. Dawn uses multiple flat maps of
+    // index-->data, and this class helps ensure an indices cannot be passed interchangably
+    // to a flat map of a different type.
+    template <typename Index, typename Value, size_t Size>
+    class array : private std::array<Value, Size> {
+        using I = UnderlyingType<Index>;
+        using Base = std::array<Value, Size>;
+
+        static_assert(Size <= std::numeric_limits<I>::max(), "");
+
+      public:
+        constexpr array() = default;
+
+        template <typename... Values>
+        constexpr array(Values&&... values) : Base{std::forward<Values>(values)...} {
+        }
+
+        Value& operator[](Index i) {
+            I index = static_cast<I>(i);
+            ASSERT(index >= 0 && index < Size);
+            return Base::operator[](index);
+        }
+
+        constexpr const Value& operator[](Index i) const {
+            I index = static_cast<I>(i);
+            ASSERT(index >= 0 && index < Size);
+            return Base::operator[](index);
+        }
+
+        Value& at(Index i) {
+            I index = static_cast<I>(i);
+            ASSERT(index >= 0 && index < Size);
+            return Base::at(index);
+        }
+
+        constexpr const Value& at(Index i) const {
+            I index = static_cast<I>(i);
+            ASSERT(index >= 0 && index < Size);
+            return Base::at(index);
+        }
+
+        Value* begin() noexcept {
+            return Base::begin();
+        }
+
+        const Value* begin() const noexcept {
+            return Base::begin();
+        }
+
+        Value* end() noexcept {
+            return Base::end();
+        }
+
+        const Value* end() const noexcept {
+            return Base::end();
+        }
+
+        constexpr Index size() const {
+            return Index(static_cast<I>(Size));
+        }
+
+        using Base::back;
+        using Base::data;
+        using Base::empty;
+        using Base::front;
+    };
+
+}  // namespace ityp
+
+#endif  // COMMON_ITYP_ARRAY_H_
diff --git a/src/common/ityp_bitset.h b/src/common/ityp_bitset.h
new file mode 100644
index 0000000..46badc9
--- /dev/null
+++ b/src/common/ityp_bitset.h
@@ -0,0 +1,124 @@
+// Copyright 2020 The Dawn 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 COMMON_ITYP_BITSET_H_
+#define COMMON_ITYP_BITSET_H_
+
+#include "common/BitSetIterator.h"
+#include "common/TypedInteger.h"
+#include "common/UnderlyingType.h"
+
+namespace ityp {
+
+    // ityp::bitset is a helper class that wraps std::bitset with the restriction that
+    // indices must be a particular type |Index|.
+    template <typename Index, size_t N>
+    class bitset : private std::bitset<N> {
+        using I = UnderlyingType<Index>;
+        using Base = std::bitset<N>;
+
+        static_assert(sizeof(I) <= sizeof(size_t), "");
+
+        constexpr bitset(const Base& rhs) : Base(rhs) {
+        }
+
+      public:
+        constexpr bitset() noexcept : Base() {
+        }
+
+        constexpr bitset(unsigned long long value) noexcept : Base(value) {
+        }
+
+        constexpr bool operator[](Index i) const {
+            return Base::operator[](static_cast<I>(i));
+        }
+
+        typename Base::reference operator[](Index i) {
+            return Base::operator[](static_cast<I>(i));
+        }
+
+        bool test(Index i) const {
+            return Base::test(static_cast<I>(i));
+        }
+
+        using Base::all;
+        using Base::any;
+        using Base::count;
+        using Base::none;
+        using Base::size;
+
+        bitset& operator&=(const bitset& other) noexcept {
+            return static_cast<bitset&>(Base::operator&=(static_cast<const Base&>(other)));
+        }
+
+        bitset& operator|=(const bitset& other) noexcept {
+            return static_cast<bitset&>(Base::operator|=(static_cast<const Base&>(other)));
+        }
+
+        bitset& operator^=(const bitset& other) noexcept {
+            return static_cast<bitset&>(Base::operator^=(static_cast<const Base&>(other)));
+        }
+
+        bitset operator~() const noexcept {
+            return bitset(*this).flip();
+        }
+
+        bitset& set() noexcept {
+            return static_cast<bitset&>(Base::set());
+        }
+
+        bitset& set(Index i, bool value = true) {
+            return static_cast<bitset&>(Base::set(static_cast<I>(i), value));
+        }
+
+        bitset& reset() noexcept {
+            return static_cast<bitset&>(Base::reset());
+        }
+
+        bitset& reset(Index i) {
+            return static_cast<bitset&>(Base::reset(static_cast<I>(i)));
+        }
+
+        bitset& flip() noexcept {
+            return static_cast<bitset&>(Base::flip());
+        }
+
+        bitset& flip(Index i) {
+            return static_cast<bitset&>(Base::flip(static_cast<I>(i)));
+        }
+
+        using Base::to_string;
+        using Base::to_ullong;
+        using Base::to_ulong;
+
+        friend bitset operator&(const bitset& lhs, const bitset& rhs) noexcept {
+            return bitset(static_cast<const Base&>(lhs) & static_cast<const Base&>(rhs));
+        }
+
+        friend bitset operator|(const bitset& lhs, const bitset& rhs) noexcept {
+            return bitset(static_cast<const Base&>(lhs) | static_cast<const Base&>(rhs));
+        }
+
+        friend bitset operator^(const bitset& lhs, const bitset& rhs) noexcept {
+            return bitset(static_cast<const Base&>(lhs) ^ static_cast<const Base&>(rhs));
+        }
+
+        friend BitSetIterator<N, Index> IterateBitSet(const bitset& bitset) {
+            return BitSetIterator<N, Index>(static_cast<const Base&>(bitset));
+        }
+    };
+
+}  // namespace ityp
+
+#endif  // COMMON_ITYP_BITSET_H_
diff --git a/src/common/ityp_span.h b/src/common/ityp_span.h
new file mode 100644
index 0000000..00ba93f
--- /dev/null
+++ b/src/common/ityp_span.h
@@ -0,0 +1,103 @@
+// Copyright 2020 The Dawn 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 COMMON_ITYP_SPAN_H_
+#define COMMON_ITYP_SPAN_H_
+
+#include "common/TypedInteger.h"
+#include "common/UnderlyingType.h"
+
+#include <type_traits>
+
+namespace ityp {
+
+    // ityp::span is a helper class that wraps an unowned packed array of type |Value|.
+    // It stores the size and pointer to first element. It has the restriction that
+    // indices must be a particular type |Index|. This provides a type-safe way to index
+    // raw pointers.
+    template <typename Index, typename Value>
+    class span {
+        using I = UnderlyingType<Index>;
+
+      public:
+        constexpr span() : mData(nullptr), mSize(0) {
+        }
+        constexpr span(Value* data, Index size) : mData(data), mSize(size) {
+        }
+
+        constexpr Value& operator[](Index i) const {
+            ASSERT(i < mSize);
+            return mData[static_cast<I>(i)];
+        }
+
+        Value* data() noexcept {
+            return mData;
+        }
+
+        const Value* data() const noexcept {
+            return mData;
+        }
+
+        Value* begin() noexcept {
+            return mData;
+        }
+
+        const Value* begin() const noexcept {
+            return mData;
+        }
+
+        Value* end() noexcept {
+            return mData + static_cast<I>(mSize);
+        }
+
+        const Value* end() const noexcept {
+            return mData + static_cast<I>(mSize);
+        }
+
+        Value& front() {
+            ASSERT(mData != nullptr);
+            ASSERT(static_cast<I>(mSize) >= 0);
+            return *mData;
+        }
+
+        const Value& front() const {
+            ASSERT(mData != nullptr);
+            ASSERT(static_cast<I>(mSize) >= 0);
+            return *mData;
+        }
+
+        Value& back() {
+            ASSERT(mData != nullptr);
+            ASSERT(static_cast<I>(mSize) >= 0);
+            return *(mData + static_cast<I>(mSize) - 1);
+        }
+
+        const Value& back() const {
+            ASSERT(mData != nullptr);
+            ASSERT(static_cast<I>(mSize) >= 0);
+            return *(mData + static_cast<I>(mSize) - 1);
+        }
+
+        Index size() const {
+            return mSize;
+        }
+
+      private:
+        Value* mData;
+        Index mSize;
+    };
+
+}  // namespace ityp
+
+#endif  // COMMON_ITYP_SPAN_H_
diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp
index d239372..feb4abd 100644
--- a/src/dawn_native/BindGroup.cpp
+++ b/src/dawn_native/BindGroup.cpp
@@ -16,6 +16,7 @@
 
 #include "common/Assert.h"
 #include "common/Math.h"
+#include "common/ityp_bitset.h"
 #include "dawn_native/BindGroupLayout.h"
 #include "dawn_native/Buffer.h"
 #include "dawn_native/Device.h"
@@ -153,13 +154,14 @@
         }
 
         DAWN_TRY(device->ValidateObject(descriptor->layout));
-        if (descriptor->entryCount != descriptor->layout->GetBindingCount()) {
+
+        if (BindingIndex(descriptor->entryCount) != descriptor->layout->GetBindingCount()) {
             return DAWN_VALIDATION_ERROR("numBindings mismatch");
         }
 
         const BindGroupLayoutBase::BindingMap& bindingMap = descriptor->layout->GetBindingMap();
 
-        std::bitset<kMaxBindingsPerGroup> bindingsSet;
+        ityp::bitset<BindingIndex, kMaxBindingsPerGroup> bindingsSet;
         for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
             const BindGroupEntry& entry = descriptor->entries[i];
 
@@ -223,7 +225,7 @@
         : ObjectBase(device),
           mLayout(descriptor->layout),
           mBindingData(mLayout->ComputeBindingDataPointers(bindingDataStart)) {
-        for (BindingIndex i = 0; i < mLayout->GetBindingCount(); ++i) {
+        for (BindingIndex i{0}; i < mLayout->GetBindingCount(); ++i) {
             // TODO(enga): Shouldn't be needed when bindings are tightly packed.
             // This is to fill Ref<ObjectBase> holes with nullptrs.
             new (&mBindingData.bindings[i]) Ref<ObjectBase>();
@@ -267,7 +269,7 @@
     BindGroupBase::~BindGroupBase() {
         if (mLayout) {
             ASSERT(!IsError());
-            for (BindingIndex i = 0; i < mLayout->GetBindingCount(); ++i) {
+            for (BindingIndex i{0}; i < mLayout->GetBindingCount(); ++i) {
                 mBindingData.bindings[i].~Ref<ObjectBase>();
             }
         }
diff --git a/src/dawn_native/BindGroupAndStorageBarrierTracker.h b/src/dawn_native/BindGroupAndStorageBarrierTracker.h
index 8227d84..f04dd64 100644
--- a/src/dawn_native/BindGroupAndStorageBarrierTracker.h
+++ b/src/dawn_native/BindGroupAndStorageBarrierTracker.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_BINDGROUPANDSTORAGEBARRIERTRACKER_H_
 #define DAWNNATIVE_BINDGROUPANDSTORAGEBARRIERTRACKER_H_
 
+#include "common/ityp_bitset.h"
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/BindGroupTracker.h"
 #include "dawn_native/Buffer.h"
@@ -41,7 +42,7 @@
 
                 const BindGroupLayoutBase* layout = bindGroup->GetLayout();
 
-                for (BindingIndex bindingIndex = 0; bindingIndex < layout->GetBindingCount();
+                for (BindingIndex bindingIndex{0}; bindingIndex < layout->GetBindingCount();
                      ++bindingIndex) {
                     const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex);
 
@@ -88,10 +89,13 @@
         }
 
       protected:
-        std::array<std::bitset<kMaxBindingsPerGroup>, kMaxBindGroups> mBindingsNeedingBarrier = {};
-        std::array<std::array<wgpu::BindingType, kMaxBindingsPerGroup>, kMaxBindGroups>
+        std::array<ityp::bitset<BindingIndex, kMaxBindingsPerGroup>, kMaxBindGroups>
+            mBindingsNeedingBarrier = {};
+        std::array<ityp::array<BindingIndex, wgpu::BindingType, kMaxBindingsPerGroup>,
+                   kMaxBindGroups>
             mBindingTypes = {};
-        std::array<std::array<ObjectBase*, kMaxBindingsPerGroup>, kMaxBindGroups> mBindings = {};
+        std::array<ityp::array<BindingIndex, ObjectBase*, kMaxBindingsPerGroup>, kMaxBindGroups>
+            mBindings = {};
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index eb2cd06..e10f7e2 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -304,12 +304,12 @@
 
         // This is a utility function to help ASSERT that the BGL-binding comparator places buffers
         // first.
-        bool CheckBufferBindingsFirst(const BindingInfo* bindings, BindingIndex count) {
-            ASSERT(count <= kMaxBindingsPerGroup);
+        bool CheckBufferBindingsFirst(ityp::span<BindingIndex, const BindingInfo> bindings) {
+            ASSERT(bindings.size() <= BindingIndex(kMaxBindingsPerGroup));
 
-            BindingIndex lastBufferIndex = 0;
+            BindingIndex lastBufferIndex{0};
             BindingIndex firstNonBufferIndex = std::numeric_limits<BindingIndex>::max();
-            for (BindingIndex i = 0; i < count; ++i) {
+            for (BindingIndex i{0}; i < bindings.size(); ++i) {
                 if (IsBufferBinding(bindings[i].type)) {
                     lastBufferIndex = std::max(i, lastBufferIndex);
                 } else {
@@ -334,8 +334,8 @@
 
         std::sort(sortedBindings.begin(), sortedBindings.end(), SortBindingsCompare);
 
-        for (BindingIndex i = 0; i < mBindingCount; ++i) {
-            const BindGroupLayoutEntry& binding = sortedBindings[i];
+        for (BindingIndex i{0}; i < mBindingCount; ++i) {
+            const BindGroupLayoutEntry& binding = sortedBindings[static_cast<uint32_t>(i)];
             mBindingInfo[i].type = binding.type;
             mBindingInfo[i].visibility = binding.visibility;
             mBindingInfo[i].textureComponentType =
@@ -385,7 +385,7 @@
             const auto& it = mBindingMap.emplace(BindingNumber(binding.binding), i);
             ASSERT(it.second);
         }
-        ASSERT(CheckBufferBindingsFirst(mBindingInfo.data(), mBindingCount));
+        ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), mBindingCount}));
     }
 
     BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
@@ -432,7 +432,7 @@
         if (a->GetBindingCount() != b->GetBindingCount()) {
             return false;
         }
-        for (BindingIndex i = 0; i < a->GetBindingCount(); ++i) {
+        for (BindingIndex i{0}; i < a->GetBindingCount(); ++i) {
             if (a->mBindingInfo[i] != b->mBindingInfo[i]) {
                 return false;
             }
@@ -445,7 +445,9 @@
     }
 
     BindingIndex BindGroupLayoutBase::GetDynamicBufferCount() const {
-        return mDynamicStorageBufferCount + mDynamicUniformBufferCount;
+        // This is a binding index because dynamic buffers are packed at the front of the binding
+        // info.
+        return static_cast<BindingIndex>(mDynamicStorageBufferCount + mDynamicUniformBufferCount);
     }
 
     uint32_t BindGroupLayoutBase::GetDynamicUniformBufferCount() const {
@@ -459,20 +461,21 @@
     size_t BindGroupLayoutBase::GetBindingDataSize() const {
         // | ------ buffer-specific ----------| ------------ object pointers -------------|
         // | --- offsets + sizes -------------| --------------- Ref<ObjectBase> ----------|
-        size_t objectPointerStart = mBufferCount * sizeof(BufferBindingData);
+        size_t objectPointerStart = static_cast<uint32_t>(mBufferCount) * sizeof(BufferBindingData);
         ASSERT(IsAligned(objectPointerStart, alignof(Ref<ObjectBase>)));
-        return objectPointerStart + mBindingCount * sizeof(Ref<ObjectBase>);
+        return objectPointerStart + static_cast<uint32_t>(mBindingCount) * sizeof(Ref<ObjectBase>);
     }
 
     BindGroupLayoutBase::BindingDataPointers BindGroupLayoutBase::ComputeBindingDataPointers(
         void* dataStart) const {
         BufferBindingData* bufferData = reinterpret_cast<BufferBindingData*>(dataStart);
-        auto bindings = reinterpret_cast<Ref<ObjectBase>*>(bufferData + mBufferCount);
+        auto bindings =
+            reinterpret_cast<Ref<ObjectBase>*>(bufferData + static_cast<uint32_t>(mBufferCount));
 
         ASSERT(IsPtrAligned(bufferData, alignof(BufferBindingData)));
         ASSERT(IsPtrAligned(bindings, alignof(Ref<ObjectBase>)));
 
-        return {bufferData, bindings};
+        return {{bufferData, mBufferCount}, {bindings, mBindingCount}};
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h
index 15ed76a..9cb7e59 100644
--- a/src/dawn_native/BindGroupLayout.h
+++ b/src/dawn_native/BindGroupLayout.h
@@ -18,6 +18,8 @@
 #include "common/Constants.h"
 #include "common/Math.h"
 #include "common/SlabAllocator.h"
+#include "common/ityp_array.h"
+#include "common/ityp_span.h"
 #include "dawn_native/BindingInfo.h"
 #include "dawn_native/CachedObject.h"
 #include "dawn_native/Error.h"
@@ -25,7 +27,6 @@
 
 #include "dawn_native/dawn_platform.h"
 
-#include <array>
 #include <bitset>
 #include <map>
 
@@ -60,7 +61,7 @@
 
         const BindingInfo& GetBindingInfo(BindingIndex bindingIndex) const {
             ASSERT(!IsError());
-            ASSERT(bindingIndex < kMaxBindingsPerGroup);
+            ASSERT(bindingIndex < BindingIndex(kMaxBindingsPerGroup));
             return mBindingInfo[bindingIndex];
         }
         const BindingMap& GetBindingMap() const;
@@ -86,8 +87,8 @@
         };
 
         struct BindingDataPointers {
-            BufferBindingData* const bufferData = nullptr;
-            Ref<ObjectBase>* const bindings = nullptr;
+            ityp::span<BindingIndex, BufferBindingData> const bufferData = {};
+            ityp::span<BindingIndex, Ref<ObjectBase>> const bindings = {};
         };
 
         // Compute the amount of space / alignment required to store bindings for a bind group of
@@ -114,11 +115,11 @@
         BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag);
 
         BindingIndex mBindingCount;
-        BindingIndex mBufferCount = 0;  // |BindingIndex| because buffers are packed at the front.
+        BindingIndex mBufferCount{0};  // |BindingIndex| because buffers are packed at the front.
         uint32_t mDynamicUniformBufferCount = 0;
         uint32_t mDynamicStorageBufferCount = 0;
 
-        std::array<BindingInfo, kMaxBindingsPerGroup> mBindingInfo;
+        ityp::array<BindingIndex, BindingInfo, kMaxBindingsPerGroup> mBindingInfo;
 
         // Map from BindGroupLayoutEntry.binding to packed indices.
         BindingMap mBindingMap;
diff --git a/src/dawn_native/BindingInfo.h b/src/dawn_native/BindingInfo.h
index 384bd8a..738c5ae 100644
--- a/src/dawn_native/BindingInfo.h
+++ b/src/dawn_native/BindingInfo.h
@@ -15,6 +15,8 @@
 #ifndef DAWNNATIVE_BINDINGINFO_H_
 #define DAWNNATIVE_BINDINGINFO_H_
 
+#include "common/Constants.h"
+#include "common/TypedInteger.h"
 #include "dawn_native/Format.h"
 #include "dawn_native/dawn_platform.h"
 
@@ -22,14 +24,13 @@
 
 namespace dawn_native {
 
-    // TODO(enga): Can we have strongly typed integers so you can't convert between them
-    // by accident? And also range-assertions (ex. kMaxBindingsPerGroup) in Debug?
-
     // Binding numbers in the shader and BindGroup/BindGroupLayoutDescriptors
-    using BindingNumber = uint32_t;
+    using BindingNumber = TypedInteger<struct BindingNumberT, uint32_t>;
 
     // Binding numbers get mapped to a packed range of indices
-    using BindingIndex = uint32_t;
+    using BindingIndex = TypedInteger<struct BindingIndexT, uint32_t>;
+
+    static constexpr BindingIndex kMaxBindingsPerGroupTyped = BindingIndex(kMaxBindingsPerGroup);
 
     struct BindingInfo {
         wgpu::ShaderStage visibility;
diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp
index 66a4df9..a4c1833 100644
--- a/src/dawn_native/PipelineLayout.cpp
+++ b/src/dawn_native/PipelineLayout.cpp
@@ -125,14 +125,15 @@
         ASSERT(count > 0);
 
         // Data which BindGroupLayoutDescriptor will point to for creation
-        std::array<std::array<BindGroupLayoutEntry, kMaxBindingsPerGroup>, kMaxBindGroups>
+        std::array<ityp::array<BindingIndex, BindGroupLayoutEntry, kMaxBindingsPerGroup>,
+                   kMaxBindGroups>
             entryData = {};
 
         // A map of bindings to the index in |entryData|
         std::array<std::map<BindingNumber, BindingIndex>, kMaxBindGroups> usedBindingsMap = {};
 
         // A counter of how many bindings we've populated in |entryData|
-        std::array<uint32_t, kMaxBindGroups> entryCounts = {};
+        std::array<BindingIndex, kMaxBindGroups> entryCounts = {};
 
         uint32_t bindGroupLayoutCount = 0;
         for (uint32_t moduleIndex = 0; moduleIndex < count; ++moduleIndex) {
@@ -149,7 +150,7 @@
                     }
 
                     BindGroupLayoutEntry bindingSlot;
-                    bindingSlot.binding = bindingNumber;
+                    bindingSlot.binding = static_cast<uint32_t>(bindingNumber);
 
                     DAWN_TRY(ValidateBindingTypeWithShaderStageVisibility(
                         bindingInfo.type, StageBit(module->GetExecutionModel())));
@@ -183,7 +184,7 @@
                         }
                     }
 
-                    uint32_t currentBindingCount = entryCounts[group];
+                    BindingIndex currentBindingCount = entryCounts[group];
                     entryData[group][currentBindingCount] = bindingSlot;
 
                     usedBindingsMap[group][bindingNumber] = currentBindingCount;
@@ -199,7 +200,7 @@
         for (uint32_t group = 0; group < bindGroupLayoutCount; ++group) {
             BindGroupLayoutDescriptor desc = {};
             desc.entries = entryData[group].data();
-            desc.entryCount = entryCounts[group];
+            desc.entryCount = static_cast<uint32_t>(entryCounts[group]);
 
             // We should never produce a bad descriptor.
             ASSERT(!ValidateBindGroupLayoutDescriptor(device, &desc).IsError());
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index df96752..5ab4ccb 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -15,6 +15,7 @@
 #include "dawn_native/ProgrammablePassEncoder.h"
 
 #include "common/BitSetIterator.h"
+#include "common/ityp_array.h"
 #include "dawn_native/BindGroup.h"
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandBuffer.h"
@@ -29,8 +30,8 @@
     namespace {
         void TrackBindGroupResourceUsage(PassResourceUsageTracker* usageTracker,
                                          BindGroupBase* group) {
-            for (BindingIndex bindingIndex = 0;
-                 bindingIndex < group->GetLayout()->GetBindingCount(); ++bindingIndex) {
+            for (BindingIndex bindingIndex{0}; bindingIndex < group->GetLayout()->GetBindingCount();
+                 ++bindingIndex) {
                 wgpu::BindingType type = group->GetLayout()->GetBindingInfo(bindingIndex).type;
 
                 switch (type) {
@@ -131,8 +132,8 @@
 
     void ProgrammablePassEncoder::SetBindGroup(uint32_t groupIndex,
                                                BindGroupBase* group,
-                                               uint32_t dynamicOffsetCount,
-                                               const uint32_t* dynamicOffsets) {
+                                               uint32_t dynamicOffsetCountIn,
+                                               const uint32_t* dynamicOffsetsIn) {
         mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
             if (GetDevice()->IsValidationEnabled()) {
                 DAWN_TRY(GetDevice()->ValidateObject(group));
@@ -141,13 +142,16 @@
                     return DAWN_VALIDATION_ERROR("Setting bind group over the max");
                 }
 
+                ityp::span<BindingIndex, const uint32_t> dynamicOffsets(
+                    dynamicOffsetsIn, BindingIndex(dynamicOffsetCountIn));
+
                 // Dynamic offsets count must match the number required by the layout perfectly.
                 const BindGroupLayoutBase* layout = group->GetLayout();
-                if (layout->GetDynamicBufferCount() != dynamicOffsetCount) {
+                if (layout->GetDynamicBufferCount() != dynamicOffsets.size()) {
                     return DAWN_VALIDATION_ERROR("dynamicOffset count mismatch");
                 }
 
-                for (BindingIndex i = 0; i < dynamicOffsetCount; ++i) {
+                for (BindingIndex i{0}; i < dynamicOffsets.size(); ++i) {
                     const BindingInfo& bindingInfo = layout->GetBindingInfo(i);
 
                     // BGL creation sorts bindings such that the dynamic buffer bindings are first.
@@ -185,10 +189,10 @@
             SetBindGroupCmd* cmd = allocator->Allocate<SetBindGroupCmd>(Command::SetBindGroup);
             cmd->index = groupIndex;
             cmd->group = group;
-            cmd->dynamicOffsetCount = dynamicOffsetCount;
-            if (dynamicOffsetCount > 0) {
+            cmd->dynamicOffsetCount = dynamicOffsetCountIn;
+            if (dynamicOffsetCountIn > 0) {
                 uint32_t* offsets = allocator->AllocateData<uint32_t>(cmd->dynamicOffsetCount);
-                memcpy(offsets, dynamicOffsets, dynamicOffsetCount * sizeof(uint32_t));
+                memcpy(offsets, dynamicOffsetsIn, dynamicOffsetCountIn * sizeof(uint32_t));
             }
 
             TrackBindGroupResourceUsage(&mUsageTracker, group);
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index a2b4e76..6c27736 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -289,9 +289,10 @@
             }
         }
 
-        std::string GetShaderDeclarationString(size_t group, uint32_t binding) {
+        std::string GetShaderDeclarationString(size_t group, BindingNumber binding) {
             std::ostringstream ostream;
-            ostream << "the shader module declaration at set " << group << " binding " << binding;
+            ostream << "the shader module declaration at set " << group << " binding "
+                    << static_cast<uint32_t>(binding);
             return ostream.str();
         }
     }  // anonymous namespace
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
index 2bd7bae..9280f8c 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
@@ -103,7 +103,7 @@
                            D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER);
         descriptorOffsets[Sampler] = 0;
 
-        for (BindingIndex bindingIndex = 0; bindingIndex < GetBindingCount(); ++bindingIndex) {
+        for (BindingIndex bindingIndex{0}; bindingIndex < GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
 
             if (bindingInfo.hasDynamicOffset) {
@@ -170,7 +170,8 @@
         mBindGroupAllocator.Deallocate(bindGroup);
     }
 
-    const std::array<uint32_t, kMaxBindingsPerGroup>& BindGroupLayout::GetBindingOffsets() const {
+    const ityp::array<BindingIndex, uint32_t, kMaxBindingsPerGroup>&
+    BindGroupLayout::GetBindingOffsets() const {
         return mBindingOffsets;
     }
 
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.h b/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
index e739ca2..5a5ba8b 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
@@ -44,7 +44,7 @@
             Count,
         };
 
-        const std::array<uint32_t, kMaxBindingsPerGroup>& GetBindingOffsets() const;
+        const ityp::array<BindingIndex, uint32_t, kMaxBindingsPerGroup>& GetBindingOffsets() const;
         uint32_t GetCbvUavSrvDescriptorTableSize() const;
         uint32_t GetSamplerDescriptorTableSize() const;
         uint32_t GetCbvUavSrvDescriptorCount() const;
@@ -54,7 +54,7 @@
 
       private:
         ~BindGroupLayout() override = default;
-        std::array<uint32_t, kMaxBindingsPerGroup> mBindingOffsets;
+        ityp::array<BindingIndex, uint32_t, kMaxBindingsPerGroup> mBindingOffsets;
         std::array<uint32_t, DescriptorType::Count> mDescriptorCounts;
         D3D12_DESCRIPTOR_RANGE mRanges[DescriptorType::Count];
 
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index f813b56..8af7a9e 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -150,7 +150,7 @@
 
             if (mInCompute) {
                 for (uint32_t index : IterateBitSet(mBindGroupLayoutsMask)) {
-                    for (uint32_t binding : IterateBitSet(mBindingsNeedingBarrier[index])) {
+                    for (BindingIndex binding : IterateBitSet(mBindingsNeedingBarrier[index])) {
                         wgpu::BindingType bindingType = mBindingTypes[index][binding];
                         switch (bindingType) {
                             case wgpu::BindingType::StorageBuffer:
@@ -213,16 +213,18 @@
                             const PipelineLayout* pipelineLayout,
                             uint32_t index,
                             BindGroup* group,
-                            uint32_t dynamicOffsetCount,
-                            const uint64_t* dynamicOffsets) {
-            ASSERT(dynamicOffsetCount == group->GetLayout()->GetDynamicBufferCount());
+                            uint32_t dynamicOffsetCountIn,
+                            const uint64_t* dynamicOffsetsIn) {
+            ityp::span<BindingIndex, const uint64_t> dynamicOffsets(
+                dynamicOffsetsIn, BindingIndex(dynamicOffsetCountIn));
+            ASSERT(dynamicOffsets.size() == group->GetLayout()->GetDynamicBufferCount());
 
             // Usually, the application won't set the same offsets many times,
             // so always try to apply dynamic offsets even if the offsets stay the same
-            if (dynamicOffsetCount != 0) {
+            if (dynamicOffsets.size() != BindingIndex(0)) {
                 // Update dynamic offsets.
                 // Dynamic buffer bindings are packed at the beginning of the layout.
-                for (BindingIndex bindingIndex = 0; bindingIndex < dynamicOffsetCount;
+                for (BindingIndex bindingIndex{0}; bindingIndex < dynamicOffsets.size();
                      ++bindingIndex) {
                     const BindingInfo& bindingInfo =
                         group->GetLayout()->GetBindingInfo(bindingIndex);
diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
index 2598758..456466b 100644
--- a/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.cpp
@@ -123,7 +123,7 @@
 
             // Init root descriptors in root signatures for dynamic buffer bindings.
             // These are packed at the beginning of the layout binding info.
-            for (BindingIndex dynamicBindingIndex = 0;
+            for (BindingIndex dynamicBindingIndex{0};
                  dynamicBindingIndex < bindGroupLayout->GetDynamicBufferCount();
                  ++dynamicBindingIndex) {
                 const BindingInfo& bindingInfo =
@@ -194,7 +194,7 @@
     uint32_t PipelineLayout::GetDynamicRootParameterIndex(uint32_t group,
                                                           BindingIndex bindingIndex) const {
         ASSERT(group < kMaxBindGroups);
-        ASSERT(bindingIndex < kMaxBindingsPerGroup);
+        ASSERT(bindingIndex < kMaxBindingsPerGroupTyped);
         ASSERT(GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).hasDynamicOffset);
         ASSERT(GetBindGroupLayout(group)->GetBindingInfo(bindingIndex).visibility !=
                wgpu::ShaderStage::None);
diff --git a/src/dawn_native/d3d12/PipelineLayoutD3D12.h b/src/dawn_native/d3d12/PipelineLayoutD3D12.h
index 5bcc1ad..a539174 100644
--- a/src/dawn_native/d3d12/PipelineLayoutD3D12.h
+++ b/src/dawn_native/d3d12/PipelineLayoutD3D12.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_D3D12_PIPELINELAYOUTD3D12_H_
 #define DAWNNATIVE_D3D12_PIPELINELAYOUTD3D12_H_
 
+#include "common/ityp_array.h"
 #include "dawn_native/BindingInfo.h"
 #include "dawn_native/PipelineLayout.h"
 #include "dawn_native/d3d12/d3d12_platform.h"
@@ -42,7 +43,7 @@
         MaybeError Initialize();
         std::array<uint32_t, kMaxBindGroups> mCbvUavSrvRootParameterInfo;
         std::array<uint32_t, kMaxBindGroups> mSamplerRootParameterInfo;
-        std::array<std::array<uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups>
+        std::array<ityp::array<BindingIndex, uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups>
             mDynamicRootParameterIndices;
         ComPtr<ID3D12RootSignature> mRootSignature;
     };
diff --git a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
index 87f1e03..8901d20 100644
--- a/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn_native/d3d12/ShaderModuleD3D12.cpp
@@ -180,13 +180,15 @@
                         "spvc"));
                     if (forceStorageBufferAsUAV) {
                         DAWN_TRY(CheckSpvcSuccess(
-                            mSpvcContext.SetHLSLForceStorageBufferAsUAV(group, bindingNumber),
+                            mSpvcContext.SetHLSLForceStorageBufferAsUAV(
+                                group, static_cast<uint32_t>(bindingNumber)),
                             "Unable to force read-only storage buffer as UAV w/ spvc"));
                     }
                 } else {
                     compiler->set_decoration(bindingInfo.id, spv::DecorationBinding, bindingOffset);
                     if (forceStorageBufferAsUAV) {
-                        compiler->set_hlsl_force_storage_buffer_as_uav(group, bindingNumber);
+                        compiler->set_hlsl_force_storage_buffer_as_uav(
+                            group, static_cast<uint32_t>(bindingNumber));
                     }
                 }
             }
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 2830e06..c52b63d 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -496,7 +496,7 @@
                 // TODO(kainino@chromium.org): Maintain buffers and offsets arrays in BindGroup
                 // so that we only have to do one setVertexBuffers and one setFragmentBuffers
                 // call here.
-                for (BindingIndex bindingIndex = 0;
+                for (BindingIndex bindingIndex{0};
                      bindingIndex < group->GetLayout()->GetBindingCount(); ++bindingIndex) {
                     const BindingInfo& bindingInfo =
                         group->GetLayout()->GetBindingInfo(bindingIndex);
diff --git a/src/dawn_native/metal/PipelineLayoutMTL.h b/src/dawn_native/metal/PipelineLayoutMTL.h
index 6f2f64d..e5acafc 100644
--- a/src/dawn_native/metal/PipelineLayoutMTL.h
+++ b/src/dawn_native/metal/PipelineLayoutMTL.h
@@ -15,6 +15,8 @@
 #ifndef DAWNNATIVE_METAL_PIPELINELAYOUTMTL_H_
 #define DAWNNATIVE_METAL_PIPELINELAYOUTMTL_H_
 
+#include "common/ityp_array.h"
+#include "dawn_native/BindingInfo.h"
 #include "dawn_native/PipelineLayout.h"
 
 #include "dawn_native/PerStage.h"
@@ -41,7 +43,7 @@
         PipelineLayout(Device* device, const PipelineLayoutDescriptor* descriptor);
 
         using BindingIndexInfo =
-            std::array<std::array<uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups>;
+            std::array<ityp::array<BindingIndex, uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups>;
         const BindingIndexInfo& GetBindingIndexInfo(SingleShaderStage stage) const;
 
         // The number of Metal vertex stage buffers used for the whole pipeline layout.
diff --git a/src/dawn_native/metal/PipelineLayoutMTL.mm b/src/dawn_native/metal/PipelineLayoutMTL.mm
index 9b2c6b2..996bd1f 100644
--- a/src/dawn_native/metal/PipelineLayoutMTL.mm
+++ b/src/dawn_native/metal/PipelineLayoutMTL.mm
@@ -29,7 +29,7 @@
             uint32_t textureIndex = 0;
 
             for (uint32_t group : IterateBitSet(GetBindGroupLayoutsMask())) {
-                for (BindingIndex bindingIndex = 0;
+                for (BindingIndex bindingIndex{0};
                      bindingIndex < GetBindGroupLayout(group)->GetBindingCount(); ++bindingIndex) {
                     const BindingInfo& bindingInfo =
                         GetBindGroupLayout(group)->GetBindingInfo(bindingIndex);
diff --git a/src/dawn_native/metal/ShaderModuleMTL.mm b/src/dawn_native/metal/ShaderModuleMTL.mm
index af56694..be45558 100644
--- a/src/dawn_native/metal/ShaderModuleMTL.mm
+++ b/src/dawn_native/metal/ShaderModuleMTL.mm
@@ -149,7 +149,7 @@
                         shaderc_spvc_msl_resource_binding mslBinding;
                         mslBinding.stage = ToSpvcExecutionModel(stage);
                         mslBinding.desc_set = group;
-                        mslBinding.binding = bindingNumber;
+                        mslBinding.binding = static_cast<uint32_t>(bindingNumber);
                         mslBinding.msl_buffer = mslBinding.msl_texture = mslBinding.msl_sampler =
                             shaderIndex;
                         DAWN_TRY(CheckSpvcSuccess(mSpvcContext.AddMSLResourceBinding(mslBinding),
@@ -158,7 +158,7 @@
                         spirv_cross::MSLResourceBinding mslBinding;
                         mslBinding.stage = SpirvExecutionModelForStage(stage);
                         mslBinding.desc_set = group;
-                        mslBinding.binding = bindingNumber;
+                        mslBinding.binding = static_cast<uint32_t>(bindingNumber);
                         mslBinding.msl_buffer = mslBinding.msl_texture = mslBinding.msl_sampler =
                             shaderIndex;
 
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index a3c5be3..88148e9 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -241,7 +241,7 @@
                 const auto& indices = ToBackend(mPipelineLayout)->GetBindingIndexInfo()[index];
                 uint32_t currentDynamicOffsetIndex = 0;
 
-                for (BindingIndex bindingIndex = 0;
+                for (BindingIndex bindingIndex{0};
                      bindingIndex < group->GetLayout()->GetBindingCount(); ++bindingIndex) {
                     const BindingInfo& bindingInfo =
                         group->GetLayout()->GetBindingInfo(bindingIndex);
diff --git a/src/dawn_native/opengl/PipelineGL.cpp b/src/dawn_native/opengl/PipelineGL.cpp
index d19e5be..41dab2d 100644
--- a/src/dawn_native/opengl/PipelineGL.cpp
+++ b/src/dawn_native/opengl/PipelineGL.cpp
@@ -183,20 +183,29 @@
 
                 gl.Uniform1i(location, textureUnit);
 
-                GLuint textureIndex =
-                    indices[combined.textureLocation.group][combined.textureLocation.binding];
-                mUnitsForTextures[textureIndex].push_back(textureUnit);
+                bool shouldUseFiltering;
+                {
+                    const BindGroupLayoutBase* bgl =
+                        layout->GetBindGroupLayout(combined.textureLocation.group);
+                    BindingIndex bindingIndex =
+                        bgl->GetBindingIndex(combined.textureLocation.binding);
 
-                const BindGroupLayoutBase* bgl =
-                    layout->GetBindGroupLayout(combined.textureLocation.group);
-                Format::Type componentType =
-                    bgl->GetBindingInfo(bgl->GetBindingIndex(combined.textureLocation.binding))
-                        .textureComponentType;
-                bool shouldUseFiltering = componentType == Format::Type::Float;
+                    GLuint textureIndex = indices[combined.textureLocation.group][bindingIndex];
+                    mUnitsForTextures[textureIndex].push_back(textureUnit);
 
-                GLuint samplerIndex =
-                    indices[combined.samplerLocation.group][combined.samplerLocation.binding];
-                mUnitsForSamplers[samplerIndex].push_back({textureUnit, shouldUseFiltering});
+                    Format::Type componentType =
+                        bgl->GetBindingInfo(bindingIndex).textureComponentType;
+                    shouldUseFiltering = componentType == Format::Type::Float;
+                }
+                {
+                    const BindGroupLayoutBase* bgl =
+                        layout->GetBindGroupLayout(combined.samplerLocation.group);
+                    BindingIndex bindingIndex =
+                        bgl->GetBindingIndex(combined.samplerLocation.binding);
+
+                    GLuint samplerIndex = indices[combined.samplerLocation.group][bindingIndex];
+                    mUnitsForSamplers[samplerIndex].push_back({textureUnit, shouldUseFiltering});
+                }
 
                 textureUnit++;
             }
diff --git a/src/dawn_native/opengl/PipelineLayoutGL.cpp b/src/dawn_native/opengl/PipelineLayoutGL.cpp
index d951b83..ed4cceb 100644
--- a/src/dawn_native/opengl/PipelineLayoutGL.cpp
+++ b/src/dawn_native/opengl/PipelineLayoutGL.cpp
@@ -31,7 +31,7 @@
         for (uint32_t group : IterateBitSet(GetBindGroupLayoutsMask())) {
             const BindGroupLayoutBase* bgl = GetBindGroupLayout(group);
 
-            for (BindingIndex bindingIndex = 0; bindingIndex < bgl->GetBindingCount();
+            for (BindingIndex bindingIndex{0}; bindingIndex < bgl->GetBindingCount();
                  ++bindingIndex) {
                 switch (bgl->GetBindingInfo(bindingIndex).type) {
                     case wgpu::BindingType::UniformBuffer:
diff --git a/src/dawn_native/opengl/PipelineLayoutGL.h b/src/dawn_native/opengl/PipelineLayoutGL.h
index fb03aaf..85458d2 100644
--- a/src/dawn_native/opengl/PipelineLayoutGL.h
+++ b/src/dawn_native/opengl/PipelineLayoutGL.h
@@ -17,6 +17,8 @@
 
 #include "dawn_native/PipelineLayout.h"
 
+#include "common/ityp_array.h"
+#include "dawn_native/BindingInfo.h"
 #include "dawn_native/opengl/opengl_platform.h"
 
 namespace dawn_native { namespace opengl {
@@ -28,7 +30,7 @@
         PipelineLayout(Device* device, const PipelineLayoutDescriptor* descriptor);
 
         using BindingIndexInfo =
-            std::array<std::array<GLuint, kMaxBindingsPerGroup>, kMaxBindGroups>;
+            std::array<ityp::array<BindingIndex, GLuint, kMaxBindingsPerGroup>, kMaxBindGroups>;
         const BindingIndexInfo& GetBindingIndexInfo() const;
 
         GLuint GetTextureUnitsUsed() const;
diff --git a/src/dawn_native/opengl/ShaderModuleGL.cpp b/src/dawn_native/opengl/ShaderModuleGL.cpp
index 789da6d..d7d9f74 100644
--- a/src/dawn_native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn_native/opengl/ShaderModuleGL.cpp
@@ -24,9 +24,9 @@
 
 namespace dawn_native { namespace opengl {
 
-    std::string GetBindingName(uint32_t group, uint32_t binding) {
+    std::string GetBindingName(uint32_t group, BindingNumber bindingNumber) {
         std::ostringstream o;
-        o << "dawn_binding_" << group << "_" << binding;
+        o << "dawn_binding_" << group << "_" << static_cast<uint32_t>(bindingNumber);
         return o.str();
     }
 
@@ -42,8 +42,9 @@
     std::string CombinedSampler::GetName() const {
         std::ostringstream o;
         o << "dawn_combined";
-        o << "_" << samplerLocation.group << "_" << samplerLocation.binding;
-        o << "_with_" << textureLocation.group << "_" << textureLocation.binding;
+        o << "_" << samplerLocation.group << "_" << static_cast<uint32_t>(samplerLocation.binding);
+        o << "_with_" << textureLocation.group << "_"
+          << static_cast<uint32_t>(textureLocation.binding);
         return o.str();
     }
 
@@ -143,12 +144,19 @@
                 mSpvcContext.GetDecoration(sampler.sampler_id,
                                            shaderc_spvc_decoration_descriptorset,
                                            &info.samplerLocation.group);
+                uint32_t samplerBinding;
                 mSpvcContext.GetDecoration(sampler.sampler_id, shaderc_spvc_decoration_binding,
-                                           &info.samplerLocation.binding);
+                                           &samplerBinding);
+                info.samplerLocation.binding = BindingNumber(samplerBinding);
+
                 mSpvcContext.GetDecoration(sampler.image_id, shaderc_spvc_decoration_descriptorset,
                                            &info.textureLocation.group);
+
+                uint32_t textureBinding;
                 mSpvcContext.GetDecoration(sampler.image_id, shaderc_spvc_decoration_binding,
-                                           &info.textureLocation.binding);
+                                           &textureBinding);
+                info.textureLocation.binding = BindingNumber(textureBinding);
+
                 mSpvcContext.SetName(sampler.combined_id, info.GetName());
             }
         } else {
@@ -158,12 +166,12 @@
                 auto& info = mCombinedInfo.back();
                 info.samplerLocation.group =
                     compiler->get_decoration(combined.sampler_id, spv::DecorationDescriptorSet);
-                info.samplerLocation.binding =
-                    compiler->get_decoration(combined.sampler_id, spv::DecorationBinding);
+                info.samplerLocation.binding = BindingNumber(
+                    compiler->get_decoration(combined.sampler_id, spv::DecorationBinding));
                 info.textureLocation.group =
                     compiler->get_decoration(combined.image_id, spv::DecorationDescriptorSet);
-                info.textureLocation.binding =
-                    compiler->get_decoration(combined.image_id, spv::DecorationBinding);
+                info.textureLocation.binding = BindingNumber(
+                    compiler->get_decoration(combined.image_id, spv::DecorationBinding));
                 compiler->set_name(combined.combined_id, info.GetName());
             }
         }
diff --git a/src/dawn_native/opengl/ShaderModuleGL.h b/src/dawn_native/opengl/ShaderModuleGL.h
index 9e2b5c9..dc267d2 100644
--- a/src/dawn_native/opengl/ShaderModuleGL.h
+++ b/src/dawn_native/opengl/ShaderModuleGL.h
@@ -23,11 +23,11 @@
 
     class Device;
 
-    std::string GetBindingName(uint32_t group, uint32_t binding);
+    std::string GetBindingName(uint32_t group, BindingNumber bindingNumber);
 
     struct BindingLocation {
         uint32_t group;
-        uint32_t binding;
+        BindingNumber binding;
     };
     bool operator<(const BindingLocation& a, const BindingLocation& b);
 
diff --git a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
index ba41c38..1b325be 100644
--- a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
@@ -93,7 +93,7 @@
             const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
 
             VkDescriptorSetLayoutBinding* vkBinding = &bindings[numBindings];
-            vkBinding->binding = bindingNumber;
+            vkBinding->binding = static_cast<uint32_t>(bindingNumber);
             vkBinding->descriptorType =
                 VulkanDescriptorType(bindingInfo.type, bindingInfo.hasDynamicOffset);
             vkBinding->descriptorCount = 1;
@@ -118,7 +118,7 @@
         // Compute the size of descriptor pools used for this layout.
         std::map<VkDescriptorType, uint32_t> descriptorCountPerType;
 
-        for (BindingIndex bindingIndex = 0; bindingIndex < GetBindingCount(); ++bindingIndex) {
+        for (BindingIndex bindingIndex{0}; bindingIndex < GetBindingCount(); ++bindingIndex) {
             const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
             VkDescriptorType vulkanType =
                 VulkanDescriptorType(bindingInfo.type, bindingInfo.hasDynamicOffset);
diff --git a/src/dawn_native/vulkan/BindGroupVk.cpp b/src/dawn_native/vulkan/BindGroupVk.cpp
index a936f20..eb31182 100644
--- a/src/dawn_native/vulkan/BindGroupVk.cpp
+++ b/src/dawn_native/vulkan/BindGroupVk.cpp
@@ -52,7 +52,7 @@
             write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
             write.pNext = nullptr;
             write.dstSet = GetHandle();
-            write.dstBinding = bindingNumber;
+            write.dstBinding = static_cast<uint32_t>(bindingNumber);
             write.dstArrayElement = 0;
             write.descriptorCount = 1;
             write.descriptorType =
diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp
index f1003c1..dca8842 100644
--- a/src/dawn_native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn_native/vulkan/CommandBufferVk.cpp
@@ -144,7 +144,8 @@
                                     mDynamicOffsetCounts, mDynamicOffsets);
 
                 for (uint32_t index : IterateBitSet(mBindGroupLayoutsMask)) {
-                    for (uint32_t bindingIndex : IterateBitSet(mBindingsNeedingBarrier[index])) {
+                    for (BindingIndex bindingIndex :
+                         IterateBitSet(mBindingsNeedingBarrier[index])) {
                         switch (mBindingTypes[index][bindingIndex]) {
                             case wgpu::BindingType::StorageBuffer:
                                 static_cast<Buffer*>(mBindings[index][bindingIndex])
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index e602269..69a0172 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -159,6 +159,9 @@
     "unittests/ErrorTests.cpp",
     "unittests/ExtensionTests.cpp",
     "unittests/GetProcAddressTests.cpp",
+    "unittests/ITypArrayTests.cpp",
+    "unittests/ITypBitsetTests.cpp",
+    "unittests/ITypSpanTests.cpp",
     "unittests/LinkedListTests.cpp",
     "unittests/MathTests.cpp",
     "unittests/ObjectBaseTests.cpp",
@@ -172,6 +175,7 @@
     "unittests/SlabAllocatorTests.cpp",
     "unittests/SystemUtilsTests.cpp",
     "unittests/ToBackendTests.cpp",
+    "unittests/TypedIntegerTests.cpp",
     "unittests/validation/BindGroupValidationTests.cpp",
     "unittests/validation/BufferValidationTests.cpp",
     "unittests/validation/CommandBufferValidationTests.cpp",
diff --git a/src/tests/unittests/BitSetIteratorTests.cpp b/src/tests/unittests/BitSetIteratorTests.cpp
index cc6c3ed..34cfb6c 100644
--- a/src/tests/unittests/BitSetIteratorTests.cpp
+++ b/src/tests/unittests/BitSetIteratorTests.cpp
@@ -15,6 +15,7 @@
 #include <gtest/gtest.h>
 
 #include "common/BitSetIterator.h"
+#include "common/ityp_bitset.h"
 
 // This is ANGLE's BitSetIterator_unittests.cpp file.
 
@@ -48,7 +49,7 @@
 // Test an empty iterator.
 TEST_F(BitSetIteratorTest, EmptySet) {
     // We don't use the FAIL gtest macro here since it returns immediately,
-    // causing an unreachable code warning in MSVS
+    // causing an unreachable code warning in MSVC
     bool sawBit = false;
     for (unsigned long bit : IterateBitSet(mStateBits)) {
         DAWN_UNUSED(bit);
@@ -82,3 +83,137 @@
 
     EXPECT_EQ((mStateBits & otherBits).count(), seenBits.size());
 }
+
+class EnumBitSetIteratorTest : public testing::Test {
+  protected:
+    enum class TestEnum { A, B, C, D, E, F, G, H, I, J, EnumCount };
+
+    static constexpr size_t kEnumCount = static_cast<size_t>(TestEnum::EnumCount);
+    ityp::bitset<TestEnum, kEnumCount> mStateBits;
+};
+
+// Simple iterator test.
+TEST_F(EnumBitSetIteratorTest, Iterator) {
+    std::set<TestEnum> originalValues;
+    originalValues.insert(TestEnum::B);
+    originalValues.insert(TestEnum::F);
+    originalValues.insert(TestEnum::C);
+    originalValues.insert(TestEnum::I);
+
+    for (TestEnum value : originalValues) {
+        mStateBits.set(value);
+    }
+
+    std::set<TestEnum> readValues;
+    for (TestEnum bit : IterateBitSet(mStateBits)) {
+        EXPECT_EQ(1u, originalValues.count(bit));
+        EXPECT_EQ(0u, readValues.count(bit));
+        readValues.insert(bit);
+    }
+
+    EXPECT_EQ(originalValues.size(), readValues.size());
+}
+
+// Test an empty iterator.
+TEST_F(EnumBitSetIteratorTest, EmptySet) {
+    // We don't use the FAIL gtest macro here since it returns immediately,
+    // causing an unreachable code warning in MSVC
+    bool sawBit = false;
+    for (TestEnum bit : IterateBitSet(mStateBits)) {
+        DAWN_UNUSED(bit);
+        sawBit = true;
+    }
+    EXPECT_FALSE(sawBit);
+}
+
+// Test iterating a result of combining two bitsets.
+TEST_F(EnumBitSetIteratorTest, NonLValueBitset) {
+    ityp::bitset<TestEnum, kEnumCount> otherBits;
+
+    mStateBits.set(TestEnum::B);
+    mStateBits.set(TestEnum::C);
+    mStateBits.set(TestEnum::D);
+    mStateBits.set(TestEnum::E);
+
+    otherBits.set(TestEnum::A);
+    otherBits.set(TestEnum::B);
+    otherBits.set(TestEnum::D);
+    otherBits.set(TestEnum::F);
+
+    std::set<TestEnum> seenBits;
+
+    for (TestEnum bit : IterateBitSet(mStateBits & otherBits)) {
+        EXPECT_EQ(0u, seenBits.count(bit));
+        seenBits.insert(bit);
+        EXPECT_TRUE(mStateBits[bit]);
+        EXPECT_TRUE(otherBits[bit]);
+    }
+
+    EXPECT_EQ((mStateBits & otherBits).count(), seenBits.size());
+}
+
+class ITypBitsetIteratorTest : public testing::Test {
+  protected:
+    using IntegerT = TypedInteger<struct Foo, uint32_t>;
+    ityp::bitset<IntegerT, 40> mStateBits;
+};
+
+// Simple iterator test.
+TEST_F(ITypBitsetIteratorTest, Iterator) {
+    std::set<IntegerT> originalValues;
+    originalValues.insert(IntegerT(2));
+    originalValues.insert(IntegerT(6));
+    originalValues.insert(IntegerT(8));
+    originalValues.insert(IntegerT(35));
+
+    for (IntegerT value : originalValues) {
+        mStateBits.set(value);
+    }
+
+    std::set<IntegerT> readValues;
+    for (IntegerT bit : IterateBitSet(mStateBits)) {
+        EXPECT_EQ(1u, originalValues.count(bit));
+        EXPECT_EQ(0u, readValues.count(bit));
+        readValues.insert(bit);
+    }
+
+    EXPECT_EQ(originalValues.size(), readValues.size());
+}
+
+// Test an empty iterator.
+TEST_F(ITypBitsetIteratorTest, EmptySet) {
+    // We don't use the FAIL gtest macro here since it returns immediately,
+    // causing an unreachable code warning in MSVC
+    bool sawBit = false;
+    for (IntegerT bit : IterateBitSet(mStateBits)) {
+        DAWN_UNUSED(bit);
+        sawBit = true;
+    }
+    EXPECT_FALSE(sawBit);
+}
+
+// Test iterating a result of combining two bitsets.
+TEST_F(ITypBitsetIteratorTest, NonLValueBitset) {
+    ityp::bitset<IntegerT, 40> otherBits;
+
+    mStateBits.set(IntegerT(1));
+    mStateBits.set(IntegerT(2));
+    mStateBits.set(IntegerT(3));
+    mStateBits.set(IntegerT(4));
+
+    otherBits.set(IntegerT(0));
+    otherBits.set(IntegerT(1));
+    otherBits.set(IntegerT(3));
+    otherBits.set(IntegerT(5));
+
+    std::set<IntegerT> seenBits;
+
+    for (IntegerT bit : IterateBitSet(mStateBits & otherBits)) {
+        EXPECT_EQ(0u, seenBits.count(bit));
+        seenBits.insert(bit);
+        EXPECT_TRUE(mStateBits[bit]);
+        EXPECT_TRUE(otherBits[bit]);
+    }
+
+    EXPECT_EQ((mStateBits & otherBits).count(), seenBits.size());
+}
diff --git a/src/tests/unittests/ITypArrayTests.cpp b/src/tests/unittests/ITypArrayTests.cpp
new file mode 100644
index 0000000..d147b4b
--- /dev/null
+++ b/src/tests/unittests/ITypArrayTests.cpp
@@ -0,0 +1,95 @@
+// Copyright 2020 The Dawn 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 <gtest/gtest.h>
+
+#include "common/TypedInteger.h"
+#include "common/ityp_array.h"
+
+class ITypArrayTest : public testing::Test {
+  protected:
+    using Key = TypedInteger<struct KeyT, uint32_t>;
+    using Val = TypedInteger<struct ValT, uint32_t>;
+    using Array = ityp::array<Key, Val, 10>;
+
+    // Test that the expected array methods can be constexpr
+    struct ConstexprTest {
+        static constexpr Array kArr = {Val(0), Val(1), Val(2), Val(3), Val(4),
+                                       Val(5), Val(6), Val(7), Val(8), Val(9)};
+
+        static_assert(kArr[Key(3)] == Val(3), "");
+        static_assert(kArr.at(Key(7)) == Val(7), "");
+        static_assert(kArr.size() == Key(10), "");
+    };
+};
+
+// Test that values can be set at an index and retrieved from the same index.
+TEST_F(ITypArrayTest, Indexing) {
+    Array arr;
+    {
+        arr[Key(2)] = Val(5);
+        arr[Key(1)] = Val(9);
+        arr[Key(9)] = Val(2);
+
+        ASSERT_EQ(arr[Key(2)], Val(5));
+        ASSERT_EQ(arr[Key(1)], Val(9));
+        ASSERT_EQ(arr[Key(9)], Val(2));
+    }
+    {
+        arr.at(Key(4)) = Val(5);
+        arr.at(Key(3)) = Val(8);
+        arr.at(Key(1)) = Val(7);
+
+        ASSERT_EQ(arr.at(Key(4)), Val(5));
+        ASSERT_EQ(arr.at(Key(3)), Val(8));
+        ASSERT_EQ(arr.at(Key(1)), Val(7));
+    }
+}
+
+// Test that the array can be iterated in order with a range-based for loop
+TEST_F(ITypArrayTest, RangeBasedIteration) {
+    Array arr;
+
+    // Assign in a non-const range-based for loop
+    uint32_t i = 0;
+    for (Val& val : arr) {
+        val = Val(i);
+    }
+
+    // Check values in a const range-based for loop
+    i = 0;
+    for (Val val : static_cast<const Array&>(arr)) {
+        ASSERT_EQ(val, arr[Key(i++)]);
+    }
+}
+
+// Test that begin/end/front/back/data return pointers/references to the correct elements.
+TEST_F(ITypArrayTest, BeginEndFrontBackData) {
+    Array arr;
+
+    // non-const versions
+    ASSERT_EQ(arr.begin(), &arr[Key(0)]);
+    ASSERT_EQ(arr.end(), &arr[Key(0)] + static_cast<uint32_t>(arr.size()));
+    ASSERT_EQ(&arr.front(), &arr[Key(0)]);
+    ASSERT_EQ(&arr.back(), &arr[Key(9)]);
+    ASSERT_EQ(arr.data(), &arr[Key(0)]);
+
+    // const versions
+    const Array& constArr = arr;
+    ASSERT_EQ(constArr.begin(), &constArr[Key(0)]);
+    ASSERT_EQ(constArr.end(), &constArr[Key(0)] + static_cast<uint32_t>(constArr.size()));
+    ASSERT_EQ(&constArr.front(), &constArr[Key(0)]);
+    ASSERT_EQ(&constArr.back(), &constArr[Key(9)]);
+    ASSERT_EQ(constArr.data(), &constArr[Key(0)]);
+}
diff --git a/src/tests/unittests/ITypBitsetTests.cpp b/src/tests/unittests/ITypBitsetTests.cpp
new file mode 100644
index 0000000..93f9be2
--- /dev/null
+++ b/src/tests/unittests/ITypBitsetTests.cpp
@@ -0,0 +1,178 @@
+// Copyright 2020 The Dawn 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 <gtest/gtest.h>
+
+#include "common/TypedInteger.h"
+#include "common/ityp_bitset.h"
+
+#include <set>
+
+class ITypBitsetTest : public testing::Test {
+  protected:
+    using Key = TypedInteger<struct KeyT, size_t>;
+    using Bitset = ityp::bitset<Key, 9>;
+
+    // Test that the expected bitset methods can be constexpr
+    struct ConstexprTest {
+        static constexpr Bitset kBitset = {1 << 0 | 1 << 3 | 1 << 7 | 1 << 8};
+
+        static_assert(kBitset[Key(0)] == true, "");
+        static_assert(kBitset[Key(1)] == false, "");
+        static_assert(kBitset[Key(2)] == false, "");
+        static_assert(kBitset[Key(3)] == true, "");
+        static_assert(kBitset[Key(4)] == false, "");
+        static_assert(kBitset[Key(5)] == false, "");
+        static_assert(kBitset[Key(6)] == false, "");
+        static_assert(kBitset[Key(7)] == true, "");
+        static_assert(kBitset[Key(8)] == true, "");
+
+        static_assert(kBitset.size() == 9, "");
+    };
+
+    void ExpectBits(const Bitset& bits, std::set<size_t> indices) {
+        size_t mask = 0;
+
+        for (size_t i = 0; i < bits.size(); ++i) {
+            if (indices.count(i) == 0) {
+                ASSERT_FALSE(bits[Key(i)]) << i;
+                ASSERT_FALSE(bits.test(Key(i))) << i;
+            } else {
+                mask |= (1 << i);
+                ASSERT_TRUE(bits[Key(i)]) << i;
+                ASSERT_TRUE(bits.test(Key(i))) << i;
+            }
+        }
+
+        ASSERT_EQ(bits.to_ullong(), mask);
+        ASSERT_EQ(bits.to_ulong(), mask);
+        ASSERT_EQ(bits.count(), indices.size());
+        ASSERT_EQ(bits.all(), indices.size() == bits.size());
+        ASSERT_EQ(bits.any(), indices.size() != 0);
+        ASSERT_EQ(bits.none(), indices.size() == 0);
+    }
+};
+
+// Test that by default no bits are set
+TEST_F(ITypBitsetTest, DefaultZero) {
+    Bitset bits;
+    ExpectBits(bits, {});
+}
+
+// Test the bitset can be initialized with a bitmask
+TEST_F(ITypBitsetTest, InitializeByBits) {
+    Bitset bits = {1 << 1 | 1 << 2 | 1 << 7};
+    ExpectBits(bits, {1, 2, 7});
+}
+
+// Test that bits can be set at an index and retrieved from the same index.
+TEST_F(ITypBitsetTest, Indexing) {
+    Bitset bits;
+    ExpectBits(bits, {});
+
+    bits[Key(2)] = true;
+    bits[Key(4)] = false;
+    bits.set(Key(1));
+    bits.set(Key(7), true);
+    bits.set(Key(8), false);
+
+    ExpectBits(bits, {1, 2, 7});
+
+    bits.reset(Key(2));
+    bits.reset(Key(7));
+    ExpectBits(bits, {1});
+}
+
+// Test that bits can be flipped
+TEST_F(ITypBitsetTest, Flip) {
+    Bitset bits = {1 << 1 | 1 << 2 | 1 << 7};
+    ExpectBits(bits, {1, 2, 7});
+
+    bits.flip(Key(4));
+    bits.flip(Key(1));  // false
+    bits.flip(Key(6));
+    bits.flip(Key(5));
+    ExpectBits(bits, {2, 4, 5, 6, 7});
+
+    bits.flip();
+    ExpectBits(bits, {0, 1, 3, 8});
+
+    ExpectBits(~bits, {2, 4, 5, 6, 7});
+}
+
+// Test that all the bits can be set/reset.
+TEST_F(ITypBitsetTest, SetResetAll) {
+    Bitset bits;
+
+    bits.set();
+
+    ASSERT_EQ(bits.count(), 9u);
+    ASSERT_TRUE(bits.all());
+    ASSERT_TRUE(bits.any());
+    ASSERT_FALSE(bits.none());
+
+    for (Key i(0); i < Key(9); ++i) {
+        ASSERT_TRUE(bits[i]);
+    }
+
+    bits.reset();
+
+    ASSERT_EQ(bits.count(), 0u);
+    ASSERT_FALSE(bits.all());
+    ASSERT_FALSE(bits.any());
+    ASSERT_TRUE(bits.none());
+
+    for (Key i(0); i < Key(9); ++i) {
+        ASSERT_FALSE(bits[i]);
+    }
+}
+
+// Test And operations
+TEST_F(ITypBitsetTest, And) {
+    Bitset bits = {1 << 1 | 1 << 2 | 1 << 7};
+    ExpectBits(bits, {1, 2, 7});
+
+    Bitset bits2 = bits & Bitset{1 << 0 | 1 << 3 | 1 << 7};
+    ExpectBits(bits2, {7});
+    ExpectBits(bits, {1, 2, 7});
+
+    bits &= Bitset{1 << 1 | 1 << 6};
+    ExpectBits(bits, {1});
+}
+
+// Test Or operations
+TEST_F(ITypBitsetTest, Or) {
+    Bitset bits = {1 << 1 | 1 << 2 | 1 << 7};
+    ExpectBits(bits, {1, 2, 7});
+
+    Bitset bits2 = bits | Bitset{1 << 0 | 1 << 3 | 1 << 7};
+    ExpectBits(bits2, {0, 1, 2, 3, 7});
+    ExpectBits(bits, {1, 2, 7});
+
+    bits |= Bitset{1 << 1 | 1 << 6};
+    ExpectBits(bits, {1, 2, 6, 7});
+}
+
+// Test xor operations
+TEST_F(ITypBitsetTest, Xor) {
+    Bitset bits = {1 << 1 | 1 << 2 | 1 << 7};
+    ExpectBits(bits, {1, 2, 7});
+
+    Bitset bits2 = bits ^ Bitset { 1 << 0 | 1 << 3 | 1 << 7 };
+    ExpectBits(bits2, {0, 1, 2, 3});
+    ExpectBits(bits, {1, 2, 7});
+
+    bits ^= Bitset{1 << 1 | 1 << 6};
+    ExpectBits(bits, {2, 6, 7});
+}
diff --git a/src/tests/unittests/ITypSpanTests.cpp b/src/tests/unittests/ITypSpanTests.cpp
new file mode 100644
index 0000000..16942ac
--- /dev/null
+++ b/src/tests/unittests/ITypSpanTests.cpp
@@ -0,0 +1,81 @@
+// Copyright 2020 The Dawn 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 <gtest/gtest.h>
+
+#include "common/TypedInteger.h"
+#include "common/ityp_span.h"
+
+#include <array>
+
+class ITypSpanTest : public testing::Test {
+  protected:
+    using Key = TypedInteger<struct KeyT, size_t>;
+    using Val = TypedInteger<struct ValT, uint32_t>;
+    using Span = ityp::span<Key, Val>;
+};
+
+// Test that values can be set at an index and retrieved from the same index.
+TEST_F(ITypSpanTest, Indexing) {
+    std::array<Val, 10> arr;
+    Span span(arr.data(), Key(arr.size()));
+    {
+        span[Key(2)] = Val(5);
+        span[Key(1)] = Val(9);
+        span[Key(9)] = Val(2);
+
+        ASSERT_EQ(span[Key(2)], Val(5));
+        ASSERT_EQ(span[Key(1)], Val(9));
+        ASSERT_EQ(span[Key(9)], Val(2));
+    }
+}
+
+// Test that the span can be is iterated in order with a range-based for loop
+TEST_F(ITypSpanTest, RangeBasedIteration) {
+    std::array<Val, 10> arr;
+    Span span(arr.data(), Key(arr.size()));
+
+    // Assign in a non-const range-based for loop
+    uint32_t i = 0;
+    for (Val& val : span) {
+        val = Val(i);
+    }
+
+    // Check values in a const range-based for loop
+    i = 0;
+    for (Val val : static_cast<const Span&>(span)) {
+        ASSERT_EQ(val, span[Key(i++)]);
+    }
+}
+
+// Test that begin/end/front/back/data return pointers/references to the correct elements.
+TEST_F(ITypSpanTest, BeginEndFrontBackData) {
+    std::array<Val, 10> arr;
+    Span span(arr.data(), Key(arr.size()));
+
+    // non-const versions
+    ASSERT_EQ(span.begin(), &span[Key(0)]);
+    ASSERT_EQ(span.end(), &span[Key(0)] + static_cast<size_t>(span.size()));
+    ASSERT_EQ(&span.front(), &span[Key(0)]);
+    ASSERT_EQ(&span.back(), &span[Key(9)]);
+    ASSERT_EQ(span.data(), &span[Key(0)]);
+
+    // const versions
+    const Span& constSpan = span;
+    ASSERT_EQ(constSpan.begin(), &constSpan[Key(0)]);
+    ASSERT_EQ(constSpan.end(), &constSpan[Key(0)] + static_cast<size_t>(constSpan.size()));
+    ASSERT_EQ(&constSpan.front(), &constSpan[Key(0)]);
+    ASSERT_EQ(&constSpan.back(), &constSpan[Key(9)]);
+    ASSERT_EQ(constSpan.data(), &constSpan[Key(0)]);
+}
diff --git a/src/tests/unittests/TypedIntegerTests.cpp b/src/tests/unittests/TypedIntegerTests.cpp
new file mode 100644
index 0000000..50cfe1d
--- /dev/null
+++ b/src/tests/unittests/TypedIntegerTests.cpp
@@ -0,0 +1,234 @@
+// Copyright 2020 The Dawn 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 <gtest/gtest.h>
+
+#include "common/TypedInteger.h"
+#include "common/UnderlyingType.h"
+
+class TypedIntegerTest : public testing::Test {
+  protected:
+    using Unsigned = TypedInteger<struct UnsignedT, uint32_t>;
+    using Signed = TypedInteger<struct SignedT, int32_t>;
+};
+
+// Test that typed integers can be created and cast and the internal values are identical
+TEST_F(TypedIntegerTest, ConstructionAndCast) {
+    Signed svalue(2);
+    EXPECT_EQ(static_cast<int32_t>(svalue), 2);
+
+    Unsigned uvalue(7);
+    EXPECT_EQ(static_cast<uint32_t>(uvalue), 7u);
+
+    static_assert(static_cast<int32_t>(Signed(3)) == 3, "");
+    static_assert(static_cast<uint32_t>(Unsigned(28)) == 28, "");
+}
+
+// Test typed integer comparison operators
+TEST_F(TypedIntegerTest, Comparison) {
+    Unsigned value(8);
+
+    // Truthy usages of comparison operators
+    EXPECT_TRUE(value < Unsigned(9));
+    EXPECT_TRUE(value <= Unsigned(9));
+    EXPECT_TRUE(value <= Unsigned(8));
+    EXPECT_TRUE(value == Unsigned(8));
+    EXPECT_TRUE(value >= Unsigned(8));
+    EXPECT_TRUE(value >= Unsigned(7));
+    EXPECT_TRUE(value > Unsigned(7));
+    EXPECT_TRUE(value != Unsigned(7));
+
+    // Falsy usages of comparison operators
+    EXPECT_FALSE(value >= Unsigned(9));
+    EXPECT_FALSE(value > Unsigned(9));
+    EXPECT_FALSE(value > Unsigned(8));
+    EXPECT_FALSE(value != Unsigned(8));
+    EXPECT_FALSE(value < Unsigned(8));
+    EXPECT_FALSE(value < Unsigned(7));
+    EXPECT_FALSE(value <= Unsigned(7));
+    EXPECT_FALSE(value == Unsigned(7));
+}
+
+TEST_F(TypedIntegerTest, Arithmetic) {
+    // Postfix Increment
+    {
+        Signed value(0);
+        EXPECT_EQ(value++, Signed(0));
+        EXPECT_EQ(value, Signed(1));
+    }
+
+    // Prefix Increment
+    {
+        Signed value(0);
+        EXPECT_EQ(++value, Signed(1));
+        EXPECT_EQ(value, Signed(1));
+    }
+
+    // Postfix Decrement
+    {
+        Signed value(0);
+        EXPECT_EQ(value--, Signed(0));
+        EXPECT_EQ(value, Signed(-1));
+    }
+
+    // Prefix Decrement
+    {
+        Signed value(0);
+        EXPECT_EQ(--value, Signed(-1));
+        EXPECT_EQ(value, Signed(-1));
+    }
+
+    // Signed addition
+    {
+        Signed a(3);
+        Signed b(-4);
+        Signed c = a + b;
+        EXPECT_EQ(a, Signed(3));
+        EXPECT_EQ(b, Signed(-4));
+        EXPECT_EQ(c, Signed(-1));
+    }
+
+    // Signed subtraction
+    {
+        Signed a(3);
+        Signed b(-4);
+        Signed c = a - b;
+        EXPECT_EQ(a, Signed(3));
+        EXPECT_EQ(b, Signed(-4));
+        EXPECT_EQ(c, Signed(7));
+    }
+
+    // Unsigned addition
+    {
+        Unsigned a(9);
+        Unsigned b(3);
+        Unsigned c = a + b;
+        EXPECT_EQ(a, Unsigned(9));
+        EXPECT_EQ(b, Unsigned(3));
+        EXPECT_EQ(c, Unsigned(12));
+    }
+
+    // Unsigned subtraction
+    {
+        Unsigned a(9);
+        Unsigned b(2);
+        Unsigned c = a - b;
+        EXPECT_EQ(a, Unsigned(9));
+        EXPECT_EQ(b, Unsigned(2));
+        EXPECT_EQ(c, Unsigned(7));
+    }
+
+    // Negation
+    {
+        Signed a(5);
+        Signed b = -a;
+        EXPECT_EQ(a, Signed(5));
+        EXPECT_EQ(b, Signed(-5));
+    }
+}
+
+TEST_F(TypedIntegerTest, NumericLimits) {
+    EXPECT_EQ(std::numeric_limits<Unsigned>::max(), Unsigned(std::numeric_limits<uint32_t>::max()));
+    EXPECT_EQ(std::numeric_limits<Unsigned>::min(), Unsigned(std::numeric_limits<uint32_t>::min()));
+    EXPECT_EQ(std::numeric_limits<Signed>::max(), Signed(std::numeric_limits<int32_t>::max()));
+    EXPECT_EQ(std::numeric_limits<Signed>::min(), Signed(std::numeric_limits<int32_t>::min()));
+}
+
+TEST_F(TypedIntegerTest, UnderlyingType) {
+    static_assert(std::is_same<UnderlyingType<Unsigned>, uint32_t>::value, "");
+    static_assert(std::is_same<UnderlyingType<Signed>, int32_t>::value, "");
+}
+
+// Tests for bounds assertions on arithmetic overflow and underflow.
+#if defined(DAWN_ENABLE_ASSERTS)
+
+TEST_F(TypedIntegerTest, IncrementUnsignedOverflow) {
+    Unsigned value(std::numeric_limits<uint32_t>::max() - 1);
+
+    value++;                    // Doesn't overflow.
+    EXPECT_DEATH(value++, "");  // Overflows.
+}
+
+TEST_F(TypedIntegerTest, IncrementSignedOverflow) {
+    Signed value(std::numeric_limits<int32_t>::max() - 1);
+
+    value++;                    // Doesn't overflow.
+    EXPECT_DEATH(value++, "");  // Overflows.
+}
+
+TEST_F(TypedIntegerTest, DecrementUnsignedUnderflow) {
+    Unsigned value(std::numeric_limits<uint32_t>::min() + 1);
+
+    value--;                    // Doesn't underflow.
+    EXPECT_DEATH(value--, "");  // Underflows.
+}
+
+TEST_F(TypedIntegerTest, DecrementSignedUnderflow) {
+    Signed value(std::numeric_limits<int32_t>::min() + 1);
+
+    value--;                    // Doesn't underflow.
+    EXPECT_DEATH(value--, "");  // Underflows.
+}
+
+TEST_F(TypedIntegerTest, UnsignedAdditionOverflow) {
+    Unsigned value(std::numeric_limits<uint32_t>::max() - 1);
+
+    value + Unsigned(1);                    // Doesn't overflow.
+    EXPECT_DEATH(value + Unsigned(2), "");  // Overflows.
+}
+
+TEST_F(TypedIntegerTest, UnsignedSubtractionUnderflow) {
+    Unsigned value(1);
+
+    value - Unsigned(1);                    // Doesn't underflow.
+    EXPECT_DEATH(value - Unsigned(2), "");  // Underflows.
+}
+
+TEST_F(TypedIntegerTest, SignedAdditionOverflow) {
+    Signed value(std::numeric_limits<int32_t>::max() - 1);
+
+    value + Signed(1);                    // Doesn't overflow.
+    EXPECT_DEATH(value + Signed(2), "");  // Overflows.
+}
+
+TEST_F(TypedIntegerTest, SignedAdditionUnderflow) {
+    Signed value(std::numeric_limits<int32_t>::min() + 1);
+
+    value + Signed(-1);                    // Doesn't underflow.
+    EXPECT_DEATH(value + Signed(-2), "");  // Underflows.
+}
+
+TEST_F(TypedIntegerTest, SignedSubtractionOverflow) {
+    Signed value(std::numeric_limits<int32_t>::max() - 1);
+
+    value - Signed(-1);                    // Doesn't overflow.
+    EXPECT_DEATH(value - Signed(-2), "");  // Overflows.
+}
+
+TEST_F(TypedIntegerTest, SignedSubtractionUnderflow) {
+    Signed value(std::numeric_limits<int32_t>::min() + 1);
+
+    value - Signed(1);                    // Doesn't underflow.
+    EXPECT_DEATH(value - Signed(2), "");  // Underflows.
+}
+
+TEST_F(TypedIntegerTest, NegationOverflow) {
+    Signed maxValue(std::numeric_limits<int32_t>::max());
+    -maxValue;  // Doesn't underflow.
+
+    Signed minValue(std::numeric_limits<int32_t>::min());
+    EXPECT_DEATH(-minValue, "");  // Overflows.
+}
+
+#endif  // defined(DAWN_ENABLE_ASSERTS)