Skip to content

Commit

Permalink
Fix base/numerics compile-time constant handling
Browse files Browse the repository at this point in the history
Compile-time constants cannot be detected by __builtin_constant_p()
outside of a constexpr call chain. So, there's no point in trying to
optimize compile-time constants unless it's in a constexpr function.

Change-Id: I5bae8931a347221b0ed030521af56d9558cfb41e
Reviewed-on: https://chromium-review.googlesource.com/588011
Reviewed-by: Erik Chen <erikchen@chromium.org>
Commit-Queue: Justin Schuh <jschuh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#490045}
  • Loading branch information
jschuh authored and Commit Bot committed Jul 27, 2017
1 parent 85bef40 commit 3f9078d
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 109 deletions.
16 changes: 3 additions & 13 deletions base/numerics/clamped_math.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,9 @@ class ClampedNumeric {
ClampedNumeric& operator^=(const Src rhs);

constexpr ClampedNumeric operator-() const {
return ClampedNumeric<T>(
// The negation of two's complement int min is int min, so we can
// check and just add one to prevent overflow in the constexpr case.
// We use an optimized code path for a known run-time variable.
std::is_signed<T>::value
? (MustTreatAsConstexpr(value_)
? ((std::is_floating_point<T>::value ||
NegateWrapper(value_) !=
std::numeric_limits<T>::lowest())
? NegateWrapper(value_)
: std::numeric_limits<T>::max())
: ClampedSubOp<T, T>::template Do<T>(T(0), value_))
: T(0)); // Clamped unsigned negation is always zero.
// The negation of two's complement int min is int min, so that's the
// only overflow case where we will saturate.
return ClampedNumeric<T>(SaturatedNegWrapper(value_));
}

constexpr ClampedNumeric operator~() const {
Expand Down
41 changes: 31 additions & 10 deletions base/numerics/clamped_math_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@
namespace base {
namespace internal {

template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported
? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
? NegateWrapper(value)
: std::numeric_limits<T>::max())
: ClampedNegFastOp<T>::Do(value);
}

template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
!std::is_signed<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return T(0);
}

template <
typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
constexpr T SaturatedNegWrapper(T value) {
return -value;
}

template <typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
constexpr T SaturatedAbsWrapper(T value) {
Expand All @@ -32,10 +57,8 @@ constexpr T SaturatedAbsWrapper(T value) {
// special case of numeric_limits<T>::min(), by evaluating the bit pattern as
// a signed integer value. If it is the overflow case, we end up subtracting
// one from the unsigned result, thus saturating to numeric_limits<T>::max().
return MustTreatAsConstexpr(value) && !ClampedAbsFastOp<T>::is_supported
? static_cast<T>(SafeUnsignedAbs(value) -
IsValueNegative<T>(SafeUnsignedAbs(value)))
: ClampedAbsFastOp<T>::Do(value);
return static_cast<T>(SafeUnsignedAbs(value) -
IsValueNegative<T>(SafeUnsignedAbs(value)));
}

template <
Expand All @@ -61,9 +84,8 @@ struct ClampedAddOp<T,
return ClampedAddFastOp<T, U>::template Do<V>(x, y);

V result;
// Prefer a compile-time constant (if we have one).
const V saturated = GetMaxOrMin<V>(
IsCompileTimeConstant(x) ? IsValueNegative(x) : IsValueNegative(y));
// TODO(jschuh) C++14 constexpr allows a compile-time constant optimization.
const V saturated = GetMaxOrMin<V>(IsValueNegative(y));
return CheckedAddOp<T, U>::Do(x, y, &result) ? result : saturated;
}
};
Expand All @@ -84,9 +106,8 @@ struct ClampedSubOp<T,
return ClampedSubFastOp<T, U>::template Do<V>(x, y);

V result;
// Prefer a compile-time constant (if we have one).
const V saturated = GetMaxOrMin<V>(
IsCompileTimeConstant(x) ? IsValueNegative(x) : !IsValueNegative(y));
// TODO(jschuh) C++14 constexpr allows a compile-time constant optimization.
const V saturated = GetMaxOrMin<V>(!IsValueNegative(y));
return CheckedSubOp<T, U>::Do(x, y, &result) ? result : saturated;
}
};
Expand Down
2 changes: 1 addition & 1 deletion base/numerics/safe_conversions_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ constexpr bool CanDetectCompileTimeConstant() {
return false;
}
template <typename T>
constexpr bool IsCompileTimeConstant(const T v) {
constexpr bool IsCompileTimeConstant(const T) {
return false;
}
#endif
Expand Down
103 changes: 19 additions & 84 deletions base/numerics/safe_math_clang_gcc_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ struct CheckedMulFastOp {
#endif
template <typename V>
__attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
return (!IsCompileTimeConstant(x) && !IsCompileTimeConstant(y)) &&
CheckedMulFastAsmOp<T, U>::is_supported
return CheckedMulFastAsmOp<T, U>::is_supported
? CheckedMulFastAsmOp<T, U>::Do(x, y, result)
: !__builtin_mul_overflow(x, y, result);
}
Expand All @@ -113,93 +112,30 @@ struct ClampedAddFastOp {
static const bool is_supported = true;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
if ((!IsCompileTimeConstant(x) || !IsCompileTimeConstant(y)) &&
ClampedAddFastAsmOp<T, U>::is_supported) {
if (ClampedAddFastAsmOp<T, U>::is_supported)
return ClampedAddFastAsmOp<T, U>::template Do<V>(x, y);
}

V result;
// TODO(jschuh) C++14 constexpr allows a compile-time constant optimization.
return !__builtin_add_overflow(x, y, &result)
? result
: GetMaxOrMin<V>(IsCompileTimeConstant(x) ? IsValueNegative(x)
: IsValueNegative(y));
: GetMaxOrMin<V>(IsValueNegative(y));
}
};

#if defined(__clang__) // Not supported on GCC.
// This is the fastest negation on Intel, and a decent fallback on arm.
__attribute__((always_inline)) inline int8_t ClampedNegate(uint8_t value) {
uint8_t carry;
return __builtin_subcb(0, value, 0, &carry) + carry;
}

__attribute__((always_inline)) inline int8_t ClampedNegate(int8_t value) {
return ClampedNegate(static_cast<uint8_t>(value));
}

__attribute__((always_inline)) inline int16_t ClampedNegate(uint16_t value) {
uint16_t carry;
return __builtin_subcs(0, value, 0, &carry) + carry;
}

__attribute__((always_inline)) inline int16_t ClampedNegate(int16_t value) {
return ClampedNegate(static_cast<uint16_t>(value));
}

__attribute__((always_inline)) inline int32_t ClampedNegate(uint32_t value) {
uint32_t carry;
return __builtin_subc(0, value, 0, &carry) + carry;
}

__attribute__((always_inline)) inline int32_t ClampedNegate(int32_t value) {
return ClampedNegate(static_cast<uint32_t>(value));
}

// These are the LP64 platforms minus Mac (because Xcode blows up otherwise).
#if !defined(__APPLE__) && defined(__LP64__) && __LP64__
__attribute__((always_inline)) inline int64_t ClampedNegate(uint64_t value) {
uint64_t carry;
return __builtin_subcl(0, value, 0, &carry) + carry;
}
#else // Mac, Windows, and any IL32 platforms.
__attribute__((always_inline)) inline int64_t ClampedNegate(uint64_t value) {
uint64_t carry;
return __builtin_subcll(0, value, 0, &carry) + carry;
}
#endif
__attribute__((always_inline)) inline int64_t ClampedNegate(int64_t value) {
return ClampedNegate(static_cast<uint64_t>(value));
}
#endif

template <typename T, typename U>
struct ClampedSubFastOp {
static const bool is_supported = true;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
if ((!IsCompileTimeConstant(x) || !IsCompileTimeConstant(y)) &&
ClampedSubFastAsmOp<T, U>::is_supported) {
if (ClampedSubFastAsmOp<T, U>::is_supported)
return ClampedSubFastAsmOp<T, U>::template Do<V>(x, y);
}

#if defined(__clang__) // Not supported on GCC.
// Fast path for generic clamped negation.
if (std::is_same<T, U>::value && std::is_same<U, V>::value &&
IsCompileTimeConstant(x) && x == 0 && !IsCompileTimeConstant(y)) {
// We use IntegerForDigitsAndSign<> to convert the type to a uint*_t,
// otherwise Xcode can't resolve to the standard integral types correctly.
return ClampedNegate(
static_cast<typename IntegerForDigitsAndSign<
IntegerBitsPlusSign<T>::value, std::is_signed<T>::value>::type>(
y));
}
#endif

V result;
// TODO(jschuh) C++14 constexpr allows a compile-time constant optimization.
return !__builtin_sub_overflow(x, y, &result)
? result
: GetMaxOrMin<V>(IsCompileTimeConstant(x) ? IsValueNegative(x)
: !IsValueNegative(y));
: GetMaxOrMin<V>(!IsValueNegative(y));
}
};

Expand All @@ -208,10 +144,8 @@ struct ClampedMulFastOp {
static const bool is_supported = CheckedMulFastOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
if ((!IsCompileTimeConstant(x) && !IsCompileTimeConstant(y)) &&
ClampedMulFastAsmOp<T, U>::is_supported) {
if (ClampedMulFastAsmOp<T, U>::is_supported)
return ClampedMulFastAsmOp<T, U>::template Do<V>(x, y);
}

V result;
return CheckedMulFastOp<T, U>::Do(x, y, &result)
Expand All @@ -221,18 +155,19 @@ struct ClampedMulFastOp {
};

template <typename T>
struct ClampedAbsFastOp {
// The generic code is pretty much optimal on arm, so we use it instead.
#if defined(__ARMEL__) || defined(__arch64__)
static const bool is_supported = false;
#else
struct ClampedNegFastOp {
static const bool is_supported = std::is_signed<T>::value;
#endif
__attribute__((always_inline)) static T Do(T value) {
// This variable assignment is necessary to prevent the compiler from
// emitting longer, ugly, branchy code.
T negated = ClampedSubFastOp<T, T>::template Do<T>(T(0), value);
return IsValueNegative(value) ? negated : value;
// Use this when there is no assembler path available.
if (!ClampedSubFastAsmOp<T, T>::is_supported) {
T result;
return !__builtin_sub_overflow(T(0), value, &result)
? result
: std::numeric_limits<T>::max();
}

// Fallback to the normal subtraction path.
return ClampedSubFastOp<T, T>::template Do<T>(T(0), value);
}
};

Expand Down
2 changes: 1 addition & 1 deletion base/numerics/safe_math_shared_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ struct ClampedMulFastOp {
};

template <typename T>
struct ClampedAbsFastOp {
struct ClampedNegFastOp {
static const bool is_supported = false;
static constexpr T Do(T) {
// Force a compile failure if instantiated.
Expand Down

0 comments on commit 3f9078d

Please sign in to comment.