blob: fd458952835753c9b8afc55689395445fb99bb53 [file] [log] [blame]
// 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_