/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Cast operations to supplement the built-in casting operations. */ #ifndef mozilla_Casting_h #define mozilla_Casting_h #include "mozilla/Assertions.h" #include #include #include #include #ifndef __clang__ # include #endif #ifdef DEBUG # include "fmt/format.h" # include #endif namespace mozilla { /** * Sets the outparam value of type |To| with the same underlying bit pattern of * |aFrom|. * * |To| and |From| must be types of the same size; be careful of cross-platform * size differences, or this might fail to compile on some but not all * platforms. * * There is also a variant that returns the value directly. In most cases, the * two variants should be identical. However, in the specific case of x86 * chips, the behavior differs: returning floating-point values directly is done * through the x87 stack, and x87 loads and stores turn signaling NaNs into * quiet NaNs... silently. Returning floating-point values via outparam, * however, is done entirely within the SSE registers when SSE2 floating-point * is enabled in the compiler, which has semantics-preserving behavior you would * expect. * * If preserving the distinction between signaling NaNs and quiet NaNs is * important to you, you should use the outparam version. In all other cases, * you should use the direct return version. */ template inline void BitwiseCast(const From aFrom, To* aResult) { // FIXME: use std::bit_cast once we move to C++20 #if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 11) *aResult = __builtin_bit_cast(To, aFrom); #else static_assert(sizeof(From) == sizeof(To), "To and From must have the same size"); // We could maybe downgrade these to std::is_trivially_copyable, but the // various STLs we use don't all provide it. static_assert(std::is_trivial::value, "shouldn't bitwise-copy a type having non-trivial " "initialization"); static_assert(std::is_trivial::value, "shouldn't bitwise-copy a type having non-trivial " "initialization"); std::memcpy(static_cast(aResult), static_cast(&aFrom), sizeof(From)); #endif } template inline To BitwiseCast(const From aFrom) { To temp; BitwiseCast(aFrom, &temp); return temp; } namespace detail { template constexpr int64_t safe_integer() { static_assert(std::is_floating_point_v); return std::pow(2, std::numeric_limits::digits); } template constexpr uint64_t safe_integer_unsigned() { static_assert(std::is_floating_point_v); return std::pow(2, std::numeric_limits::digits); } template const char* TypeToStringFallback(); template inline constexpr const char* TypeToString() { return TypeToStringFallback(); } #define T2S(type) \ template <> \ inline constexpr const char* TypeToString() { \ return #type; \ } #define T2SF(type) \ template <> \ inline constexpr const char* TypeToStringFallback() { \ return #type; \ } T2S(uint8_t); T2S(uint16_t); T2S(uint32_t); T2S(uint64_t); T2S(int8_t); T2S(int16_t); T2S(int32_t); T2S(int64_t); T2S(char16_t); T2S(char32_t); T2SF(int); T2SF(unsigned int); T2SF(long); T2SF(unsigned long); T2S(float); T2S(double); #undef T2S #undef T2SF #ifdef DEBUG template inline void DiagnosticMessage(In aIn, char aDiagnostic[1024]) { if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { static_assert(sizeof(wchar_t) <= sizeof(int32_t)); // Characters types are printed in hexadecimal for two reasons: // - to easily debug problems with non-printable characters. // - {fmt} refuses to format a string with mixed character type. // It's always possible to cast them to int64_t for lossless printing of the // value. auto [out, size] = fmt::format_to_n( aDiagnostic, 1023, FMT_STRING("Cannot cast {:x} from {} to {}: out of range"), static_cast(aIn), TypeToString(), TypeToString()); *out = 0; } else { auto [out, size] = fmt::format_to_n( aDiagnostic, 1023, FMT_STRING("Cannot cast {} from {} to {}: out of range"), aIn, TypeToString(), TypeToString()); *out = 0; } } #endif template bool IsInBounds(In aIn) { constexpr bool inSigned = std::is_signed_v; constexpr bool outSigned = std::is_signed_v; constexpr bool bothSigned = inSigned && outSigned; constexpr bool bothUnsigned = !inSigned && !outSigned; constexpr bool inFloat = std::is_floating_point_v; constexpr bool outFloat = std::is_floating_point_v; constexpr bool bothFloat = inFloat && outFloat; constexpr bool noneFloat = !inFloat && !outFloat; constexpr Out outMax = std::numeric_limits::max(); constexpr Out outMin = std::numeric_limits::lowest(); // 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>; if constexpr (bothFloat) { return select_widest(outMin) <= aIn && aIn <= select_widest(outMax); } // Normal casting applies, the floating point number is floored. else 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(outMin) || aIn > static_cast(outMax)) { return false; } // 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 constexpr (outSigned) { int64_t asInteger = static_cast(aIn); return outMin <= asInteger && asInteger <= outMax; } else { uint64_t asInteger = static_cast(aIn); return asInteger <= outMax; } } // Checks if the integer is representable exactly as a floating point value of // a specific width. else if constexpr (!inFloat && outFloat) { if constexpr (inSigned) { return -safe_integer() <= aIn && aIn <= safe_integer(); } else { return aIn < safe_integer_unsigned(); } } else if constexpr (noneFloat) { if constexpr (bothUnsigned) { return aIn <= select_widest(outMax); } else if constexpr (bothSigned) { return select_widest(outMin) <= aIn && aIn <= select_widest(outMax); } else if constexpr (inSigned && !outSigned) { return aIn >= 0 && std::make_unsigned_t(aIn) <= outMax; } else if constexpr (!inSigned && outSigned) { return aIn <= select_widest(outMax); } } } } // namespace detail /** * 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 cannot be represented exactly as a * floating point value, because it's too large. */ template inline To AssertedCast(const From aFrom) { static_assert(std::is_arithmetic_v && std::is_arithmetic_v); #ifdef DEBUG if (!detail::IsInBounds(aFrom)) { char buf[1024]; detail::DiagnosticMessage(aFrom, buf); fprintf(stderr, "AssertedCast error: %s\n", buf); MOZ_CRASH(); } #endif return static_cast(aFrom); } /** * 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 inline To ReleaseAssertedCast(const From aFrom) { static_assert(std::is_arithmetic_v && std::is_arithmetic_v); MOZ_RELEASE_ASSERT((detail::IsInBounds(aFrom))); return static_cast(aFrom); } /** * Cast from type From to type To, clamping to minimum and maximum value of the * destination type if needed. */ template inline To SaturatingCast(const From aFrom) { static_assert(std::is_arithmetic_v && std::is_arithmetic_v); // This implementation works up to 64-bits integers. static_assert(sizeof(From) <= 8 && sizeof(To) <= 8); constexpr bool fromFloat = std::is_floating_point_v; constexpr bool toFloat = std::is_floating_point_v; // It's not clear what the caller wants here, it could be round, truncate, // closest value, etc. static_assert((fromFloat && !toFloat) || (!fromFloat && !toFloat), "Handle manually depending on desired behaviour"); // If the source is floating point and the destination isn't, it can be that // casting changes the value unexpectedly. Casting to double and clamping to // the max of the destination type is correct, this also handles infinity. if constexpr (fromFloat) { if (aFrom > static_cast(std::numeric_limits::max())) { return std::numeric_limits::max(); } if (aFrom < static_cast(std::numeric_limits::lowest())) { return std::numeric_limits::lowest(); } return static_cast(aFrom); } // Source and destination are of opposite signedness if constexpr (std::is_signed_v != std::is_signed_v) { // Input is negative, output is unsigned, return 0 if (std::is_signed_v && aFrom < 0) { return 0; } // At this point the input is positive, cast everything to uint64_t for // simplicity and compare uint64_t inflated = AssertedCast(aFrom); if (inflated > static_cast(std::numeric_limits::max())) { return std::numeric_limits::max(); } return static_cast(aFrom); } else { // Regular case: clamp to destination type range if (aFrom > std::numeric_limits::max()) { return std::numeric_limits::max(); } if (aFrom < std::numeric_limits::lowest()) { return std::numeric_limits::lowest(); } return static_cast(aFrom); } } namespace detail { template class LazyAssertedCastT final { const From mVal; public: explicit LazyAssertedCastT(const From val) : mVal(val) {} template operator To() const { return AssertedCast(mVal); } }; } // namespace detail /** * Like AssertedCast, but infers |To| for AssertedCast lazily based on usage. * > uint8_t foo = LazyAssertedCast(1000); // boom */ template inline auto LazyAssertedCast(const From val) { return detail::LazyAssertedCastT(val); } } // namespace mozilla #endif /* mozilla_Casting_h */