263 lines
10 KiB
C++
263 lines
10 KiB
C++
// 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_
|