mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-25 09:57:32 +09:00
Issue #2749 - Part 6 - Teach mfbt/casting.h to deal with floating point values
This now uses `if constexpr (...)` which is a lot more readable, and still compiles to almost no assembly instructions, as expected. Floating point casting assert when casting an integer that's too large to be represented exactly as a floating point (e.g. UINT64_MAX to double, since double have less than 64 bytes of mantissa), or when casting a double that's too large to be represented in a float.
This commit is contained in:
parent
d461e75034
commit
fc9643a1a7
2 changed files with 257 additions and 155 deletions
256
mfbt/Casting.h
256
mfbt/Casting.h
|
|
@ -11,7 +11,8 @@
|
|||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
|
@ -62,190 +63,135 @@ BitwiseCast(const From aFrom)
|
|||
|
||||
namespace detail {
|
||||
|
||||
enum ToSignedness { ToIsSigned, ToIsUnsigned };
|
||||
enum FromSignedness { FromIsSigned, FromIsUnsigned };
|
||||
template <typename T>
|
||||
constexpr int64_t safe_integer() {
|
||||
static_assert(std::is_floating_point_v<T>);
|
||||
return std::pow(2, std::numeric_limits<T>::digits);
|
||||
}
|
||||
|
||||
template<typename From,
|
||||
typename To,
|
||||
FromSignedness = IsSigned<From>::value ? FromIsSigned : FromIsUnsigned,
|
||||
ToSignedness = IsSigned<To>::value ? ToIsSigned : ToIsUnsigned>
|
||||
struct BoundsCheckImpl;
|
||||
template <typename T>
|
||||
constexpr uint64_t safe_integer_unsigned() {
|
||||
static_assert(std::is_floating_point_v<T>);
|
||||
return std::pow(2, std::numeric_limits<T>::digits);
|
||||
}
|
||||
|
||||
// Implicit conversions on operands to binary operations make this all a bit
|
||||
// hard to verify. Attempt to ease the pain below by *only* comparing values
|
||||
// that are obviously the same type (and will undergo no further conversions),
|
||||
// even when it's not strictly necessary, for explicitness.
|
||||
// This is working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81676,
|
||||
// fixed in gcc-10
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
template <typename In, typename Out>
|
||||
bool IsInBounds(In aIn) {
|
||||
constexpr bool inSigned = std::is_signed_v<In>;
|
||||
constexpr bool outSigned = std::is_signed_v<Out>;
|
||||
constexpr bool bothSigned = inSigned && outSigned;
|
||||
constexpr bool bothUnsigned = !inSigned && !outSigned;
|
||||
constexpr bool inFloat = std::is_floating_point_v<In>;
|
||||
constexpr bool outFloat = std::is_floating_point_v<Out>;
|
||||
constexpr bool bothFloat = inFloat && outFloat;
|
||||
constexpr bool noneFloat = !inFloat && !outFloat;
|
||||
constexpr Out outMax = std::numeric_limits<Out>::max();
|
||||
constexpr Out outMin = std::numeric_limits<Out>::lowest();
|
||||
|
||||
enum UUComparison { FromIsBigger, FromIsNotBigger };
|
||||
// This selects the widest of two types, and is used to cast throughout.
|
||||
using select_widest = std::conditional_t<(sizeof(In) > sizeof(Out)), In, Out>;
|
||||
|
||||
// Unsigned-to-unsigned range check
|
||||
|
||||
template<typename From, typename To,
|
||||
UUComparison = (sizeof(From) > sizeof(To))
|
||||
? FromIsBigger
|
||||
: FromIsNotBigger>
|
||||
struct UnsignedUnsignedCheck;
|
||||
|
||||
template<typename From, typename To>
|
||||
struct UnsignedUnsignedCheck<From, To, FromIsBigger>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
return aFrom <= From(To(-1));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
struct UnsignedUnsignedCheck<From, To, FromIsNotBigger>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
struct BoundsCheckImpl<From, To, FromIsUnsigned, ToIsUnsigned>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
return UnsignedUnsignedCheck<From, To>::checkBounds(aFrom);
|
||||
}
|
||||
};
|
||||
|
||||
// Signed-to-unsigned range check
|
||||
|
||||
template<typename From, typename To>
|
||||
struct BoundsCheckImpl<From, To, FromIsSigned, ToIsUnsigned>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
if (aFrom < 0) {
|
||||
if constexpr (bothFloat) {
|
||||
if (aIn > select_widest(outMax) || aIn < select_widest(outMin)) {
|
||||
return false;
|
||||
}
|
||||
if (sizeof(To) >= sizeof(From)) {
|
||||
return true;
|
||||
}
|
||||
// Normal casting applies, the floating point number is floored.
|
||||
if constexpr (inFloat && !outFloat) {
|
||||
static_assert(sizeof(aIn) <= sizeof(int64_t));
|
||||
// Check if the input floating point is larger than the output bounds. This
|
||||
// catches situations where the input is a float larger than the max of the
|
||||
// output type.
|
||||
if (aIn < static_cast<double>(outMin) ||
|
||||
aIn > static_cast<double>(outMax)) {
|
||||
return false;
|
||||
}
|
||||
return aFrom <= From(To(-1));
|
||||
}
|
||||
};
|
||||
|
||||
// Unsigned-to-signed range check
|
||||
|
||||
enum USComparison { FromIsSmaller, FromIsNotSmaller };
|
||||
|
||||
template<typename From, typename To,
|
||||
USComparison = (sizeof(From) < sizeof(To))
|
||||
? FromIsSmaller
|
||||
: FromIsNotSmaller>
|
||||
struct UnsignedSignedCheck;
|
||||
|
||||
template<typename From, typename To>
|
||||
struct UnsignedSignedCheck<From, To, FromIsSmaller>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
struct UnsignedSignedCheck<From, To, FromIsNotSmaller>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
const To MaxValue = To((1ULL << (CHAR_BIT * sizeof(To) - 1)) - 1);
|
||||
return aFrom <= From(MaxValue);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
struct BoundsCheckImpl<From, To, FromIsUnsigned, ToIsSigned>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
return UnsignedSignedCheck<From, To>::checkBounds(aFrom);
|
||||
}
|
||||
};
|
||||
|
||||
// Signed-to-signed range check
|
||||
|
||||
template<typename From, typename To>
|
||||
struct BoundsCheckImpl<From, To, FromIsSigned, ToIsSigned>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
if (sizeof(From) <= sizeof(To)) {
|
||||
return true;
|
||||
// At this point we know that the input can be converted to an integer.
|
||||
// Check if it's larger than the bounds of the target integer.
|
||||
if (outSigned) {
|
||||
int64_t asInteger = static_cast<int64_t>(aIn);
|
||||
if (asInteger < outMin || asInteger > outMax) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
uint64_t asInteger = static_cast<uint64_t>(aIn);
|
||||
if (asInteger > outMax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const To MaxValue = To((1ULL << (CHAR_BIT * sizeof(To) - 1)) - 1);
|
||||
const To MinValue = -MaxValue - To(1);
|
||||
return From(MinValue) <= aFrom &&
|
||||
From(aFrom) <= From(MaxValue);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename From, typename To,
|
||||
bool TypesAreIntegral = IsIntegral<From>::value &&
|
||||
IsIntegral<To>::value>
|
||||
class BoundsChecker;
|
||||
|
||||
template<typename From>
|
||||
class BoundsChecker<From, From, true>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom) { return true; }
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
class BoundsChecker<From, To, true>
|
||||
{
|
||||
public:
|
||||
static bool checkBounds(const From aFrom)
|
||||
{
|
||||
return BoundsCheckImpl<From, To>::checkBounds(aFrom);
|
||||
// Checks if the integer is representable exactly as a floating point value of
|
||||
// a specific width.
|
||||
if constexpr (!inFloat && outFloat) {
|
||||
if constexpr (inSigned) {
|
||||
if (aIn < -safe_integer<Out>() || aIn > safe_integer<Out>()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (aIn >= safe_integer_unsigned<Out>()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename From, typename To>
|
||||
inline bool
|
||||
IsInBounds(const From aFrom)
|
||||
{
|
||||
return BoundsChecker<From, To>::checkBounds(aFrom);
|
||||
if constexpr (noneFloat) {
|
||||
if constexpr (bothUnsigned) {
|
||||
if (aIn > select_widest(outMax)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if constexpr (bothSigned) {
|
||||
if (aIn > select_widest(outMax) || aIn < select_widest(outMin)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if constexpr (inSigned && !outSigned) {
|
||||
if (aIn < 0 || std::make_unsigned_t<In>(aIn) > outMax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if constexpr (!inSigned && outSigned) {
|
||||
if (aIn > select_widest(outMax)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Cast a value of integral type |From| to a value of integral type |To|,
|
||||
* asserting that the cast will be a safe cast per C++ (that is, that |to| is in
|
||||
* the range of values permitted for the type |From|).
|
||||
* Cast a value of type |From| to a value of type |To|, asserting that the cast
|
||||
* will be a safe cast per C++ (that is, that |to| is in the range of values
|
||||
* permitted for the type |From|).
|
||||
* In particular, this will fail if a integer cann
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
inline To
|
||||
AssertedCast(const From aFrom)
|
||||
{
|
||||
static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
|
||||
MOZ_ASSERT((detail::IsInBounds<From, To>(aFrom)));
|
||||
return static_cast<To>(aFrom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a value of integral type |From| to a value of integral type |To|,
|
||||
* release asserting that the cast will be a safe cast per C++ (that is, that
|
||||
* |to| is in the range of values permitted for the type |From|).
|
||||
* Cast a value of numeric type |From| to a value of numeric type |To|, release
|
||||
* asserting that the cast will be a safe cast per C++ (that is, that |to| is in
|
||||
* the range of values permitted for the type |From|).
|
||||
* In particular, this will fail if a integer cannot be represented exactly as a
|
||||
* floating point value, because it's too large.
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
inline To
|
||||
ReleaseAssertedCast(const From aFrom)
|
||||
{
|
||||
static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
|
||||
MOZ_RELEASE_ASSERT((detail::IsInBounds<From, To>(aFrom)));
|
||||
return static_cast<To>(aFrom);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue