• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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