Commit f3a69e04 authored by Justin Schuh's avatar Justin Schuh Committed by Commit Bot

Add optimized arm implementations to base/numerics

BUG=672489

Change-Id: I7bb13d0ae3850f3334978bd8d483550ea2f3e3fd
Reviewed-on: https://chromium-review.googlesource.com/567436
Commit-Queue: Emil A Eklund <eae@chromium.org>
Reviewed-by: default avatarEmil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486438}
parent 2806adc6
...@@ -587,8 +587,10 @@ component("base") { ...@@ -587,8 +587,10 @@ component("base") {
"numerics/clamped_math.h", "numerics/clamped_math.h",
"numerics/clamped_math_impl.h", "numerics/clamped_math_impl.h",
"numerics/safe_conversions.h", "numerics/safe_conversions.h",
"numerics/safe_conversions_arm_impl.h",
"numerics/safe_conversions_impl.h", "numerics/safe_conversions_impl.h",
"numerics/safe_math.h", "numerics/safe_math.h",
"numerics/safe_math_arm_impl.h",
"numerics/safe_math_clang_gcc_impl.h", "numerics/safe_math_clang_gcc_impl.h",
"numerics/safe_math_shared_impl.h", "numerics/safe_math_shared_impl.h",
"numerics/saturated_arithmetic.h", "numerics/saturated_arithmetic.h",
......
...@@ -21,28 +21,6 @@ ...@@ -21,28 +21,6 @@
namespace base { namespace base {
namespace internal { namespace internal {
// This provides a small optimization that generates more compact code when one
// of the components in an operation is a compile-time constant.
template <typename T>
constexpr bool IsCompileTimeConstant(const T v) {
#if defined(__clang__) || defined(__GNUC__)
return __builtin_constant_p(v);
#else
return false;
#endif
}
// 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 T>
constexpr T GetMaxOrMin(bool is_min) {
// For both signed and unsigned math the bit pattern for minimum is really
// just one plus the maximum. However, we have to cast to unsigned to ensure
// we get well-defined overflow semantics.
return as_unsigned(std::numeric_limits<T>::max()) + is_min;
}
template <typename T, typename U, class Enable = void> template <typename T, typename U, class Enable = void>
struct ClampedAddOp {}; struct ClampedAddOp {};
...@@ -102,6 +80,10 @@ struct ClampedMulOp<T, ...@@ -102,6 +80,10 @@ struct ClampedMulOp<T,
using result_type = typename MaxExponentPromotion<T, U>::type; using result_type = typename MaxExponentPromotion<T, U>::type;
template <typename V = result_type> template <typename V = result_type>
static V Do(T x, U y) { static V Do(T x, U y) {
// TODO(jschuh) Make this "constexpr if" once we're C++17.
if (ClampedMulFastOp<T, U>::is_supported)
return ClampedMulFastOp<T, U>::template Do<V>(x, y);
V result; V result;
return CheckedMulOp<T, U>::Do(x, y, &result) return CheckedMulOp<T, U>::Do(x, y, &result)
? result ? result
......
...@@ -13,41 +13,51 @@ ...@@ -13,41 +13,51 @@
#include "base/numerics/safe_conversions_impl.h" #include "base/numerics/safe_conversions_impl.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
#include "base/numerics/safe_conversions_arm_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
#endif
namespace base { namespace base {
namespace internal {
// Convenience function that returns true if the supplied value is in range #if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
// for the destination type.
template <typename Dst, typename Src> template <typename Dst, typename Src>
constexpr bool IsValueInRangeForNumericType(Src value) { struct SaturateFastAsmOp {
return internal::DstRangeRelationToSrcRange<Dst>(value).IsValid(); static const bool is_supported = false;
} static constexpr Dst Do(Src) {
// Force a compile failure if instantiated.
// Forces a crash, like a CHECK(false). Used for numeric boundary errors. return CheckOnFailure::template HandleFailure<Dst>();
struct CheckOnFailure {
template <typename T>
static T HandleFailure() {
#if defined(__GNUC__) || defined(__clang__)
__builtin_trap();
#else
((void)(*(volatile char*)0 = 0));
#endif
return T();
} }
}; };
// Simple wrapper for statically checking if a type's range is contained.
template <typename Dst, typename Src> template <typename Dst, typename Src>
struct IsTypeInRangeForNumericType { struct IsValueInRangeFastOp {
static const bool value = static const bool is_supported = false;
internal::StaticDstRangeRelationToSrcRange<Dst, Src>::value == static constexpr bool Do(Src) {
internal::NUMERIC_RANGE_CONTAINED; // Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
}; };
#endif // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
// Convenience function that returns true if the supplied value is in range
// for the destination type.
template <typename Dst, typename Src>
constexpr bool IsValueInRangeForNumericType(Src value) {
return internal::IsValueInRangeFastOp<Dst, Src>::is_supported
? internal::IsValueInRangeFastOp<Dst, Src>::Do(value)
: internal::DstRangeRelationToSrcRange<Dst>(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, template <typename Dst,
class CheckHandler = CheckOnFailure, class CheckHandler = internal::CheckOnFailure,
typename Src> typename Src>
constexpr Dst checked_cast(Src value) { constexpr Dst checked_cast(Src value) {
// This throws a compile-time error on evaluating the constexpr if it can be // This throws a compile-time error on evaluating the constexpr if it can be
...@@ -58,30 +68,6 @@ constexpr Dst checked_cast(Src value) { ...@@ -58,30 +68,6 @@ constexpr Dst checked_cast(Src value) {
: CheckHandler::template HandleFailure<Dst>(); : CheckHandler::template HandleFailure<Dst>();
} }
// 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);
}
// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN. // 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 // 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. // implement all of the static constexpr member functions in the class below.
...@@ -106,8 +92,6 @@ struct SaturationDefaultLimits : public std::numeric_limits<T> { ...@@ -106,8 +92,6 @@ struct SaturationDefaultLimits : public std::numeric_limits<T> {
} }
}; };
namespace internal {
template <typename Dst, template <typename> class S, typename Src> template <typename Dst, template <typename> class S, typename Src>
constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) { constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
// For some reason clang generates much better code when the branch is // For some reason clang generates much better code when the branch is
...@@ -130,9 +114,15 @@ template <typename Dst, ...@@ -130,9 +114,15 @@ template <typename Dst,
typename Src> typename Src>
constexpr Dst saturated_cast(Src value) { constexpr Dst saturated_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type; using SrcType = typename UnderlyingType<Src>::type;
return saturated_cast_impl<Dst, SaturationHandler, SrcType>( return !IsCompileTimeConstant(value) &&
value, SaturateFastAsmOp<Dst, Src>::is_supported &&
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(value)); std::is_same<SaturationHandler<Dst>,
SaturationDefaultLimits<Dst>>::value
? SaturateFastAsmOp<Dst, SrcType>::Do(value)
: saturated_cast_impl<Dst, SaturationHandler, SrcType>(
value,
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
value));
} }
// strict_cast<> is analogous to static_cast<> for numeric types, except that // strict_cast<> is analogous to static_cast<> for numeric types, except that
...@@ -257,11 +247,16 @@ BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=); ...@@ -257,11 +247,16 @@ BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=);
}; // namespace internal }; // namespace internal
using internal::as_signed;
using internal::as_unsigned;
using internal::checked_cast;
using internal::strict_cast; using internal::strict_cast;
using internal::saturated_cast; using internal::saturated_cast;
using internal::SafeUnsignedAbs; using internal::SafeUnsignedAbs;
using internal::StrictNumeric; using internal::StrictNumeric;
using internal::MakeStrictNum; using internal::MakeStrictNum;
using internal::IsValueInRangeForNumericType;
using internal::IsTypeInRangeForNumericType;
using internal::IsValueNegative; using internal::IsValueNegative;
// Explicitly make a shorter size_t alias for convenience. // Explicitly make a shorter size_t alias for convenience.
......
// 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.
#ifndef BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#define BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions_impl.h"
namespace base {
namespace internal {
template <typename Dst, typename Src, typename Enable = void>
struct IsValueInRangeFastOp {
static const bool is_supported = false;
static constexpr bool Do(Src value) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
// This signed range comparison is faster on arm than the normal boundary
// checking due to the arm instructions for fixed shift operations.
template <typename Dst, typename Src>
struct IsValueInRangeFastOp<
Src,
Dst,
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 const bool is_supported = true;
__attribute__((always_inline)) static constexpr bool Do(Src value) {
return (value >> IntegerBitsPlusSign<Dst>::value) ==
(std::is_signed<Src>::value
? (value >> (IntegerBitsPlusSign<Src>::value - 1))
: Src(0));
}
};
// Fast saturation to a destination type.
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static const 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));
} else {
asm("usat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
:
[src] "r"(src), [shift] "n"(std::is_same<uint32_t, Dst>::value
? IntegerBitsPlusSign<Dst>::value - 1
: IntegerBitsPlusSign<Dst>::value));
}
return static_cast<Dst>(result);
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
...@@ -76,6 +76,32 @@ constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) { ...@@ -76,6 +76,32 @@ constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
: static_cast<UnsignedT>(value); : static_cast<UnsignedT>(value);
} }
// This provides a small optimization that generates more compact code when one
// of the components in an operation is a compile-time constant.
template <typename T>
constexpr bool IsCompileTimeConstant(const T v) {
#if defined(__clang__) || defined(__GNUC__)
return __builtin_constant_p(v);
#else
return false;
#endif
}
// 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(__GNUC__) || defined(__clang__)
__builtin_trap();
#else
((void)(*(volatile char*)0 = 0));
#endif
return T();
}
};
enum IntegerRepresentation { enum IntegerRepresentation {
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED INTEGER_REPRESENTATION_SIGNED
...@@ -337,6 +363,13 @@ struct DstRangeRelationToSrcRangeImpl<Dst, ...@@ -337,6 +363,13 @@ struct DstRangeRelationToSrcRangeImpl<Dst,
} }
}; };
// Simple wrapper for statically checking if a type's range is contained.
template <typename Dst, typename Src>
struct IsTypeInRangeForNumericType {
static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
NUMERIC_RANGE_CONTAINED;
};
template <typename Dst, template <typename Dst,
template <typename> class Bounds = std::numeric_limits, template <typename> class Bounds = std::numeric_limits,
typename Src> typename Src>
...@@ -604,6 +637,41 @@ struct IsStrictOp { ...@@ -604,6 +637,41 @@ struct IsStrictOp {
!(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped); !(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);
}
// 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 T>
constexpr T GetMaxOrMin(bool is_min) {
// For both signed and unsigned math the bit pattern for minimum is really
// just one plus the maximum. However, we have to cast to unsigned to ensure
// we get well-defined overflow semantics.
return as_unsigned(std::numeric_limits<T>::max()) + is_min;
}
template <typename L, typename R> template <typename L, typename R>
constexpr bool IsLessImpl(const L lhs, constexpr bool IsLessImpl(const L lhs,
const R rhs, const R rhs,
......
// 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.
#ifndef BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#define BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#include <cassert>
#include <limits>
#include <type_traits>
#include "base/numerics/safe_conversions.h"
namespace base {
namespace internal {
template <typename T, typename U>
struct CheckedMulFastAsmOp {
static const bool is_supported =
FastIntegerArithmeticPromotion<T, U>::is_contained;
// The following is much more efficient than the Clang and GCC builtins for
// performing overflow-checked multiplication when a twice wider type is
// available. The below compiles down to 2-3 instructions, depending on the
// width of the types in use.
// As an example, an int32_t multiply compiles to:
// smull r0, r1, r0, r1
// cmp r1, r1, asr #31
// And an int16_t multiply compiles to:
// smulbb r1, r1, r0
// asr r2, r1, #16
// cmp r2, r1, asr #15
template <typename V>
__attribute__((always_inline)) static bool Do(T x, U y, V* result) {
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
Promotion presult;
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
*result = static_cast<V>(presult);
return IsValueInRangeForNumericType<V>(presult);
}
};
template <typename T, typename U>
struct ClampedAddFastAsmOp {
static const bool is_supported =
BigEnoughPromotion<T, U>::is_contained &&
IsTypeInRangeForNumericType<
int32_t,
typename BigEnoughPromotion<T, U>::type>::value;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
if (IsIntegerArithmeticSafe<int, T, U>::value)
return saturated_cast<V>(x + y);
int32_t result;
int32_t x_i32 = x;
int32_t y_i32 = y;
asm("qadd %[result], %[first], %[second]"
: [result] "=r"(result)
: [first] "r"(x_i32), [second] "r"(y_i32));
return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
static const bool is_supported =
BigEnoughPromotion<T, U>::is_contained &&
IsTypeInRangeForNumericType<
int32_t,
typename BigEnoughPromotion<T, U>::type>::value;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
if (IsIntegerArithmeticSafe<int, T, U>::value)
return saturated_cast<V>(x - y);
int32_t result;
int32_t x_i32 = x;
int32_t y_i32 = y;
asm("qsub %[result], %[first], %[second]"
: [result] "=r"(result)
: [first] "r"(x_i32), [second] "r"(y_i32));
return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
static const bool is_supported = CheckedMulFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// Use the CheckedMulFastAsmOp for full-width 32-bit values, because
// it's fewer instructions than promoting and then saturating.
if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
!IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
V result;
if (CheckedMulFastAsmOp<T, U>::Do(x, y, &result))
return result;
return GetMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
}
assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
return saturated_cast<V>(static_cast<Promotion>(x) *
static_cast<Promotion>(y));
}
};
} // namespace internal
} // namespace base
#endif // BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
...@@ -11,9 +11,61 @@ ...@@ -11,9 +11,61 @@
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
#include "base/numerics/safe_math_arm_impl.h"
#define BASE_HAS_ASSEMBLER_SAFE_MATH (1)
#else
#define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
#endif
namespace base { namespace base {
namespace internal { namespace internal {
// These are the non-functioning boilerplate implementations of the optimized
// safe math routines.
#if !BASE_HAS_ASSEMBLER_SAFE_MATH
template <typename T, typename U>
struct CheckedMulFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr bool Do(T, U, V*) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
};
template <typename T, typename U>
struct ClampedAddFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
static const bool is_supported = false;
template <typename V>
static constexpr V Do(T, U) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
#endif // BASE_HAS_ASSEMBLER_SAFE_MATH
#undef BASE_HAS_ASSEMBLER_SAFE_MATH
template <typename T, typename U> template <typename T, typename U>
struct CheckedAddFastOp { struct CheckedAddFastOp {
static const bool is_supported = true; static const bool is_supported = true;
...@@ -49,29 +101,63 @@ struct CheckedMulFastOp { ...@@ -49,29 +101,63 @@ struct CheckedMulFastOp {
#endif #endif
template <typename V> template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) { __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return !__builtin_mul_overflow(x, y, result); return (!IsCompileTimeConstant(x) && !IsCompileTimeConstant(y)) &&
CheckedMulFastAsmOp<T, U>::is_supported
? CheckedMulFastAsmOp<T, U>::Do(x, y, result)
: !__builtin_mul_overflow(x, y, result);
} }
}; };
// This is a placeholder until the ARM implementation lands in the next CL.
template <typename T, typename U> template <typename T, typename U>
struct ClampedAddFastOp { struct ClampedAddFastOp {
static const bool is_supported = false; static const bool is_supported = true;
template <typename V> template <typename V>
static V Do(T, U) { static V Do(T x, U y) {
assert(false); if ((!IsCompileTimeConstant(x) || !IsCompileTimeConstant(y)) &&
return 0; ClampedAddFastAsmOp<T, U>::is_supported) {
return ClampedAddFastAsmOp<T, U>::template Do<V>(x, y);
}
V result;
return !__builtin_add_overflow(x, y, &result)
? result
: GetMaxOrMin<V>(IsCompileTimeConstant(x) ? IsValueNegative(x)
: IsValueNegative(y));
} }
}; };
// This is a placeholder until the ARM implementation lands in the next CL.
template <typename T, typename U> template <typename T, typename U>
struct ClampedSubFastOp { struct ClampedSubFastOp {
static const bool is_supported = false; static const bool is_supported = true;
template <typename V> template <typename V>
static V Do(T, U) { static V Do(T x, U y) {
assert(false); if ((!IsCompileTimeConstant(x) || !IsCompileTimeConstant(y)) &&
return 0; ClampedSubFastAsmOp<T, U>::is_supported) {
return ClampedSubFastAsmOp<T, U>::template Do<V>(x, y);
}
V result;
return !__builtin_sub_overflow(x, y, &result)
? result
: GetMaxOrMin<V>(IsCompileTimeConstant(x) ? IsValueNegative(x)
: !IsValueNegative(y));
}
};
template <typename T, typename U>
struct ClampedMulFastOp {
static const bool is_supported = CheckedMulFastOp<T, U>::is_supported;
template <typename V>
static V Do(T x, U y) {
if ((!IsCompileTimeConstant(x) && !IsCompileTimeConstant(y)) &&
ClampedMulFastAsmOp<T, U>::is_supported) {
return ClampedMulFastAsmOp<T, U>::template Do<V>(x, y);
}
V result;
return CheckedMulFastOp<T, U>::Do(x, y, &result)
? result
: GetMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
} }
}; };
......
...@@ -24,20 +24,24 @@ ...@@ -24,20 +24,24 @@
(__clang_major__ == 3 && __clang_minor__ >= 4))) || \ (__clang_major__ == 3 && __clang_minor__ >= 4))) || \
(defined(__GNUC__) && __GNUC__ >= 5)) (defined(__GNUC__) && __GNUC__ >= 5))
#include "base/numerics/safe_math_clang_gcc_impl.h" #include "base/numerics/safe_math_clang_gcc_impl.h"
#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
// These are the placeholder implementations for platforms that don't provide
// optimized builtin/intrinsic operations.
#else #else
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
#endif
namespace base { namespace base {
namespace internal { namespace internal {
// These are the non-functioning boilerplate implementations of the optimized
// safe math routines.
#if !BASE_HAS_OPTIMIZED_SAFE_MATH
template <typename T, typename U> template <typename T, typename U>
struct CheckedAddFastOp { struct CheckedAddFastOp {
static const bool is_supported = false; static const bool is_supported = false;
template <typename V> template <typename V>
static bool Do(T, U, V*) { static constexpr bool Do(T, U, V*) {
assert(false); // Should not be reached. // Force a compile failure if instantiated.
return false; return CheckOnFailure::template HandleFailure<bool>();
} }
}; };
...@@ -45,9 +49,9 @@ template <typename T, typename U> ...@@ -45,9 +49,9 @@ template <typename T, typename U>
struct CheckedSubFastOp { struct CheckedSubFastOp {
static const bool is_supported = false; static const bool is_supported = false;
template <typename V> template <typename V>
static bool Do(T, U, V*) { static constexpr bool Do(T, U, V*) {
assert(false); // Should not be reached. // Force a compile failure if instantiated.
return false; return CheckOnFailure::template HandleFailure<bool>();
} }
}; };
...@@ -55,9 +59,9 @@ template <typename T, typename U> ...@@ -55,9 +59,9 @@ template <typename T, typename U>
struct CheckedMulFastOp { struct CheckedMulFastOp {
static const bool is_supported = false; static const bool is_supported = false;
template <typename V> template <typename V>
static bool Do(T, U, V*) { static constexpr bool Do(T, U, V*) {
assert(false); // Should not be reached. // Force a compile failure if instantiated.
return false; return CheckOnFailure::template HandleFailure<bool>();
} }
}; };
...@@ -65,9 +69,9 @@ template <typename T, typename U> ...@@ -65,9 +69,9 @@ template <typename T, typename U>
struct ClampedAddFastOp { struct ClampedAddFastOp {
static const bool is_supported = false; static const bool is_supported = false;
template <typename V> template <typename V>
static V Do(T, U) { static constexpr V Do(T, U) {
assert(false); // Should not be reached. // Force a compile failure if instantiated.
return false; return CheckOnFailure::template HandleFailure<V>();
} }
}; };
...@@ -75,18 +79,23 @@ template <typename T, typename U> ...@@ -75,18 +79,23 @@ template <typename T, typename U>
struct ClampedSubFastOp { struct ClampedSubFastOp {
static const bool is_supported = false; static const bool is_supported = false;
template <typename V> template <typename V>
static V Do(T, U) { static constexpr V Do(T, U) {
assert(false); // Should not be reached. // Force a compile failure if instantiated.
return false; return CheckOnFailure::template HandleFailure<V>();
} }
}; };
} // namespace internal template <typename T, typename U>
} // namespace base struct ClampedMulFastOp {
#endif static const bool is_supported = false;
template <typename V>
namespace base { static constexpr V Do(T, U) {
namespace internal { // Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<V>();
}
};
#endif // BASE_HAS_OPTIMIZED_SAFE_MATH
#undef BASE_HAS_OPTIMIZED_SAFE_MATH
// This is used for UnsignedAbs, where we need to support floating-point // This is used for UnsignedAbs, where we need to support floating-point
// template instantiations even though we don't actually support the operations. // template instantiations even though we don't actually support the operations.
......
...@@ -20,45 +20,10 @@ ...@@ -20,45 +20,10 @@
#include <mmintrin.h> #include <mmintrin.h>
#endif #endif
namespace base {
namespace internal {
using std::numeric_limits; using std::numeric_limits;
using base::CheckedNumeric;
using base::ClampedNumeric;
using base::IsValidForType;
using base::ValueOrDieForType;
using base::ValueOrDefaultForType;
using base::MakeCheckedNum;
using base::MakeClampedNum;
using base::CheckMax;
using base::CheckMin;
using base::CheckAdd;
using base::CheckSub;
using base::CheckMul;
using base::CheckDiv;
using base::CheckMod;
using base::CheckLsh;
using base::CheckRsh;
using base::ClampMax;
using base::ClampMin;
using base::ClampAdd;
using base::ClampSub;
using base::ClampMul;
using base::ClampDiv;
using base::ClampMod;
using base::ClampLsh;
using base::ClampRsh;
using base::as_unsigned;
using base::checked_cast;
using base::IsValueInRangeForNumericType;
using base::IsValueNegative;
using base::SaturationDefaultLimits;
using base::SizeT;
using base::StrictNumeric;
using base::MakeStrictNum;
using base::saturated_cast;
using base::strict_cast;
using base::internal::MaxExponent;
using base::internal::IntegerBitsPlusSign;
using base::internal::RangeCheck;
// These tests deliberately cause arithmetic boundary errors. If the compiler is // These tests deliberately cause arithmetic boundary errors. If the compiler is
// aggressive enough, it can const detect these errors, so we disable warnings. // aggressive enough, it can const detect these errors, so we disable warnings.
...@@ -87,9 +52,6 @@ Dst GetMaxConvertibleToFloat() { ...@@ -87,9 +52,6 @@ Dst GetMaxConvertibleToFloat() {
return static_cast<Dst>(max); return static_cast<Dst>(max);
} }
namespace base {
namespace internal {
// Test corner case promotions used // Test corner case promotions used
static_assert(IsIntegerArithmeticSafe<int32_t, int8_t, int8_t>::value, ""); static_assert(IsIntegerArithmeticSafe<int32_t, int8_t, int8_t>::value, "");
static_assert(IsIntegerArithmeticSafe<int32_t, int16_t, int8_t>::value, ""); static_assert(IsIntegerArithmeticSafe<int32_t, int16_t, int8_t>::value, "");
...@@ -155,11 +117,6 @@ U GetNumericValueForTest(const U& src) { ...@@ -155,11 +117,6 @@ U GetNumericValueForTest(const U& src) {
return src; return src;
} }
} // namespace internal.
} // namespace base.
using base::internal::GetNumericValueForTest;
// Logs the ValueOrDie() failure instead of crashing. // Logs the ValueOrDie() failure instead of crashing.
struct LogOnFailure { struct LogOnFailure {
template <typename T> template <typename T>
...@@ -802,6 +759,7 @@ static void TestArithmetic(const char* dst, int line) { ...@@ -802,6 +759,7 @@ static void TestArithmetic(const char* dst, int line) {
TEST(SafeNumerics, SignedIntegerMath) { TEST(SafeNumerics, SignedIntegerMath) {
TEST_ARITHMETIC(int8_t); TEST_ARITHMETIC(int8_t);
TEST_ARITHMETIC(int16_t);
TEST_ARITHMETIC(int); TEST_ARITHMETIC(int);
TEST_ARITHMETIC(intptr_t); TEST_ARITHMETIC(intptr_t);
TEST_ARITHMETIC(intmax_t); TEST_ARITHMETIC(intmax_t);
...@@ -809,6 +767,7 @@ TEST(SafeNumerics, SignedIntegerMath) { ...@@ -809,6 +767,7 @@ TEST(SafeNumerics, SignedIntegerMath) {
TEST(SafeNumerics, UnsignedIntegerMath) { TEST(SafeNumerics, UnsignedIntegerMath) {
TEST_ARITHMETIC(uint8_t); TEST_ARITHMETIC(uint8_t);
TEST_ARITHMETIC(uint16_t);
TEST_ARITHMETIC(unsigned int); TEST_ARITHMETIC(unsigned int);
TEST_ARITHMETIC(uintptr_t); TEST_ARITHMETIC(uintptr_t);
TEST_ARITHMETIC(uintmax_t); TEST_ARITHMETIC(uintmax_t);
...@@ -849,8 +808,7 @@ constexpr RangeConstraint RangeCheckToEnum(const RangeCheck constraint) { ...@@ -849,8 +808,7 @@ constexpr RangeConstraint RangeCheckToEnum(const RangeCheck constraint) {
// EXPECT_EQ wrappers providing specific detail on test failures. // EXPECT_EQ wrappers providing specific detail on test failures.
#define TEST_EXPECTED_RANGE(expected, actual) \ #define TEST_EXPECTED_RANGE(expected, actual) \
EXPECT_EQ(expected, \ EXPECT_EQ(expected, \
RangeCheckToEnum( \ RangeCheckToEnum(DstRangeRelationToSrcRange<Dst>(actual))) \
base::internal::DstRangeRelationToSrcRange<Dst>(actual))) \
<< "Conversion test: " << src << " value " << actual << " to " << dst \ << "Conversion test: " << src << " value " << actual << " to " << dst \
<< " on line " << line << " on line " << line
...@@ -1164,20 +1122,43 @@ TEST(SafeNumerics, IntMinOperations) { ...@@ -1164,20 +1122,43 @@ TEST(SafeNumerics, IntMinOperations) {
TEST_NUMERIC_CONVERSION(int8_t, int8_t, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(int8_t, int8_t, SIGN_PRESERVING_VALUE_PRESERVING);
TEST_NUMERIC_CONVERSION(uint8_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(uint8_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
TEST_NUMERIC_CONVERSION(int8_t, int16_t, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(int8_t, int, SIGN_PRESERVING_NARROW); TEST_NUMERIC_CONVERSION(int8_t, int, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(uint8_t, uint16_t, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(uint8_t, unsigned int, SIGN_PRESERVING_NARROW); TEST_NUMERIC_CONVERSION(uint8_t, unsigned int, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(int8_t, float, SIGN_PRESERVING_NARROW); TEST_NUMERIC_CONVERSION(int8_t, float, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(uint8_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); TEST_NUMERIC_CONVERSION(uint8_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
TEST_NUMERIC_CONVERSION(uint8_t, int16_t, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(uint8_t, int, SIGN_TO_UNSIGN_NARROW); TEST_NUMERIC_CONVERSION(uint8_t, int, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(uint8_t, intmax_t, SIGN_TO_UNSIGN_NARROW); TEST_NUMERIC_CONVERSION(uint8_t, intmax_t, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(uint8_t, float, SIGN_TO_UNSIGN_NARROW); TEST_NUMERIC_CONVERSION(uint8_t, float, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(int8_t, uint16_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
TEST_NUMERIC_CONVERSION(int8_t, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); TEST_NUMERIC_CONVERSION(int8_t, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
TEST_NUMERIC_CONVERSION(int8_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); TEST_NUMERIC_CONVERSION(int8_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
} }
TEST(SafeNumerics, Int16Operations) {
TEST_NUMERIC_CONVERSION(int16_t, int16_t, SIGN_PRESERVING_VALUE_PRESERVING);
TEST_NUMERIC_CONVERSION(uint16_t, uint16_t, SIGN_PRESERVING_VALUE_PRESERVING);
TEST_NUMERIC_CONVERSION(int16_t, int, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(uint16_t, unsigned int, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(int16_t, float, SIGN_PRESERVING_NARROW);
TEST_NUMERIC_CONVERSION(uint16_t, int16_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
TEST_NUMERIC_CONVERSION(uint16_t, int, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(uint16_t, intmax_t, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(uint16_t, float, SIGN_TO_UNSIGN_NARROW);
TEST_NUMERIC_CONVERSION(int16_t, unsigned int,
UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
TEST_NUMERIC_CONVERSION(int16_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
}
TEST(SafeNumerics, IntOperations) { TEST(SafeNumerics, IntOperations) {
TEST_NUMERIC_CONVERSION(int, int, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(int, int, SIGN_PRESERVING_VALUE_PRESERVING);
TEST_NUMERIC_CONVERSION(unsigned int, unsigned int, TEST_NUMERIC_CONVERSION(unsigned int, unsigned int,
...@@ -1561,3 +1542,6 @@ TEST(SafeNumerics, VariadicNumericOperations) { ...@@ -1561,3 +1542,6 @@ TEST(SafeNumerics, VariadicNumericOperations) {
EXPECT_EQ(static_cast<decltype(h)::type>(1), h); EXPECT_EQ(static_cast<decltype(h)::type>(1), h);
} }
} }
} // namespace internal
} // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment