// 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 #include // 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; // using UintB = TypedInteger; // // in Release: // using UintA = uint32_t; // using UintB = uint32_t; // // in Debug: // using UintA = detail::TypedIntegerImpl; // using UintB = detail::TypedIntegerImpl; // // 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(a); // namespace detail { template class TypedIntegerImpl; } // namespace detail template ::value>> #if defined(DAWN_ENABLE_ASSERTS) using TypedInteger = detail::TypedIntegerImpl; #else using TypedInteger = T; #endif namespace detail { template class alignas(T) TypedIntegerImpl { static_assert(std::is_integral::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 ::value && std::numeric_limits::max() <= std::numeric_limits::max() && std::numeric_limits::min() >= std::numeric_limits::min()>> explicit constexpr TypedIntegerImpl(I rhs) : mValue(static_cast(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(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::max()); ++this->mValue; return *this; } constexpr TypedIntegerImpl operator++(int) { TypedIntegerImpl ret = *this; ASSERT(this->mValue < std::numeric_limits::max()); ++this->mValue; return ret; } constexpr TypedIntegerImpl& operator--() { assert(this->mValue > std::numeric_limits::min()); --this->mValue; return *this; } constexpr TypedIntegerImpl operator--(int) { TypedIntegerImpl ret = *this; ASSERT(this->mValue > std::numeric_limits::min()); --this->mValue; return ret; } template static constexpr std::enable_if_t::value, decltype(T(0) + T2(0))> AddImpl(TypedIntegerImpl lhs, TypedIntegerImpl rhs) { static_assert(std::is_same::value, ""); // Overflow would wrap around ASSERT(lhs.mValue + rhs.mValue >= lhs.mValue); return lhs.mValue + rhs.mValue; } template static constexpr std::enable_if_t::value, decltype(T(0) + T2(0))> AddImpl(TypedIntegerImpl lhs, TypedIntegerImpl rhs) { static_assert(std::is_same::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::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::min() - lhs.mValue); } return lhs.mValue + rhs.mValue; } template static constexpr std::enable_if_t::value, decltype(T(0) - T2(0))> SubImpl(TypedIntegerImpl lhs, TypedIntegerImpl rhs) { static_assert(std::is_same::value, ""); // Overflow would wrap around ASSERT(lhs.mValue - rhs.mValue <= lhs.mValue); return lhs.mValue - rhs.mValue; } template static constexpr std::enable_if_t::value, decltype(T(0) - T2(0))> SubImpl( TypedIntegerImpl lhs, TypedIntegerImpl rhs) { static_assert(std::is_same::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::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::min()); } return lhs.mValue - rhs.mValue; } template constexpr std::enable_if_t::value, TypedIntegerImpl> operator-() const { static_assert(std::is_same::value, ""); // The negation of the most negative value cannot be represented. ASSERT(this->mValue != std::numeric_limits::min()); return TypedIntegerImpl(-this->mValue); } constexpr TypedIntegerImpl operator+(TypedIntegerImpl rhs) const { auto result = AddImpl(*this, rhs); static_assert(std::is_same::value, "Use ityp::Add instead."); return TypedIntegerImpl(result); } constexpr TypedIntegerImpl operator-(TypedIntegerImpl rhs) const { auto result = SubImpl(*this, rhs); static_assert(std::is_same::value, "Use ityp::Sub instead."); return TypedIntegerImpl(result); } }; } // namespace detail namespace std { template class numeric_limits> : public numeric_limits { public: static detail::TypedIntegerImpl max() noexcept { return detail::TypedIntegerImpl(std::numeric_limits::max()); } static detail::TypedIntegerImpl min() noexcept { return detail::TypedIntegerImpl(std::numeric_limits::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 constexpr ::detail::TypedIntegerImpl Add(::detail::TypedIntegerImpl lhs, ::detail::TypedIntegerImpl rhs) { return ::detail::TypedIntegerImpl( static_cast(::detail::TypedIntegerImpl::AddImpl(lhs, rhs))); } template constexpr ::detail::TypedIntegerImpl Sub(::detail::TypedIntegerImpl lhs, ::detail::TypedIntegerImpl rhs) { return ::detail::TypedIntegerImpl( static_cast(::detail::TypedIntegerImpl::SubImpl(lhs, rhs))); } template constexpr std::enable_if_t::value, T> Add(T lhs, T rhs) { return static_cast(lhs + rhs); } template constexpr std::enable_if_t::value, T> Sub(T lhs, T rhs) { return static_cast(lhs - rhs); } } // namespace ityp #endif // COMMON_TYPEDINTEGER_H_