// 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 #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 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); } template constexpr std::enable_if_t::value, TypedIntegerImpl> operator+( TypedIntegerImpl rhs) const { static_assert(std::is_same::value, ""); // Overflow would wrap around ASSERT(this->mValue + rhs.mValue >= this->mValue); return TypedIntegerImpl(this->mValue + rhs.mValue); } template constexpr std::enable_if_t::value, TypedIntegerImpl> operator-( TypedIntegerImpl rhs) const { static_assert(std::is_same::value, ""); // Overflow would wrap around ASSERT(this->mValue - rhs.mValue <= this->mValue); return TypedIntegerImpl(this->mValue - rhs.mValue); } template constexpr std::enable_if_t::value, TypedIntegerImpl> operator+( TypedIntegerImpl rhs) const { static_assert(std::is_same::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::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::min() - this->mValue); } return TypedIntegerImpl(this->mValue + rhs.mValue); } template constexpr std::enable_if_t::value, TypedIntegerImpl> operator-( TypedIntegerImpl rhs) const { static_assert(std::is_same::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::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::min()); } return TypedIntegerImpl(this->mValue - rhs.mValue); } }; } // 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 #endif // COMMON_TYPEDINTEGER_H_