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:
parent
1dac9f3b10
commit
c10c83c31b
1
BUILD.gn
1
BUILD.gn
@ -4065,6 +4065,7 @@ v8_component("v8_libbase") {
|
||||
"src/base/region-allocator.h",
|
||||
"src/base/ring-buffer.h",
|
||||
"src/base/safe_conversions.h",
|
||||
"src/base/safe_conversions_arm_impl.h",
|
||||
"src/base/safe_conversions_impl.h",
|
||||
"src/base/small-vector.h",
|
||||
"src/base/sys-info.cc",
|
||||
|
@ -4,59 +4,383 @@
|
||||
|
||||
// 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
|
||||
// - formatting (git cl format)
|
||||
// - include paths
|
||||
|
||||
#ifndef V8_BASE_SAFE_CONVERSIONS_H_
|
||||
#define V8_BASE_SAFE_CONVERSIONS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#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 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
|
||||
// for the destination type.
|
||||
template <typename Dst, typename Src>
|
||||
inline bool IsValueInRangeForNumericType(Src value) {
|
||||
return internal::DstRangeRelationToSrcRange<Dst>(value) ==
|
||||
internal::RANGE_VALID;
|
||||
constexpr bool IsValueInRangeForNumericType(Src value) {
|
||||
using SrcType = typename internal::UnderlyingType<Src>::type;
|
||||
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,
|
||||
// except that it CHECKs that the specified numeric conversion will not
|
||||
// overflow or underflow. NaN source will always trigger a CHECK.
|
||||
template <typename Dst, typename Src>
|
||||
inline Dst checked_cast(Src value) {
|
||||
CHECK(IsValueInRangeForNumericType<Dst>(value));
|
||||
return static_cast<Dst>(value);
|
||||
template <typename Dst, class CheckHandler = internal::CheckOnFailure,
|
||||
typename Src>
|
||||
constexpr Dst checked_cast(Src 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
|
||||
// that the specified numeric conversion will saturate rather than overflow or
|
||||
// underflow. NaN assignment to an integral will trigger a CHECK condition.
|
||||
// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
|
||||
// You may provide your own limits (e.g. to saturated_cast) so long as you
|
||||
// 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>
|
||||
inline Dst saturated_cast(Src value) {
|
||||
// Optimization for floating point values, which already saturate.
|
||||
if (std::numeric_limits<Dst>::is_iec559)
|
||||
return static_cast<Dst>(value);
|
||||
struct SaturateFastOp<
|
||||
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) {
|
||||
return SaturateFastAsmOp<Dst, Src>::Do(value);
|
||||
}
|
||||
};
|
||||
|
||||
switch (internal::DstRangeRelationToSrcRange<Dst>(value)) {
|
||||
case internal::RANGE_VALID:
|
||||
return static_cast<Dst>(value);
|
||||
template <typename Dst, typename Src>
|
||||
struct SaturateFastOp<
|
||||
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:
|
||||
return std::numeric_limits<Dst>::min();
|
||||
// saturated_cast<> is analogous to static_cast<> for numeric types, except
|
||||
// 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:
|
||||
return std::numeric_limits<Dst>::max();
|
||||
// strict_cast<> is analogous to static_cast<> for numeric types, except that
|
||||
// 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.
|
||||
case internal::RANGE_INVALID:
|
||||
UNREACHABLE();
|
||||
// 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.
|
||||
// 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
|
||||
|
60
src/base/safe_conversions_arm_impl.h
Normal file
60
src/base/safe_conversions_arm_impl.h
Normal 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_
|
@ -4,28 +4,130 @@
|
||||
|
||||
// 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
|
||||
// - formatting (git cl format)
|
||||
|
||||
#ifndef V8_BASE_SAFE_CONVERSIONS_IMPL_H_
|
||||
#define V8_BASE_SAFE_CONVERSIONS_IMPL_H_
|
||||
|
||||
#include <limits>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "src/base/logging.h"
|
||||
#include "src/base/macros.h"
|
||||
#include <limits>
|
||||
#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 base {
|
||||
namespace internal {
|
||||
|
||||
// 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
|
||||
// for accurate range comparisons between floating point and integer types.
|
||||
// we can compute an analog using std::numeric_limits<>::digits.
|
||||
template <typename NumericType>
|
||||
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
|
||||
: (sizeof(NumericType) * 8 + 1 -
|
||||
std::numeric_limits<NumericType>::is_signed);
|
||||
: std::numeric_limits<NumericType>::digits + 1;
|
||||
};
|
||||
|
||||
// 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 {
|
||||
@ -35,7 +137,7 @@ enum IntegerRepresentation {
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// comparisons to ensure type correctness in our comparisons.
|
||||
enum NumericRangeRepresentation {
|
||||
@ -46,16 +148,13 @@ enum NumericRangeRepresentation {
|
||||
// Helper templates to statically determine if our destination type can contain
|
||||
// maximum and minimum values represented by the source type.
|
||||
|
||||
template <
|
||||
typename Dst,
|
||||
typename Src,
|
||||
IntegerRepresentation DstSign = std::numeric_limits<Dst>::is_signed
|
||||
? INTEGER_REPRESENTATION_SIGNED
|
||||
: INTEGER_REPRESENTATION_UNSIGNED,
|
||||
IntegerRepresentation SrcSign =
|
||||
std::numeric_limits<Src>::is_signed
|
||||
? INTEGER_REPRESENTATION_SIGNED
|
||||
: INTEGER_REPRESENTATION_UNSIGNED >
|
||||
template <typename Dst, typename Src,
|
||||
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>
|
||||
struct StaticDstRangeRelationToSrcRange;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
enum RangeConstraint {
|
||||
RANGE_VALID = 0x0, // Value can be represented by the destination type.
|
||||
RANGE_UNDERFLOW = 0x1, // Value would overflow.
|
||||
RANGE_OVERFLOW = 0x2, // Value would underflow.
|
||||
RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN).
|
||||
// This class wraps the range constraints as separate booleans so the compiler
|
||||
// can identify constants and eliminate unused code paths.
|
||||
class RangeCheck {
|
||||
public:
|
||||
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.
|
||||
inline RangeConstraint GetRangeConstraint(int integer_range_constraint) {
|
||||
DCHECK(integer_range_constraint >= RANGE_VALID &&
|
||||
integer_range_constraint <= RANGE_INVALID);
|
||||
return static_cast<RangeConstraint>(integer_range_constraint);
|
||||
}
|
||||
// The following helper template addresses a corner case in range checks for
|
||||
// conversion from a floating-point type to an integral type of smaller range
|
||||
// but larger precision (e.g. float -> unsigned). The problem is as follows:
|
||||
// 1. Integral maximum is always one less than a power of two, so it must be
|
||||
// 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
|
||||
// check by taking advantage of the fact that only NaN can be out of range in
|
||||
// both directions at once.
|
||||
inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound,
|
||||
bool is_in_lower_bound) {
|
||||
return GetRangeConstraint((is_in_upper_bound ? 0 : RANGE_OVERFLOW) |
|
||||
(is_in_lower_bound ? 0 : RANGE_UNDERFLOW));
|
||||
}
|
||||
// Computes the mask required to make an accurate comparison between types.
|
||||
static const int kShift =
|
||||
(MaxExponent<Src>::value > MaxExponent<Dst>::value &&
|
||||
SrcLimits::digits < DstLimits::digits)
|
||||
? (DstLimits::digits - SrcLimits::digits)
|
||||
: 0;
|
||||
template <typename T, typename std::enable_if<
|
||||
std::is_integral<T>::value>::type* = nullptr>
|
||||
|
||||
template <
|
||||
typename Dst,
|
||||
typename Src,
|
||||
IntegerRepresentation DstSign = std::numeric_limits<Dst>::is_signed
|
||||
? INTEGER_REPRESENTATION_SIGNED
|
||||
: INTEGER_REPRESENTATION_UNSIGNED,
|
||||
IntegerRepresentation SrcSign = std::numeric_limits<Src>::is_signed
|
||||
? INTEGER_REPRESENTATION_SIGNED
|
||||
: INTEGER_REPRESENTATION_UNSIGNED,
|
||||
NumericRangeRepresentation DstRange =
|
||||
StaticDstRangeRelationToSrcRange<Dst, Src>::value >
|
||||
// Masks out the integer bits that are beyond the precision of the
|
||||
// intermediate type used for comparison.
|
||||
static constexpr T Adjust(T value) {
|
||||
static_assert(std::is_same<T, Dst>::value, "");
|
||||
static_assert(kShift < DstLimits::digits, "");
|
||||
return static_cast<T>(
|
||||
ConditionalNegate(SafeUnsignedAbs(value) & ~((T(1) << kShift) - T(1)),
|
||||
IsValueNegative(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;
|
||||
|
||||
// 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
|
||||
// compiler warnings on signed an unsigned comparisons.
|
||||
|
||||
// Dst range is statically determined to contain Src: Nothing to check.
|
||||
template <typename Dst,
|
||||
typename Src,
|
||||
IntegerRepresentation DstSign,
|
||||
IntegerRepresentation SrcSign>
|
||||
struct DstRangeRelationToSrcRangeImpl<Dst,
|
||||
Src,
|
||||
DstSign,
|
||||
SrcSign,
|
||||
// Same sign narrowing: The range is contained for normal limits.
|
||||
template <typename Dst, typename Src, template <typename> class Bounds,
|
||||
IntegerRepresentation DstSign, IntegerRepresentation SrcSign>
|
||||
struct DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds, DstSign, SrcSign,
|
||||
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
|
||||
// exceeded.
|
||||
template <typename Dst, typename Src>
|
||||
struct DstRangeRelationToSrcRangeImpl<Dst,
|
||||
Src,
|
||||
INTEGER_REPRESENTATION_SIGNED,
|
||||
INTEGER_REPRESENTATION_SIGNED,
|
||||
NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static RangeConstraint Check(Src value) {
|
||||
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());
|
||||
// exceeded for standard limits.
|
||||
template <typename Dst, typename Src, template <typename> class Bounds>
|
||||
struct DstRangeRelationToSrcRangeImpl<
|
||||
Dst, Src, Bounds, INTEGER_REPRESENTATION_SIGNED,
|
||||
INTEGER_REPRESENTATION_SIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static constexpr RangeCheck Check(Src value) {
|
||||
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
|
||||
return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
|
||||
}
|
||||
};
|
||||
|
||||
// Unsigned to unsigned narrowing: Only the upper boundary can be exceeded.
|
||||
template <typename Dst, typename Src>
|
||||
struct DstRangeRelationToSrcRangeImpl<Dst,
|
||||
Src,
|
||||
INTEGER_REPRESENTATION_UNSIGNED,
|
||||
INTEGER_REPRESENTATION_UNSIGNED,
|
||||
NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static RangeConstraint Check(Src value) {
|
||||
return GetRangeConstraint(value <= std::numeric_limits<Dst>::max(), true);
|
||||
// Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
|
||||
// standard limits.
|
||||
template <typename Dst, typename Src, template <typename> class Bounds>
|
||||
struct DstRangeRelationToSrcRangeImpl<
|
||||
Dst, Src, Bounds, INTEGER_REPRESENTATION_UNSIGNED,
|
||||
INTEGER_REPRESENTATION_UNSIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static constexpr RangeCheck Check(Src value) {
|
||||
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
|
||||
return RangeCheck(
|
||||
DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
|
||||
value <= DstLimits::max());
|
||||
}
|
||||
};
|
||||
|
||||
// Unsigned to signed: The upper boundary may be exceeded.
|
||||
template <typename Dst, typename Src>
|
||||
struct DstRangeRelationToSrcRangeImpl<Dst,
|
||||
Src,
|
||||
INTEGER_REPRESENTATION_SIGNED,
|
||||
INTEGER_REPRESENTATION_UNSIGNED,
|
||||
NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static RangeConstraint Check(Src value) {
|
||||
return sizeof(Dst) > sizeof(Src)
|
||||
? RANGE_VALID
|
||||
: GetRangeConstraint(
|
||||
value <= static_cast<Src>(std::numeric_limits<Dst>::max()),
|
||||
true);
|
||||
// Unsigned to signed: Only the upper bound can be exceeded for standard limits.
|
||||
template <typename Dst, typename Src, template <typename> class Bounds>
|
||||
struct DstRangeRelationToSrcRangeImpl<
|
||||
Dst, Src, Bounds, INTEGER_REPRESENTATION_SIGNED,
|
||||
INTEGER_REPRESENTATION_UNSIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static constexpr RangeCheck Check(Src value) {
|
||||
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
|
||||
using Promotion = decltype(Src() + Dst());
|
||||
return RangeCheck(DstLimits::lowest() <= Dst(0) ||
|
||||
static_cast<Promotion>(value) >=
|
||||
static_cast<Promotion>(DstLimits::lowest()),
|
||||
static_cast<Promotion>(value) <=
|
||||
static_cast<Promotion>(DstLimits::max()));
|
||||
}
|
||||
};
|
||||
|
||||
// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
|
||||
// and any negative value exceeds the lower boundary.
|
||||
template <typename Dst, typename Src>
|
||||
struct DstRangeRelationToSrcRangeImpl<Dst,
|
||||
Src,
|
||||
INTEGER_REPRESENTATION_UNSIGNED,
|
||||
INTEGER_REPRESENTATION_SIGNED,
|
||||
NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static RangeConstraint Check(Src value) {
|
||||
return (MaxExponent<Dst>::value >= MaxExponent<Src>::value)
|
||||
? GetRangeConstraint(true, value >= static_cast<Src>(0))
|
||||
: GetRangeConstraint(
|
||||
value <= static_cast<Src>(std::numeric_limits<Dst>::max()),
|
||||
value >= static_cast<Src>(0));
|
||||
// and any negative value exceeds the lower boundary for standard limits.
|
||||
template <typename Dst, typename Src, template <typename> class Bounds>
|
||||
struct DstRangeRelationToSrcRangeImpl<
|
||||
Dst, Src, Bounds, INTEGER_REPRESENTATION_UNSIGNED,
|
||||
INTEGER_REPRESENTATION_SIGNED, NUMERIC_RANGE_NOT_CONTAINED> {
|
||||
static constexpr RangeCheck Check(Src value) {
|
||||
using SrcLimits = std::numeric_limits<Src>;
|
||||
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
|
||||
using Promotion = decltype(Src() + Dst());
|
||||
return RangeCheck(
|
||||
value >= Src(0) && (DstLimits::lowest() == 0 ||
|
||||
static_cast<Dst>(value) >= DstLimits::lowest()),
|
||||
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>
|
||||
inline RangeConstraint DstRangeRelationToSrcRange(Src value) {
|
||||
// Both source and destination must be numeric.
|
||||
STATIC_ASSERT(std::numeric_limits<Src>::is_specialized);
|
||||
STATIC_ASSERT(std::numeric_limits<Dst>::is_specialized);
|
||||
return DstRangeRelationToSrcRangeImpl<Dst, Src>::Check(value);
|
||||
struct IsTypeInRangeForNumericType {
|
||||
static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
|
||||
NUMERIC_RANGE_CONTAINED;
|
||||
};
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user