1 // Copyright 2020 The Dawn Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef COMMON_TYPEDINTEGER_H_ 16 #define COMMON_TYPEDINTEGER_H_ 17 18 #include "common/Assert.h" 19 #include "common/UnderlyingType.h" 20 21 #include <limits> 22 #include <type_traits> 23 24 // TypedInteger is helper class that provides additional type safety in Debug. 25 // - Integers of different (Tag, BaseIntegerType) may not be used interoperably 26 // - Allows casts only to the underlying type. 27 // - Integers of the same (Tag, BaseIntegerType) may be compared or assigned. 28 // This class helps ensure that the many types of indices in Dawn aren't mixed up and used 29 // interchangably. 30 // In Release builds, when DAWN_ENABLE_ASSERTS is not defined, TypedInteger is a passthrough 31 // typedef of the underlying type. 32 // 33 // Example: 34 // using UintA = TypedInteger<struct TypeA, uint32_t>; 35 // using UintB = TypedInteger<struct TypeB, uint32_t>; 36 // 37 // in Release: 38 // using UintA = uint32_t; 39 // using UintB = uint32_t; 40 // 41 // in Debug: 42 // using UintA = detail::TypedIntegerImpl<struct TypeA, uint32_t>; 43 // using UintB = detail::TypedIntegerImpl<struct TypeB, uint32_t>; 44 // 45 // Assignment, construction, comparison, and arithmetic with TypedIntegerImpl are allowed 46 // only for typed integers of exactly the same type. Further, they must be 47 // created / cast explicitly; there is no implicit conversion. 48 // 49 // UintA a(2); 50 // uint32_t aValue = static_cast<uint32_t>(a); 51 // 52 namespace detail { 53 template <typename Tag, typename T> 54 class TypedIntegerImpl; 55 } // namespace detail 56 57 template <typename Tag, typename T, typename = std::enable_if_t<std::is_integral<T>::value>> 58 #if defined(DAWN_ENABLE_ASSERTS) 59 using TypedInteger = detail::TypedIntegerImpl<Tag, T>; 60 #else 61 using TypedInteger = T; 62 #endif 63 64 namespace detail { 65 template <typename Tag, typename T> 66 class alignas(T) TypedIntegerImpl { 67 static_assert(std::is_integral<T>::value, "TypedInteger must be integral"); 68 T mValue; 69 70 public: TypedIntegerImpl()71 constexpr TypedIntegerImpl() : mValue(0) { 72 static_assert(alignof(TypedIntegerImpl) == alignof(T), ""); 73 static_assert(sizeof(TypedIntegerImpl) == sizeof(T), ""); 74 } 75 76 // Construction from non-narrowing integral types. 77 template <typename I, 78 typename = std::enable_if_t< 79 std::is_integral<I>::value && 80 std::numeric_limits<I>::max() <= std::numeric_limits<T>::max() && 81 std::numeric_limits<I>::min() >= std::numeric_limits<T>::min()>> TypedIntegerImpl(I rhs)82 explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast<T>(rhs)) { 83 } 84 85 // Allow explicit casts only to the underlying type. If you're casting out of an 86 // TypedInteger, you should know what what you're doing, and exactly what type you 87 // expect. T()88 explicit constexpr operator T() const { 89 return static_cast<T>(this->mValue); 90 } 91 92 // Same-tag TypedInteger comparison operators 93 #define TYPED_COMPARISON(op) \ 94 constexpr bool operator op(const TypedIntegerImpl& rhs) const { \ 95 return mValue op rhs.mValue; \ 96 } 97 TYPED_COMPARISON(<) 98 TYPED_COMPARISON(<=) 99 TYPED_COMPARISON(>) 100 TYPED_COMPARISON(>=) 101 TYPED_COMPARISON(==) 102 TYPED_COMPARISON(!=) 103 #undef TYPED_COMPARISON 104 105 // Increment / decrement operators for for-loop iteration 106 constexpr TypedIntegerImpl& operator++() { 107 ASSERT(this->mValue < std::numeric_limits<T>::max()); 108 ++this->mValue; 109 return *this; 110 } 111 112 constexpr TypedIntegerImpl operator++(int) { 113 TypedIntegerImpl ret = *this; 114 115 ASSERT(this->mValue < std::numeric_limits<T>::max()); 116 ++this->mValue; 117 return ret; 118 } 119 120 constexpr TypedIntegerImpl& operator--() { 121 assert(this->mValue > std::numeric_limits<T>::min()); 122 --this->mValue; 123 return *this; 124 } 125 126 constexpr TypedIntegerImpl operator--(int) { 127 TypedIntegerImpl ret = *this; 128 129 ASSERT(this->mValue > std::numeric_limits<T>::min()); 130 --this->mValue; 131 return ret; 132 } 133 134 template <typename T2 = T> 135 static constexpr std::enable_if_t<std::is_unsigned<T2>::value, decltype(T(0) + T2(0))> AddImpl(TypedIntegerImpl<Tag,T> lhs,TypedIntegerImpl<Tag,T2> rhs)136 AddImpl(TypedIntegerImpl<Tag, T> lhs, TypedIntegerImpl<Tag, T2> rhs) { 137 static_assert(std::is_same<T, T2>::value, ""); 138 139 // Overflow would wrap around 140 ASSERT(lhs.mValue + rhs.mValue >= lhs.mValue); 141 return lhs.mValue + rhs.mValue; 142 } 143 144 template <typename T2 = T> 145 static constexpr std::enable_if_t<std::is_signed<T2>::value, decltype(T(0) + T2(0))> AddImpl(TypedIntegerImpl<Tag,T> lhs,TypedIntegerImpl<Tag,T2> rhs)146 AddImpl(TypedIntegerImpl<Tag, T> lhs, TypedIntegerImpl<Tag, T2> rhs) { 147 static_assert(std::is_same<T, T2>::value, ""); 148 149 if (lhs.mValue > 0) { 150 // rhs is positive: |rhs| is at most the distance between max and |lhs|. 151 // rhs is negative: (positive + negative) won't overflow 152 ASSERT(rhs.mValue <= std::numeric_limits<T>::max() - lhs.mValue); 153 } else { 154 // rhs is postive: (negative + positive) won't underflow 155 // rhs is negative: |rhs| isn't less than the (negative) distance between min 156 // and |lhs| 157 ASSERT(rhs.mValue >= std::numeric_limits<T>::min() - lhs.mValue); 158 } 159 return lhs.mValue + rhs.mValue; 160 } 161 162 template <typename T2 = T> 163 static constexpr std::enable_if_t<std::is_unsigned<T>::value, decltype(T(0) - T2(0))> SubImpl(TypedIntegerImpl<Tag,T> lhs,TypedIntegerImpl<Tag,T2> rhs)164 SubImpl(TypedIntegerImpl<Tag, T> lhs, TypedIntegerImpl<Tag, T2> rhs) { 165 static_assert(std::is_same<T, T2>::value, ""); 166 167 // Overflow would wrap around 168 ASSERT(lhs.mValue - rhs.mValue <= lhs.mValue); 169 return lhs.mValue - rhs.mValue; 170 } 171 172 template <typename T2 = T> SubImpl(TypedIntegerImpl<Tag,T> lhs,TypedIntegerImpl<Tag,T2> rhs)173 static constexpr std::enable_if_t<std::is_signed<T>::value, decltype(T(0) - T2(0))> SubImpl( 174 TypedIntegerImpl<Tag, T> lhs, 175 TypedIntegerImpl<Tag, T2> rhs) { 176 static_assert(std::is_same<T, T2>::value, ""); 177 178 if (lhs.mValue > 0) { 179 // rhs is positive: positive minus positive won't overflow 180 // rhs is negative: |rhs| isn't less than the (negative) distance between |lhs| 181 // and max. 182 ASSERT(rhs.mValue >= lhs.mValue - std::numeric_limits<T>::max()); 183 } else { 184 // rhs is positive: |rhs| is at most the distance between min and |lhs| 185 // rhs is negative: negative minus negative won't overflow 186 ASSERT(rhs.mValue <= lhs.mValue - std::numeric_limits<T>::min()); 187 } 188 return lhs.mValue - rhs.mValue; 189 } 190 191 template <typename T2 = T> 192 constexpr std::enable_if_t<std::is_signed<T2>::value, TypedIntegerImpl> operator-() const { 193 static_assert(std::is_same<T, T2>::value, ""); 194 // The negation of the most negative value cannot be represented. 195 ASSERT(this->mValue != std::numeric_limits<T>::min()); 196 return TypedIntegerImpl(-this->mValue); 197 } 198 199 constexpr TypedIntegerImpl operator+(TypedIntegerImpl rhs) const { 200 auto result = AddImpl(*this, rhs); 201 static_assert(std::is_same<T, decltype(result)>::value, "Use ityp::Add instead."); 202 return TypedIntegerImpl(result); 203 } 204 205 constexpr TypedIntegerImpl operator-(TypedIntegerImpl rhs) const { 206 auto result = SubImpl(*this, rhs); 207 static_assert(std::is_same<T, decltype(result)>::value, "Use ityp::Sub instead."); 208 return TypedIntegerImpl(result); 209 } 210 }; 211 212 } // namespace detail 213 214 namespace std { 215 216 template <typename Tag, typename T> 217 class numeric_limits<detail::TypedIntegerImpl<Tag, T>> : public numeric_limits<T> { 218 public: max()219 static detail::TypedIntegerImpl<Tag, T> max() noexcept { 220 return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::max()); 221 } min()222 static detail::TypedIntegerImpl<Tag, T> min() noexcept { 223 return detail::TypedIntegerImpl<Tag, T>(std::numeric_limits<T>::min()); 224 } 225 }; 226 227 } // namespace std 228 229 namespace ityp { 230 231 // These helpers below are provided since the default arithmetic operators for small integer 232 // types like uint8_t and uint16_t return integers, not their same type. To avoid lots of 233 // casting or conditional code between Release/Debug. Callsites should use ityp::Add(a, b) and 234 // ityp::Sub(a, b) instead. 235 236 template <typename Tag, typename T> Add(::detail::TypedIntegerImpl<Tag,T> lhs,::detail::TypedIntegerImpl<Tag,T> rhs)237 constexpr ::detail::TypedIntegerImpl<Tag, T> Add(::detail::TypedIntegerImpl<Tag, T> lhs, 238 ::detail::TypedIntegerImpl<Tag, T> rhs) { 239 return ::detail::TypedIntegerImpl<Tag, T>( 240 static_cast<T>(::detail::TypedIntegerImpl<Tag, T>::AddImpl(lhs, rhs))); 241 } 242 243 template <typename Tag, typename T> Sub(::detail::TypedIntegerImpl<Tag,T> lhs,::detail::TypedIntegerImpl<Tag,T> rhs)244 constexpr ::detail::TypedIntegerImpl<Tag, T> Sub(::detail::TypedIntegerImpl<Tag, T> lhs, 245 ::detail::TypedIntegerImpl<Tag, T> rhs) { 246 return ::detail::TypedIntegerImpl<Tag, T>( 247 static_cast<T>(::detail::TypedIntegerImpl<Tag, T>::SubImpl(lhs, rhs))); 248 } 249 250 template <typename T> Add(T lhs,T rhs)251 constexpr std::enable_if_t<std::is_integral<T>::value, T> Add(T lhs, T rhs) { 252 return static_cast<T>(lhs + rhs); 253 } 254 255 template <typename T> Sub(T lhs,T rhs)256 constexpr std::enable_if_t<std::is_integral<T>::value, T> Sub(T lhs, T rhs) { 257 return static_cast<T>(lhs - rhs); 258 } 259 260 } // namespace ityp 261 262 #endif // COMMON_TYPEDINTEGER_H_ 263