| // 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 "common/UnderlyingType.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 alignas(T) TypedIntegerImpl { |
| static_assert(std::is_integral<T>::value, "TypedInteger must be integral"); |
| T mValue; |
| |
| public: |
| constexpr TypedIntegerImpl() : mValue(0) { |
| static_assert(alignof(TypedIntegerImpl) == alignof(T), ""); |
| static_assert(sizeof(TypedIntegerImpl) == sizeof(T), ""); |
| } |
| |
| // 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> |
| 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) { |
| static_assert(std::is_same<T, T2>::value, ""); |
| |
| // Overflow would wrap around |
| ASSERT(lhs.mValue + rhs.mValue >= lhs.mValue); |
| return lhs.mValue + rhs.mValue; |
| } |
| |
| template <typename T2 = T> |
| 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) { |
| static_assert(std::is_same<T, T2>::value, ""); |
| |
| if (lhs.mValue > 0) { |
| // rhs is positive: |rhs| is at most the distance between max and |lhs|. |
| // rhs is negative: (positive + negative) won't overflow |
| ASSERT(rhs.mValue <= std::numeric_limits<T>::max() - lhs.mValue); |
| } else { |
| // rhs is postive: (negative + positive) won't underflow |
| // rhs is negative: |rhs| isn't less than the (negative) distance between min |
| // and |lhs| |
| ASSERT(rhs.mValue >= std::numeric_limits<T>::min() - lhs.mValue); |
| } |
| return lhs.mValue + rhs.mValue; |
| } |
| |
| template <typename T2 = T> |
| 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) { |
| static_assert(std::is_same<T, T2>::value, ""); |
| |
| // Overflow would wrap around |
| ASSERT(lhs.mValue - rhs.mValue <= lhs.mValue); |
| return lhs.mValue - rhs.mValue; |
| } |
| |
| template <typename T2 = T> |
| static constexpr std::enable_if_t<std::is_signed<T>::value, decltype(T(0) - T2(0))> SubImpl( |
| TypedIntegerImpl<Tag, T> lhs, |
| TypedIntegerImpl<Tag, T2> rhs) { |
| static_assert(std::is_same<T, T2>::value, ""); |
| |
| if (lhs.mValue > 0) { |
| // rhs is positive: positive minus positive won't overflow |
| // rhs is negative: |rhs| isn't less than the (negative) distance between |lhs| |
| // and max. |
| ASSERT(rhs.mValue >= lhs.mValue - std::numeric_limits<T>::max()); |
| } else { |
| // rhs is positive: |rhs| is at most the distance between min and |lhs| |
| // rhs is negative: negative minus negative won't overflow |
| ASSERT(rhs.mValue <= lhs.mValue - std::numeric_limits<T>::min()); |
| } |
| return lhs.mValue - rhs.mValue; |
| } |
| |
| 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); |
| } |
| |
| constexpr TypedIntegerImpl operator+(TypedIntegerImpl rhs) const { |
| auto result = AddImpl(*this, rhs); |
| static_assert(std::is_same<T, decltype(result)>::value, "Use ityp::Add instead."); |
| return TypedIntegerImpl(result); |
| } |
| |
| constexpr TypedIntegerImpl operator-(TypedIntegerImpl rhs) const { |
| auto result = SubImpl(*this, rhs); |
| static_assert(std::is_same<T, decltype(result)>::value, "Use ityp::Sub instead."); |
| return TypedIntegerImpl(result); |
| } |
| }; |
| |
| } // 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()); |
| } |
| }; |
| |
| } // namespace std |
| |
| namespace ityp { |
| |
| // These helpers below are provided since the default arithmetic operators for small integer |
| // types like uint8_t and uint16_t return integers, not their same type. To avoid lots of |
| // casting or conditional code between Release/Debug. Callsites should use ityp::Add(a, b) and |
| // ityp::Sub(a, b) instead. |
| |
| template <typename Tag, typename T> |
| constexpr ::detail::TypedIntegerImpl<Tag, T> Add(::detail::TypedIntegerImpl<Tag, T> lhs, |
| ::detail::TypedIntegerImpl<Tag, T> rhs) { |
| return ::detail::TypedIntegerImpl<Tag, T>( |
| static_cast<T>(::detail::TypedIntegerImpl<Tag, T>::AddImpl(lhs, rhs))); |
| } |
| |
| template <typename Tag, typename T> |
| constexpr ::detail::TypedIntegerImpl<Tag, T> Sub(::detail::TypedIntegerImpl<Tag, T> lhs, |
| ::detail::TypedIntegerImpl<Tag, T> rhs) { |
| return ::detail::TypedIntegerImpl<Tag, T>( |
| static_cast<T>(::detail::TypedIntegerImpl<Tag, T>::SubImpl(lhs, rhs))); |
| } |
| |
| template <typename T> |
| constexpr std::enable_if_t<std::is_integral<T>::value, T> Add(T lhs, T rhs) { |
| return static_cast<T>(lhs + rhs); |
| } |
| |
| template <typename T> |
| constexpr std::enable_if_t<std::is_integral<T>::value, T> Sub(T lhs, T rhs) { |
| return static_cast<T>(lhs - rhs); |
| } |
| |
| } // namespace ityp |
| |
| #endif // COMMON_TYPEDINTEGER_H_ |