Pull safe_conversions from chromium

Manual copy and paste of all code found in the namespace base. I didn't
change any of the implementation code. Pull in a new file for optimized
ARM implementation.

Added a list of adaptions made to document what is different from
chromium.

Change-Id: I88b4af45437506cf57755e48fdfc88027a5aed33
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2436610
Commit-Queue: Zhi An Ng <zhin@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70452}
This commit is contained in:
Ng Zhi An 2020-10-12 09:13:18 -07:00 committed by Commit Bot
parent 1dac9f3b10
commit c10c83c31b
4 changed files with 1116 additions and 137 deletions

View File

@ -4065,6 +4065,7 @@ v8_component("v8_libbase") {
"src/base/region-allocator.h", "src/base/region-allocator.h",
"src/base/ring-buffer.h", "src/base/ring-buffer.h",
"src/base/safe_conversions.h", "src/base/safe_conversions.h",
"src/base/safe_conversions_arm_impl.h",
"src/base/safe_conversions_impl.h", "src/base/safe_conversions_impl.h",
"src/base/small-vector.h", "src/base/small-vector.h",
"src/base/sys-info.cc", "src/base/sys-info.cc",

View File

@ -4,59 +4,383 @@
// Slightly adapted for inclusion in V8. // Slightly adapted for inclusion in V8.
// Copyright 2014 the V8 project authors. All rights reserved. // Copyright 2014 the V8 project authors. All rights reserved.
// List of adaptations:
// - include guard names
// - wrap in v8 namespace
// - formatting (git cl format)
// - include paths
#ifndef V8_BASE_SAFE_CONVERSIONS_H_ #ifndef V8_BASE_SAFE_CONVERSIONS_H_
#define V8_BASE_SAFE_CONVERSIONS_H_ #define V8_BASE_SAFE_CONVERSIONS_H_
#include <stddef.h>
#include <cmath>
#include <limits> #include <limits>
#include <type_traits>
#include "src/base/safe_conversions_impl.h" #include "src/base/safe_conversions_impl.h"
#if defined(__ARMEL__) && !defined(__native_client__)
#include "src/base/safe_conversions_arm_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
#endif
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
#include <ostream>
#endif
namespace v8 { namespace v8 {
namespace base { namespace base {
namespace internal {
#if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static constexpr bool is_supported = false;
static constexpr Dst Do(Src) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
#endif // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
// The following special case a few specific integer conversions where we can
// eke out better performance than range checking.
template <typename Dst, typename Src, typename Enable = void>
struct IsValueInRangeFastOp {
static constexpr bool is_supported = false;
static constexpr bool Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
// Signed to signed range comparison.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Dst, Src,
typename std::enable_if<
std::is_integral<Dst>::value && std::is_integral<Src>::value &&
std::is_signed<Dst>::value && std::is_signed<Src>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
static constexpr bool is_supported = true;
static constexpr bool Do(Src value) {
// Just downcast to the smaller type, sign extend it back to the original
// type, and then see if it matches the original value.
return value == static_cast<Dst>(value);
}
};
// Signed to unsigned range comparison.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Dst, Src,
typename std::enable_if<
std::is_integral<Dst>::value && std::is_integral<Src>::value &&
!std::is_signed<Dst>::value && std::is_signed<Src>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
static constexpr bool is_supported = true;
static constexpr bool Do(Src value) {
// We cast a signed as unsigned to overflow negative values to the top,
// then compare against whichever maximum is smaller, as our upper bound.
return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
}
};
// Convenience function that returns true if the supplied value is in range // Convenience function that returns true if the supplied value is in range
// for the destination type. // for the destination type.
template <typename Dst, typename Src> template <typename Dst, typename Src>
inline bool IsValueInRangeForNumericType(Src value) { constexpr bool IsValueInRangeForNumericType(Src value) {
return internal::DstRangeRelationToSrcRange<Dst>(value) == using SrcType = typename internal::UnderlyingType<Src>::type;
internal::RANGE_VALID; return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
static_cast<SrcType>(value))
: internal::DstRangeRelationToSrcRange<Dst>(
static_cast<SrcType>(value))
.IsValid();
} }
// checked_cast<> is analogous to static_cast<> for numeric types, // checked_cast<> is analogous to static_cast<> for numeric types,
// except that it CHECKs that the specified numeric conversion will not // except that it CHECKs that the specified numeric conversion will not
// overflow or underflow. NaN source will always trigger a CHECK. // overflow or underflow. NaN source will always trigger a CHECK.
template <typename Dst, typename Src> template <typename Dst, class CheckHandler = internal::CheckOnFailure,
inline Dst checked_cast(Src value) { typename Src>
CHECK(IsValueInRangeForNumericType<Dst>(value)); constexpr Dst checked_cast(Src value) {
return static_cast<Dst>(value); // This throws a compile-time error on evaluating the constexpr if it can be
// determined at compile-time as failing, otherwise it will CHECK at runtime.
using SrcType = typename internal::UnderlyingType<Src>::type;
return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
? static_cast<Dst>(static_cast<SrcType>(value))
: CheckHandler::template HandleFailure<Dst>();
} }
// saturated_cast<> is analogous to static_cast<> for numeric types, except // Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
// that the specified numeric conversion will saturate rather than overflow or // You may provide your own limits (e.g. to saturated_cast) so long as you
// underflow. NaN assignment to an integral will trigger a CHECK condition. // implement all of the static constexpr member functions in the class below.
template <typename T>
struct SaturationDefaultLimits : public std::numeric_limits<T> {
static constexpr T NaN() {
return std::numeric_limits<T>::has_quiet_NaN
? std::numeric_limits<T>::quiet_NaN()
: T();
}
using std::numeric_limits<T>::max;
static constexpr T Overflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::max();
}
using std::numeric_limits<T>::lowest;
static constexpr T Underflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity() * -1
: std::numeric_limits<T>::lowest();
}
};
template <typename Dst, template <typename> class S, typename Src>
constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
// For some reason clang generates much better code when the branch is
// structured exactly this way, rather than a sequence of checks.
return !constraint.IsOverflowFlagSet()
? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
: S<Dst>::Underflow())
// Skip this check for integral Src, which cannot be NaN.
: (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
? S<Dst>::Overflow()
: S<Dst>::NaN());
}
// We can reduce the number of conditions and get slightly better performance
// for normal signed and unsigned integer ranges. And in the specific case of
// Arm, we can use the optimized saturation instructions.
template <typename Dst, typename Src, typename Enable = void>
struct SaturateFastOp {
static constexpr bool is_supported = false;
static constexpr Dst Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
template <typename Dst, typename Src> template <typename Dst, typename Src>
inline Dst saturated_cast(Src value) { struct SaturateFastOp<
// Optimization for floating point values, which already saturate. Dst, Src,
if (std::numeric_limits<Dst>::is_iec559) typename std::enable_if<std::is_integral<Src>::value &&
return static_cast<Dst>(value); std::is_integral<Dst>::value &&
SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
static constexpr bool is_supported = true;
static constexpr Dst Do(Src value) {
return SaturateFastAsmOp<Dst, Src>::Do(value);
}
};
switch (internal::DstRangeRelationToSrcRange<Dst>(value)) { template <typename Dst, typename Src>
case internal::RANGE_VALID: struct SaturateFastOp<
return static_cast<Dst>(value); Dst, Src,
typename std::enable_if<std::is_integral<Src>::value &&
std::is_integral<Dst>::value &&
!SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
static constexpr bool is_supported = true;
static constexpr Dst Do(Src value) {
// The exact order of the following is structured to hit the correct
// optimization heuristics across compilers. Do not change without
// checking the emitted code.
const Dst saturated = CommonMaxOrMin<Dst, Src>(
IsMaxInRangeForNumericType<Dst, Src>() ||
(!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
? static_cast<Dst>(value)
: saturated;
}
};
case internal::RANGE_UNDERFLOW: // saturated_cast<> is analogous to static_cast<> for numeric types, except
return std::numeric_limits<Dst>::min(); // that the specified numeric conversion will saturate by default rather than
// overflow or underflow, and NaN assignment to an integral will return 0.
// All boundary condition behaviors can be overriden with a custom handler.
template <typename Dst,
template <typename> class SaturationHandler = SaturationDefaultLimits,
typename Src>
constexpr Dst saturated_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
return !IsCompileTimeConstant(value) &&
SaturateFastOp<Dst, SrcType>::is_supported &&
std::is_same<SaturationHandler<Dst>,
SaturationDefaultLimits<Dst>>::value
? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
: saturated_cast_impl<Dst, SaturationHandler, SrcType>(
static_cast<SrcType>(value),
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
static_cast<SrcType>(value)));
}
case internal::RANGE_OVERFLOW: // strict_cast<> is analogous to static_cast<> for numeric types, except that
return std::numeric_limits<Dst>::max(); // it will cause a compile failure if the destination type is not large enough
// to contain any value in the source type. It performs no runtime checking.
template <typename Dst, typename Src>
constexpr Dst strict_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
// Should fail only on attempting to assign NaN to a saturated integer. // If you got here from a compiler error, it's because you tried to assign
case internal::RANGE_INVALID: // from a source type to a destination type that has insufficient range.
UNREACHABLE(); // The solution may be to change the destination type you're assigning to,
// and use one large enough to represent the source.
// Alternatively, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value ==
NUMERIC_RANGE_CONTAINED,
"The source type is out of range for the destination type. "
"Please see strict_cast<> comments for more information.");
return static_cast<Dst>(static_cast<SrcType>(value));
}
// Some wrappers to statically check that a type is in range.
template <typename Dst, typename Src, class Enable = void>
struct IsNumericRangeContained {
static constexpr bool value = false;
};
template <typename Dst, typename Src>
struct IsNumericRangeContained<
Dst, Src,
typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
ArithmeticOrUnderlyingEnum<Src>::value>::type> {
static constexpr bool value =
StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
NUMERIC_RANGE_CONTAINED;
};
// StrictNumeric implements compile time range checking between numeric types by
// wrapping assignment operations in a strict_cast. This class is intended to be
// used for function arguments and return types, to ensure the destination type
// can always contain the source type. This is essentially the same as enforcing
// -Wconversion in gcc and C4302 warnings on MSVC, but it can be applied
// incrementally at API boundaries, making it easier to convert code so that it
// compiles cleanly with truncation warnings enabled.
// This template should introduce no runtime overhead, but it also provides no
// runtime checking of any of the associated mathematical operations. Use
// CheckedNumeric for runtime range checks of the actual value being assigned.
template <typename T>
class StrictNumeric {
public:
using type = T;
constexpr StrictNumeric() : value_(0) {}
// Copy constructor.
template <typename Src>
constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
: value_(strict_cast<T>(rhs.value_)) {}
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to StrictNumerics to make them easier to use.
template <typename Src>
constexpr StrictNumeric(Src value) // NOLINT(runtime/explicit)
: value_(strict_cast<T>(value)) {}
// If you got here from a compiler error, it's because you tried to assign
// from a source type to a destination type that has insufficient range.
// The solution may be to change the destination type you're assigning to,
// and use one large enough to represent the source.
// If you're assigning from a CheckedNumeric<> class, you may be able to use
// the AssignIfValid() member function, specify a narrower destination type to
// the member value functions (e.g. val.template ValueOrDie<Dst>()), use one
// of the value helper functions (e.g. ValueOrDieForType<Dst>(val)).
// If you've encountered an _ambiguous overload_ you can use a static_cast<>
// to explicitly cast the result to the destination type.
// If none of that works, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
template <typename Dst, typename std::enable_if<IsNumericRangeContained<
Dst, T>::value>::type* = nullptr>
constexpr operator Dst() const {
return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
} }
UNREACHABLE(); private:
const T value_;
};
// Convience wrapper returns a StrictNumeric from the provided arithmetic type.
template <typename T>
constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
const T value) {
return value;
}
#if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
// Overload the ostream output operator to make logging work nicely.
template <typename T>
std::ostream& operator<<(std::ostream& os, const StrictNumeric<T>& value) {
os << static_cast<T>(value);
return os;
}
#endif
#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
template <typename L, typename R, \
typename std::enable_if< \
internal::Is##CLASS##Op<L, R>::value>::type* = nullptr> \
constexpr bool operator OP(const L lhs, const R rhs) { \
return SafeCompare<NAME, typename UnderlyingType<L>::type, \
typename UnderlyingType<R>::type>(lhs, rhs); \
}
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==)
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=)
} // namespace internal
using internal::as_signed;
using internal::as_unsigned;
using internal::checked_cast;
using internal::IsTypeInRangeForNumericType;
using internal::IsValueInRangeForNumericType;
using internal::IsValueNegative;
using internal::MakeStrictNum;
using internal::SafeUnsignedAbs;
using internal::saturated_cast;
using internal::strict_cast;
using internal::StrictNumeric;
// Explicitly make a shorter size_t alias for convenience.
using SizeT = StrictNumeric<size_t>;
// floating -> integral conversions that saturate and thus can actually return
// an integral type. In most cases, these should be preferred over the std::
// versions.
template <typename Dst = int, typename Src,
typename = std::enable_if_t<std::is_integral<Dst>::value &&
std::is_floating_point<Src>::value>>
Dst ClampFloor(Src value) {
return saturated_cast<Dst>(std::floor(value));
}
template <typename Dst = int, typename Src,
typename = std::enable_if_t<std::is_integral<Dst>::value &&
std::is_floating_point<Src>::value>>
Dst ClampCeil(Src value) {
return saturated_cast<Dst>(std::ceil(value));
}
template <typename Dst = int, typename Src,
typename = std::enable_if_t<std::is_integral<Dst>::value &&
std::is_floating_point<Src>::value>>
Dst ClampRound(Src value) {
const Src rounded =
(value >= 0.0f) ? std::floor(value + 0.5f) : std::ceil(value - 0.5f);
return saturated_cast<Dst>(rounded);
} }
} // namespace base } // namespace base

View File

@ -0,0 +1,60 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Slightly adapted for inclusion in V8.
// Copyright 2014 the V8 project authors. All rights reserved.
// List of adaptations:
// - include guard names
// - wrap in v8 namespace
// - include paths
#ifndef V8_BASE_SAFE_CONVERSIONS_ARM_IMPL_H_
#define V8_BASE_SAFE_CONVERSIONS_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "src/base/safe_conversions_impl.h"
namespace v8 {
namespace base {
namespace internal {
// Fast saturation to a destination type.
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static constexpr bool is_supported =
std::is_signed<Src>::value && std::is_integral<Dst>::value &&
std::is_integral<Src>::value &&
IntegerBitsPlusSign<Src>::value <= IntegerBitsPlusSign<int32_t>::value &&
IntegerBitsPlusSign<Dst>::value <= IntegerBitsPlusSign<int32_t>::value &&
!IsTypeInRangeForNumericType<Dst, Src>::value;
__attribute__((always_inline)) static Dst Do(Src value) {
int32_t src = value;
typename std::conditional<std::is_signed<Dst>::value, int32_t,
uint32_t>::type result;
if (std::is_signed<Dst>::value) {
asm("ssat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
: [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
? IntegerBitsPlusSign<Dst>::value
: 32));
} else {
asm("usat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
: [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value < 32
? IntegerBitsPlusSign<Dst>::value
: 31));
}
return static_cast<Dst>(result);
}
};
} // namespace internal
} // namespace base
} // namespace v8
#endif // V8_BASE_SAFE_CONVERSIONS_ARM_IMPL_H_

View File

@ -4,28 +4,130 @@
// Slightly adapted for inclusion in V8. // Slightly adapted for inclusion in V8.
// Copyright 2014 the V8 project authors. All rights reserved. // Copyright 2014 the V8 project authors. All rights reserved.
// List of adaptations:
// - include guard names
// - wrap in v8 namespace
// - formatting (git cl format)
#ifndef V8_BASE_SAFE_CONVERSIONS_IMPL_H_ #ifndef V8_BASE_SAFE_CONVERSIONS_IMPL_H_
#define V8_BASE_SAFE_CONVERSIONS_IMPL_H_ #define V8_BASE_SAFE_CONVERSIONS_IMPL_H_
#include <limits> #include <stdint.h>
#include "src/base/logging.h" #include <limits>
#include "src/base/macros.h" #include <type_traits>
#if defined(__GNUC__) || defined(__clang__)
#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define BASE_NUMERICS_LIKELY(x) (x)
#define BASE_NUMERICS_UNLIKELY(x) (x)
#endif
namespace v8 { namespace v8 {
namespace base { namespace base {
namespace internal { namespace internal {
// The std library doesn't provide a binary max_exponent for integers, however // The std library doesn't provide a binary max_exponent for integers, however
// we can compute one by adding one to the number of non-sign bits. This allows // we can compute an analog using std::numeric_limits<>::digits.
// for accurate range comparisons between floating point and integer types.
template <typename NumericType> template <typename NumericType>
struct MaxExponent { struct MaxExponent {
static const int value = std::numeric_limits<NumericType>::is_iec559 static const int value = std::is_floating_point<NumericType>::value
? std::numeric_limits<NumericType>::max_exponent ? std::numeric_limits<NumericType>::max_exponent
: (sizeof(NumericType) * 8 + 1 - : std::numeric_limits<NumericType>::digits + 1;
std::numeric_limits<NumericType>::is_signed); };
// The number of bits (including the sign) in an integer. Eliminates sizeof
// hacks.
template <typename NumericType>
struct IntegerBitsPlusSign {
static const int value = std::numeric_limits<NumericType>::digits +
std::is_signed<NumericType>::value;
};
// Helper templates for integer manipulations.
template <typename Integer>
struct PositionOfSignBit {
static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
};
// Determines if a numeric value is negative without throwing compiler
// warnings on: unsigned(value) < 0.
template <typename T,
typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T value) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return value < 0;
}
template <typename T,
typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
constexpr bool IsValueNegative(T) {
static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
return false;
}
// This performs a fast negation, returning a signed value. It works on unsigned
// arguments, but probably doesn't do what you want for any unsigned value
// larger than max / 2 + 1 (i.e. signed min cast to unsigned).
template <typename T>
constexpr typename std::make_signed<T>::type ConditionalNegate(
T x, bool is_negative) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using SignedT = typename std::make_signed<T>::type;
using UnsignedT = typename std::make_unsigned<T>::type;
return static_cast<SignedT>(
(static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
}
// This performs a safe, absolute value via unsigned overflow.
template <typename T>
constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
static_assert(std::is_integral<T>::value, "Type must be integral");
using UnsignedT = typename std::make_unsigned<T>::type;
return IsValueNegative(value)
? static_cast<UnsignedT>(0u - static_cast<UnsignedT>(value))
: static_cast<UnsignedT>(value);
}
// This allows us to switch paths on known compile-time constants.
#if defined(__clang__) || defined(__GNUC__)
constexpr bool CanDetectCompileTimeConstant() { return true; }
template <typename T>
constexpr bool IsCompileTimeConstant(const T v) {
return __builtin_constant_p(v);
}
#else
constexpr bool CanDetectCompileTimeConstant() { return false; }
template <typename T>
constexpr bool IsCompileTimeConstant(const T) {
return false;
}
#endif
template <typename T>
constexpr bool MustTreatAsConstexpr(const T v) {
// Either we can't detect a compile-time constant, and must always use the
// constexpr path, or we know we have a compile-time constant.
return !CanDetectCompileTimeConstant() || IsCompileTimeConstant(v);
}
// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
// Also used in a constexpr template to trigger a compilation failure on
// an error condition.
struct CheckOnFailure {
template <typename T>
static T HandleFailure() {
#if defined(_MSC_VER)
__debugbreak();
#elif defined(__GNUC__) || defined(__clang__)
__builtin_trap();
#else
((void)(*(volatile char*)0 = 0));
#endif
return T();
}
}; };
enum IntegerRepresentation { enum IntegerRepresentation {
@ -35,7 +137,7 @@ enum IntegerRepresentation {
// A range for a given nunmeric Src type is contained for a given numeric Dst // A range for a given nunmeric Src type is contained for a given numeric Dst
// type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and // type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and
// numeric_limits<Src>::min() >= numeric_limits<Dst>::min() are true. // numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true.
// We implement this as template specializations rather than simple static // We implement this as template specializations rather than simple static
// comparisons to ensure type correctness in our comparisons. // comparisons to ensure type correctness in our comparisons.
enum NumericRangeRepresentation { enum NumericRangeRepresentation {
@ -46,16 +148,13 @@ enum NumericRangeRepresentation {
// Helper templates to statically determine if our destination type can contain // Helper templates to statically determine if our destination type can contain
// maximum and minimum values represented by the source type. // maximum and minimum values represented by the source type.
template < template <typename Dst, typename Src,
typename Dst, IntegerRepresentation DstSign = std::is_signed<Dst>::value
typename Src, ? INTEGER_REPRESENTATION_SIGNED
IntegerRepresentation DstSign = std::numeric_limits<Dst>::is_signed : INTEGER_REPRESENTATION_UNSIGNED,
? INTEGER_REPRESENTATION_SIGNED IntegerRepresentation SrcSign = std::is_signed<Src>::value
: INTEGER_REPRESENTATION_UNSIGNED, ? INTEGER_REPRESENTATION_SIGNED
IntegerRepresentation SrcSign = : INTEGER_REPRESENTATION_UNSIGNED>
std::numeric_limits<Src>::is_signed
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED >
struct StaticDstRangeRelationToSrcRange; struct StaticDstRangeRelationToSrcRange;
// Same sign: Dst is guaranteed to contain Src only if its range is equal or // Same sign: Dst is guaranteed to contain Src only if its range is equal or
@ -90,127 +189,622 @@ struct StaticDstRangeRelationToSrcRange<Dst,
static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED; static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
}; };
enum RangeConstraint { // This class wraps the range constraints as separate booleans so the compiler
RANGE_VALID = 0x0, // Value can be represented by the destination type. // can identify constants and eliminate unused code paths.
RANGE_UNDERFLOW = 0x1, // Value would overflow. class RangeCheck {
RANGE_OVERFLOW = 0x2, // Value would underflow. public:
RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN). constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound)
: is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {}
constexpr RangeCheck() : is_underflow_(0), is_overflow_(0) {}
constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
constexpr bool IsOverflowFlagSet() const { return is_overflow_; }
constexpr bool IsUnderflowFlagSet() const { return is_underflow_; }
constexpr bool operator==(const RangeCheck rhs) const {
return is_underflow_ == rhs.is_underflow_ &&
is_overflow_ == rhs.is_overflow_;
}
constexpr bool operator!=(const RangeCheck rhs) const {
return !(*this == rhs);
}
private:
// Do not change the order of these member variables. The integral conversion
// optimization depends on this exact order.
const bool is_underflow_;
const bool is_overflow_;
}; };
// Helper function for coercing an int back to a RangeContraint. // The following helper template addresses a corner case in range checks for
inline RangeConstraint GetRangeConstraint(int integer_range_constraint) { // conversion from a floating-point type to an integral type of smaller range
DCHECK(integer_range_constraint >= RANGE_VALID && // but larger precision (e.g. float -> unsigned). The problem is as follows:
integer_range_constraint <= RANGE_INVALID); // 1. Integral maximum is always one less than a power of two, so it must be
return static_cast<RangeConstraint>(integer_range_constraint); // truncated to fit the mantissa of the floating point. The direction of
} // rounding is implementation defined, but by default it's always IEEE
// floats, which round to nearest and thus result in a value of larger
// magnitude than the integral value.
// Example: float f = UINT_MAX; // f is 4294967296f but UINT_MAX
// // is 4294967295u.
// 2. If the floating point value is equal to the promoted integral maximum
// value, a range check will erroneously pass.
// Example: (4294967296f <= 4294967295u) // This is true due to a precision
// // loss in rounding up to float.
// 3. When the floating point value is then converted to an integral, the
// resulting value is out of range for the target integral type and
// thus is implementation defined.
// Example: unsigned u = (float)INT_MAX; // u will typically overflow to 0.
// To fix this bug we manually truncate the maximum value when the destination
// type is an integral of larger precision than the source floating-point type,
// such that the resulting maximum is represented exactly as a floating point.
template <typename Dst, typename Src, template <typename> class Bounds>
struct NarrowingRange {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = typename std::numeric_limits<Dst>;
// This function creates a RangeConstraint from an upper and lower bound // Computes the mask required to make an accurate comparison between types.
// check by taking advantage of the fact that only NaN can be out of range in static const int kShift =
// both directions at once. (MaxExponent<Src>::value > MaxExponent<Dst>::value &&
inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound, SrcLimits::digits < DstLimits::digits)
bool is_in_lower_bound) { ? (DstLimits::digits - SrcLimits::digits)
return GetRangeConstraint((is_in_upper_bound ? 0 : RANGE_OVERFLOW) | : 0;
(is_in_lower_bound ? 0 : RANGE_UNDERFLOW)); template <typename T, typename std::enable_if<
} std::is_integral<T>::value>::type* = nullptr>
template < // Masks out the integer bits that are beyond the precision of the
typename Dst, // intermediate type used for comparison.
typename Src, static constexpr T Adjust(T value) {
IntegerRepresentation DstSign = std::numeric_limits<Dst>::is_signed static_assert(std::is_same<T, Dst>::value, "");
? INTEGER_REPRESENTATION_SIGNED static_assert(kShift < DstLimits::digits, "");
: INTEGER_REPRESENTATION_UNSIGNED, return static_cast<T>(
IntegerRepresentation SrcSign = std::numeric_limits<Src>::is_signed ConditionalNegate(SafeUnsignedAbs(value) & ~((T(1) << kShift) - T(1)),
? INTEGER_REPRESENTATION_SIGNED IsValueNegative(value)));
: INTEGER_REPRESENTATION_UNSIGNED, }
NumericRangeRepresentation DstRange =
StaticDstRangeRelationToSrcRange<Dst, Src>::value > template <typename T, typename std::enable_if<
std::is_floating_point<T>::value>::type* = nullptr>
static constexpr T Adjust(T value) {
static_assert(std::is_same<T, Dst>::value, "");
static_assert(kShift == 0, "");
return value;
}
static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
};
template <typename Dst, typename Src, template <typename> class Bounds,
IntegerRepresentation DstSign = std::is_signed<Dst>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
IntegerRepresentation SrcSign = std::is_signed<Src>::value
? INTEGER_REPRESENTATION_SIGNED
: INTEGER_REPRESENTATION_UNSIGNED,
NumericRangeRepresentation DstRange =
StaticDstRangeRelationToSrcRange<Dst, Src>::value>
struct DstRangeRelationToSrcRangeImpl; struct DstRangeRelationToSrcRangeImpl;
// The following templates are for ranges that must be verified at runtime. We // The following templates are for ranges that must be verified at runtime. We
// split it into checks based on signedness to avoid confusing casts and // split it into checks based on signedness to avoid confusing casts and
// compiler warnings on signed an unsigned comparisons. // compiler warnings on signed an unsigned comparisons.
// Dst range is statically determined to contain Src: Nothing to check. // Same sign narrowing: The range is contained for normal limits.
template <typename Dst, template <typename Dst, typename Src, template <typename> class Bounds,
typename Src, IntegerRepresentation DstSign, IntegerRepresentation SrcSign>
IntegerRepresentation DstSign, struct DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds, DstSign, SrcSign,
IntegerRepresentation SrcSign>
struct DstRangeRelationToSrcRangeImpl<Dst,
Src,
DstSign,
SrcSign,
NUMERIC_RANGE_CONTAINED> { NUMERIC_RANGE_CONTAINED> {
static RangeConstraint Check(Src value) { return RANGE_VALID; } static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(
static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() ||
static_cast<Dst>(value) >= DstLimits::lowest(),
static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() ||
static_cast<Dst>(value) <= DstLimits::max());
}
}; };
// Signed to signed narrowing: Both the upper and lower boundaries may be // Signed to signed narrowing: Both the upper and lower boundaries may be
// exceeded. // exceeded for standard limits.
template <typename Dst, typename Src> template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<
Src, Dst, Src, Bounds, INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_SIGNED, INTEGER_REPRESENTATION_SIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
INTEGER_REPRESENTATION_SIGNED, static constexpr RangeCheck Check(Src value) {
NUMERIC_RANGE_NOT_CONTAINED> { using DstLimits = NarrowingRange<Dst, Src, Bounds>;
static RangeConstraint Check(Src value) { return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
return std::numeric_limits<Dst>::is_iec559
? GetRangeConstraint(value <= std::numeric_limits<Dst>::max(),
value >= -std::numeric_limits<Dst>::max())
: GetRangeConstraint(value <= std::numeric_limits<Dst>::max(),
value >= std::numeric_limits<Dst>::min());
} }
}; };
// Unsigned to unsigned narrowing: Only the upper boundary can be exceeded. // Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
template <typename Dst, typename Src> // standard limits.
struct DstRangeRelationToSrcRangeImpl<Dst, template <typename Dst, typename Src, template <typename> class Bounds>
Src, struct DstRangeRelationToSrcRangeImpl<
INTEGER_REPRESENTATION_UNSIGNED, Dst, Src, Bounds, INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
NUMERIC_RANGE_NOT_CONTAINED> { static constexpr RangeCheck Check(Src value) {
static RangeConstraint Check(Src value) { using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return GetRangeConstraint(value <= std::numeric_limits<Dst>::max(), true); return RangeCheck(
DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
value <= DstLimits::max());
} }
}; };
// Unsigned to signed: The upper boundary may be exceeded. // Unsigned to signed: Only the upper bound can be exceeded for standard limits.
template <typename Dst, typename Src> template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<
Src, Dst, Src, Bounds, INTEGER_REPRESENTATION_SIGNED,
INTEGER_REPRESENTATION_SIGNED, INTEGER_REPRESENTATION_UNSIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
INTEGER_REPRESENTATION_UNSIGNED, static constexpr RangeCheck Check(Src value) {
NUMERIC_RANGE_NOT_CONTAINED> { using DstLimits = NarrowingRange<Dst, Src, Bounds>;
static RangeConstraint Check(Src value) { using Promotion = decltype(Src() + Dst());
return sizeof(Dst) > sizeof(Src) return RangeCheck(DstLimits::lowest() <= Dst(0) ||
? RANGE_VALID static_cast<Promotion>(value) >=
: GetRangeConstraint( static_cast<Promotion>(DstLimits::lowest()),
value <= static_cast<Src>(std::numeric_limits<Dst>::max()), static_cast<Promotion>(value) <=
true); static_cast<Promotion>(DstLimits::max()));
} }
}; };
// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst, // Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
// and any negative value exceeds the lower boundary. // and any negative value exceeds the lower boundary for standard limits.
template <typename Dst, typename Src> template <typename Dst, typename Src, template <typename> class Bounds>
struct DstRangeRelationToSrcRangeImpl<Dst, struct DstRangeRelationToSrcRangeImpl<
Src, Dst, Src, Bounds, INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_SIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
INTEGER_REPRESENTATION_SIGNED, static constexpr RangeCheck Check(Src value) {
NUMERIC_RANGE_NOT_CONTAINED> { using SrcLimits = std::numeric_limits<Src>;
static RangeConstraint Check(Src value) { using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return (MaxExponent<Dst>::value >= MaxExponent<Src>::value) using Promotion = decltype(Src() + Dst());
? GetRangeConstraint(true, value >= static_cast<Src>(0)) return RangeCheck(
: GetRangeConstraint( value >= Src(0) && (DstLimits::lowest() == 0 ||
value <= static_cast<Src>(std::numeric_limits<Dst>::max()), static_cast<Dst>(value) >= DstLimits::lowest()),
value >= static_cast<Src>(0)); static_cast<Promotion>(SrcLimits::max()) <=
static_cast<Promotion>(DstLimits::max()) ||
static_cast<Promotion>(value) <=
static_cast<Promotion>(DstLimits::max()));
} }
}; };
// Simple wrapper for statically checking if a type's range is contained.
template <typename Dst, typename Src> template <typename Dst, typename Src>
inline RangeConstraint DstRangeRelationToSrcRange(Src value) { struct IsTypeInRangeForNumericType {
// Both source and destination must be numeric. static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
STATIC_ASSERT(std::numeric_limits<Src>::is_specialized); NUMERIC_RANGE_CONTAINED;
STATIC_ASSERT(std::numeric_limits<Dst>::is_specialized); };
return DstRangeRelationToSrcRangeImpl<Dst, Src>::Check(value);
template <typename Dst, template <typename> class Bounds = std::numeric_limits,
typename Src>
constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
}
// Integer promotion templates used by the portable checked integer arithmetic.
template <size_t Size, bool IsSigned>
struct IntegerForDigitsAndSign;
#define INTEGER_FOR_DIGITS_AND_SIGN(I) \
template <> \
struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
std::is_signed<I>::value> { \
using type = I; \
}
INTEGER_FOR_DIGITS_AND_SIGN(int8_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint8_t);
INTEGER_FOR_DIGITS_AND_SIGN(int16_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint16_t);
INTEGER_FOR_DIGITS_AND_SIGN(int32_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint32_t);
INTEGER_FOR_DIGITS_AND_SIGN(int64_t);
INTEGER_FOR_DIGITS_AND_SIGN(uint64_t);
#undef INTEGER_FOR_DIGITS_AND_SIGN
// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to
// support 128-bit math, then the ArithmeticPromotion template below will need
// to be updated (or more likely replaced with a decltype expression).
static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
"Max integer size not supported for this toolchain.");
template <typename Integer, bool IsSigned = std::is_signed<Integer>::value>
struct TwiceWiderInteger {
using type =
typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
IsSigned>::type;
};
enum ArithmeticPromotionCategory {
LEFT_PROMOTION, // Use the type of the left-hand argument.
RIGHT_PROMOTION // Use the type of the right-hand argument.
};
// Determines the type that can represent the largest positive value.
template <typename Lhs, typename Rhs,
ArithmeticPromotionCategory Promotion =
(MaxExponent<Lhs>::value > MaxExponent<Rhs>::value)
? LEFT_PROMOTION
: RIGHT_PROMOTION>
struct MaxExponentPromotion;
template <typename Lhs, typename Rhs>
struct MaxExponentPromotion<Lhs, Rhs, LEFT_PROMOTION> {
using type = Lhs;
};
template <typename Lhs, typename Rhs>
struct MaxExponentPromotion<Lhs, Rhs, RIGHT_PROMOTION> {
using type = Rhs;
};
// Determines the type that can represent the lowest arithmetic value.
template <typename Lhs, typename Rhs,
ArithmeticPromotionCategory Promotion =
std::is_signed<Lhs>::value
? (std::is_signed<Rhs>::value
? (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value
? LEFT_PROMOTION
: RIGHT_PROMOTION)
: LEFT_PROMOTION)
: (std::is_signed<Rhs>::value
? RIGHT_PROMOTION
: (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
? LEFT_PROMOTION
: RIGHT_PROMOTION))>
struct LowestValuePromotion;
template <typename Lhs, typename Rhs>
struct LowestValuePromotion<Lhs, Rhs, LEFT_PROMOTION> {
using type = Lhs;
};
template <typename Lhs, typename Rhs>
struct LowestValuePromotion<Lhs, Rhs, RIGHT_PROMOTION> {
using type = Rhs;
};
// Determines the type that is best able to represent an arithmetic result.
template <
typename Lhs, typename Rhs = Lhs,
bool is_intmax_type =
std::is_integral<typename MaxExponentPromotion<Lhs, Rhs>::type>::value&&
IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::type>::
value == IntegerBitsPlusSign<intmax_t>::value,
bool is_max_exponent =
StaticDstRangeRelationToSrcRange<
typename MaxExponentPromotion<Lhs, Rhs>::type, Lhs>::value ==
NUMERIC_RANGE_CONTAINED&& StaticDstRangeRelationToSrcRange<
typename MaxExponentPromotion<Lhs, Rhs>::type, Rhs>::value ==
NUMERIC_RANGE_CONTAINED>
struct BigEnoughPromotion;
// The side with the max exponent is big enough.
template <typename Lhs, typename Rhs, bool is_intmax_type>
struct BigEnoughPromotion<Lhs, Rhs, is_intmax_type, true> {
using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
static const bool is_contained = true;
};
// We can use a twice wider type to fit.
template <typename Lhs, typename Rhs>
struct BigEnoughPromotion<Lhs, Rhs, false, false> {
using type =
typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value>::type;
static const bool is_contained = true;
};
// No type is large enough.
template <typename Lhs, typename Rhs>
struct BigEnoughPromotion<Lhs, Rhs, true, false> {
using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
static const bool is_contained = false;
};
// We can statically check if operations on the provided types can wrap, so we
// can skip the checked operations if they're not needed. So, for an integer we
// care if the destination type preserves the sign and is twice the width of
// the source.
template <typename T, typename Lhs, typename Rhs = Lhs>
struct IsIntegerArithmeticSafe {
static const bool value =
!std::is_floating_point<T>::value &&
!std::is_floating_point<Lhs>::value &&
!std::is_floating_point<Rhs>::value &&
std::is_signed<T>::value >= std::is_signed<Lhs>::value &&
IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Lhs>::value) &&
std::is_signed<T>::value >= std::is_signed<Rhs>::value &&
IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
};
// Promotes to a type that can represent any possible result of a binary
// arithmetic operation with the source types.
template <typename Lhs, typename Rhs,
bool is_promotion_possible = IsIntegerArithmeticSafe<
typename std::conditional<std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value,
intmax_t, uintmax_t>::type,
typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
struct FastIntegerArithmeticPromotion;
template <typename Lhs, typename Rhs>
struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
using type =
typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
std::is_signed<Lhs>::value ||
std::is_signed<Rhs>::value>::type;
static_assert(IsIntegerArithmeticSafe<type, Lhs, Rhs>::value, "");
static const bool is_contained = true;
};
template <typename Lhs, typename Rhs>
struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
using type = typename BigEnoughPromotion<Lhs, Rhs>::type;
static const bool is_contained = false;
};
// Extracts the underlying type from an enum.
template <typename T, bool is_enum = std::is_enum<T>::value>
struct ArithmeticOrUnderlyingEnum;
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, true> {
using type = typename std::underlying_type<T>::type;
static const bool value = std::is_arithmetic<type>::value;
};
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, false> {
using type = T;
static const bool value = std::is_arithmetic<type>::value;
};
// The following are helper templates used in the CheckedNumeric class.
template <typename T>
class CheckedNumeric;
template <typename T>
class ClampedNumeric;
template <typename T>
class StrictNumeric;
// Used to treat CheckedNumeric and arithmetic underlying types the same.
template <typename T>
struct UnderlyingType {
using type = typename ArithmeticOrUnderlyingEnum<T>::type;
static const bool is_numeric = std::is_arithmetic<type>::value;
static const bool is_checked = false;
static const bool is_clamped = false;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<CheckedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = true;
static const bool is_clamped = false;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<ClampedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
static const bool is_clamped = true;
static const bool is_strict = false;
};
template <typename T>
struct UnderlyingType<StrictNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
static const bool is_clamped = false;
static const bool is_strict = true;
};
template <typename L, typename R>
struct IsCheckedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
struct IsClampedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
!(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
struct IsStrictOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
(UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
!(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
!(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
};
// as_signed<> returns the supplied integral value (or integral castable
// Numeric template) cast as a signed integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
template <typename Src>
constexpr typename std::make_signed<
typename base::internal::UnderlyingType<Src>::type>::type
as_signed(const Src value) {
static_assert(std::is_integral<decltype(as_signed(value))>::value,
"Argument must be a signed or unsigned integer type.");
return static_cast<decltype(as_signed(value))>(value);
}
// as_unsigned<> returns the supplied integral value (or integral castable
// Numeric template) cast as an unsigned integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
template <typename Src>
constexpr typename std::make_unsigned<
typename base::internal::UnderlyingType<Src>::type>::type
as_unsigned(const Src value) {
static_assert(std::is_integral<decltype(as_unsigned(value))>::value,
"Argument must be a signed or unsigned integer type.");
return static_cast<decltype(as_unsigned(value))>(value);
}
template <typename L, typename R>
constexpr bool IsLessImpl(const L lhs, const R rhs, const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsUnderflow() || r_range.IsOverflow() ||
(l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsLess {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsLessImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsLessOrEqualImpl(const L lhs, const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsUnderflow() || r_range.IsOverflow() ||
(l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <=
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsLessOrEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsLessOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsGreaterImpl(const L lhs, const R rhs, const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsOverflow() || r_range.IsUnderflow() ||
(l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsGreater {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsGreaterImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
constexpr bool IsGreaterOrEqualImpl(const L lhs, const R rhs,
const RangeCheck l_range,
const RangeCheck r_range) {
return l_range.IsOverflow() || r_range.IsUnderflow() ||
(l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >=
static_cast<decltype(lhs + rhs)>(rhs));
}
template <typename L, typename R>
struct IsGreaterOrEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return IsGreaterOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
DstRangeRelationToSrcRange<L>(rhs));
}
};
template <typename L, typename R>
struct IsEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) ==
DstRangeRelationToSrcRange<L>(rhs) &&
static_cast<decltype(lhs + rhs)>(lhs) ==
static_cast<decltype(lhs + rhs)>(rhs);
}
};
template <typename L, typename R>
struct IsNotEqual {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
static constexpr bool Test(const L lhs, const R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) !=
DstRangeRelationToSrcRange<L>(rhs) ||
static_cast<decltype(lhs + rhs)>(lhs) !=
static_cast<decltype(lhs + rhs)>(rhs);
}
};
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
template <template <typename, typename> class C, typename L, typename R>
constexpr bool SafeCompare(const L lhs, const R rhs) {
static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
"Types must be numeric.");
using Promotion = BigEnoughPromotion<L, R>;
using BigType = typename Promotion::type;
return Promotion::is_contained
// Force to a larger type for speed if both are contained.
? C<BigType, BigType>::Test(
static_cast<BigType>(static_cast<L>(lhs)),
static_cast<BigType>(static_cast<R>(rhs)))
// Let the template functions figure it out for mixed types.
: C<L, R>::Test(lhs, rhs);
}
template <typename Dst, typename Src>
constexpr bool IsMaxInRangeForNumericType() {
return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
std::numeric_limits<Src>::max());
}
template <typename Dst, typename Src>
constexpr bool IsMinInRangeForNumericType() {
return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
std::numeric_limits<Src>::lowest());
}
template <typename Dst, typename Src>
constexpr Dst CommonMax() {
return !IsMaxInRangeForNumericType<Dst, Src>()
? Dst(std::numeric_limits<Dst>::max())
: Dst(std::numeric_limits<Src>::max());
}
template <typename Dst, typename Src>
constexpr Dst CommonMin() {
return !IsMinInRangeForNumericType<Dst, Src>()
? Dst(std::numeric_limits<Dst>::lowest())
: Dst(std::numeric_limits<Src>::lowest());
}
// This is a wrapper to generate return the max or min for a supplied type.
// If the argument is false, the returned value is the maximum. If true the
// returned value is the minimum.
template <typename Dst, typename Src = Dst>
constexpr Dst CommonMaxOrMin(bool is_min) {
return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
} }
} // namespace internal } // namespace internal