Casting.h (11763B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Cast operations to supplement the built-in casting operations. */ 8 9 #ifndef mozilla_Casting_h 10 #define mozilla_Casting_h 11 12 #include "mozilla/Assertions.h" 13 14 #include <cinttypes> 15 #include <cmath> 16 #include <limits> 17 #include <type_traits> 18 19 #ifndef __clang__ 20 # include <cstring> 21 #endif 22 23 #ifdef DEBUG 24 # include "fmt/format.h" 25 # include <cstdio> 26 #endif 27 28 namespace mozilla { 29 30 /** 31 * Sets the outparam value of type |To| with the same underlying bit pattern of 32 * |aFrom|. 33 * 34 * |To| and |From| must be types of the same size; be careful of cross-platform 35 * size differences, or this might fail to compile on some but not all 36 * platforms. 37 * 38 * There is also a variant that returns the value directly. In most cases, the 39 * two variants should be identical. However, in the specific case of x86 40 * chips, the behavior differs: returning floating-point values directly is done 41 * through the x87 stack, and x87 loads and stores turn signaling NaNs into 42 * quiet NaNs... silently. Returning floating-point values via outparam, 43 * however, is done entirely within the SSE registers when SSE2 floating-point 44 * is enabled in the compiler, which has semantics-preserving behavior you would 45 * expect. 46 * 47 * If preserving the distinction between signaling NaNs and quiet NaNs is 48 * important to you, you should use the outparam version. In all other cases, 49 * you should use the direct return version. 50 */ 51 template <typename To, typename From> 52 inline void BitwiseCast(const From aFrom, To* aResult) { 53 // FIXME: use std::bit_cast once we move to C++20 54 #if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 11) 55 *aResult = __builtin_bit_cast(To, aFrom); 56 #else 57 static_assert(sizeof(From) == sizeof(To), 58 "To and From must have the same size"); 59 60 // We could maybe downgrade these to std::is_trivially_copyable, but the 61 // various STLs we use don't all provide it. 62 static_assert(std::is_trivial<From>::value, 63 "shouldn't bitwise-copy a type having non-trivial " 64 "initialization"); 65 static_assert(std::is_trivial<To>::value, 66 "shouldn't bitwise-copy a type having non-trivial " 67 "initialization"); 68 std::memcpy(static_cast<void*>(aResult), static_cast<const void*>(&aFrom), 69 sizeof(From)); 70 #endif 71 } 72 73 template <typename To, typename From> 74 inline To BitwiseCast(const From aFrom) { 75 To temp; 76 BitwiseCast<To, From>(aFrom, &temp); 77 return temp; 78 } 79 80 namespace detail { 81 82 template <typename T> 83 constexpr int64_t safe_integer() { 84 static_assert(std::is_floating_point_v<T>); 85 return std::pow(2, std::numeric_limits<T>::digits); 86 } 87 88 template <typename T> 89 constexpr uint64_t safe_integer_unsigned() { 90 static_assert(std::is_floating_point_v<T>); 91 return std::pow(2, std::numeric_limits<T>::digits); 92 } 93 94 template <typename T> 95 const char* TypeToStringFallback(); 96 97 template <typename T> 98 inline constexpr const char* TypeToString() { 99 return TypeToStringFallback<T>(); 100 } 101 102 #define T2S(type) \ 103 template <> \ 104 inline constexpr const char* TypeToString<type>() { \ 105 return #type; \ 106 } 107 108 #define T2SF(type) \ 109 template <> \ 110 inline constexpr const char* TypeToStringFallback<type>() { \ 111 return #type; \ 112 } 113 114 T2S(uint8_t); 115 T2S(uint16_t); 116 T2S(uint32_t); 117 T2S(uint64_t); 118 T2S(int8_t); 119 T2S(int16_t); 120 T2S(int32_t); 121 T2S(int64_t); 122 T2S(char16_t); 123 T2S(char32_t); 124 T2SF(int); 125 T2SF(unsigned int); 126 T2SF(long); 127 T2SF(unsigned long); 128 T2S(float); 129 T2S(double); 130 131 #undef T2S 132 #undef T2SF 133 134 #ifdef DEBUG 135 template <typename In, typename Out> 136 inline void DiagnosticMessage(In aIn, char aDiagnostic[1024]) { 137 if constexpr (std::is_same_v<In, char> || std::is_same_v<In, wchar_t> || 138 std::is_same_v<In, char16_t> || std::is_same_v<In, char32_t>) { 139 static_assert(sizeof(wchar_t) <= sizeof(int32_t)); 140 // Characters types are printed in hexadecimal for two reasons: 141 // - to easily debug problems with non-printable characters. 142 // - {fmt} refuses to format a string with mixed character type. 143 // It's always possible to cast them to int64_t for lossless printing of the 144 // value. 145 auto [out, size] = fmt::format_to_n( 146 aDiagnostic, 1023, 147 FMT_STRING("Cannot cast {:x} from {} to {}: out of range"), 148 static_cast<int64_t>(aIn), TypeToString<In>(), TypeToString<Out>()); 149 *out = 0; 150 } else { 151 auto [out, size] = fmt::format_to_n( 152 aDiagnostic, 1023, 153 FMT_STRING("Cannot cast {} from {} to {}: out of range"), aIn, 154 TypeToString<In>(), TypeToString<Out>()); 155 *out = 0; 156 } 157 } 158 #endif 159 160 template <typename In, typename Out> 161 bool IsInBounds(In aIn) { 162 constexpr bool inSigned = std::is_signed_v<In>; 163 constexpr bool outSigned = std::is_signed_v<Out>; 164 constexpr bool bothSigned = inSigned && outSigned; 165 constexpr bool bothUnsigned = !inSigned && !outSigned; 166 constexpr bool inFloat = std::is_floating_point_v<In>; 167 constexpr bool outFloat = std::is_floating_point_v<Out>; 168 constexpr bool bothFloat = inFloat && outFloat; 169 constexpr bool noneFloat = !inFloat && !outFloat; 170 constexpr Out outMax = std::numeric_limits<Out>::max(); 171 constexpr Out outMin = std::numeric_limits<Out>::lowest(); 172 173 // This selects the widest of two types, and is used to cast throughout. 174 using select_widest = std::conditional_t<(sizeof(In) > sizeof(Out)), In, Out>; 175 176 if constexpr (bothFloat) { 177 return select_widest(outMin) <= aIn && aIn <= select_widest(outMax); 178 } 179 // Normal casting applies, the floating point number is floored. 180 else if constexpr (inFloat && !outFloat) { 181 static_assert(sizeof(aIn) <= sizeof(int64_t)); 182 // Check if the input floating point is larger than the output bounds. This 183 // catches situations where the input is a float larger than the max of the 184 // output type. 185 if (aIn < static_cast<double>(outMin) || 186 aIn > static_cast<double>(outMax)) { 187 return false; 188 } 189 // At this point we know that the input can be converted to an integer. 190 // Check if it's larger than the bounds of the target integer. 191 if constexpr (outSigned) { 192 int64_t asInteger = static_cast<int64_t>(aIn); 193 return outMin <= asInteger && asInteger <= outMax; 194 } else { 195 uint64_t asInteger = static_cast<uint64_t>(aIn); 196 return asInteger <= outMax; 197 } 198 } 199 200 // Checks if the integer is representable exactly as a floating point value of 201 // a specific width. 202 else if constexpr (!inFloat && outFloat) { 203 if constexpr (inSigned) { 204 return -safe_integer<Out>() <= aIn && aIn <= safe_integer<Out>(); 205 } else { 206 return aIn < safe_integer_unsigned<Out>(); 207 } 208 } 209 210 else if constexpr (noneFloat) { 211 if constexpr (bothUnsigned) { 212 return aIn <= select_widest(outMax); 213 } else if constexpr (bothSigned) { 214 return select_widest(outMin) <= aIn && aIn <= select_widest(outMax); 215 } else if constexpr (inSigned && !outSigned) { 216 return aIn >= 0 && std::make_unsigned_t<In>(aIn) <= outMax; 217 } else if constexpr (!inSigned && outSigned) { 218 return aIn <= select_widest(outMax); 219 } 220 } 221 } 222 223 } // namespace detail 224 225 /** 226 * Cast a value of type |From| to a value of type |To|, asserting that the cast 227 * will be a safe cast per C++ (that is, that |to| is in the range of values 228 * permitted for the type |From|). 229 * In particular, this will fail if a integer cannot be represented exactly as a 230 * floating point value, because it's too large. 231 */ 232 template <typename To, typename From> 233 inline To AssertedCast(const From aFrom) { 234 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>); 235 #ifdef DEBUG 236 if (!detail::IsInBounds<From, To>(aFrom)) { 237 char buf[1024]; 238 detail::DiagnosticMessage<From, To>(aFrom, buf); 239 fprintf(stderr, "AssertedCast error: %s\n", buf); 240 MOZ_CRASH(); 241 } 242 #endif 243 return static_cast<To>(aFrom); 244 } 245 246 /** 247 * Cast a value of numeric type |From| to a value of numeric type |To|, release 248 * asserting that the cast will be a safe cast per C++ (that is, that |to| is in 249 * the range of values permitted for the type |From|). 250 * In particular, this will fail if a integer cannot be represented exactly as a 251 * floating point value, because it's too large. 252 */ 253 template <typename To, typename From> 254 inline To ReleaseAssertedCast(const From aFrom) { 255 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>); 256 MOZ_RELEASE_ASSERT((detail::IsInBounds<From, To>(aFrom))); 257 return static_cast<To>(aFrom); 258 } 259 260 /** 261 * Cast from type From to type To, clamping to minimum and maximum value of the 262 * destination type if needed. 263 */ 264 template <typename To, typename From> 265 inline To SaturatingCast(const From aFrom) { 266 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>); 267 // This implementation works up to 64-bits integers. 268 static_assert(sizeof(From) <= 8 && sizeof(To) <= 8); 269 constexpr bool fromFloat = std::is_floating_point_v<From>; 270 constexpr bool toFloat = std::is_floating_point_v<To>; 271 272 // It's not clear what the caller wants here, it could be round, truncate, 273 // closest value, etc. 274 static_assert((fromFloat && !toFloat) || (!fromFloat && !toFloat), 275 "Handle manually depending on desired behaviour"); 276 277 // If the source is floating point and the destination isn't, it can be that 278 // casting changes the value unexpectedly. Casting to double and clamping to 279 // the max of the destination type is correct, this also handles infinity. 280 if constexpr (fromFloat) { 281 if (aFrom > static_cast<double>(std::numeric_limits<To>::max())) { 282 return std::numeric_limits<To>::max(); 283 } 284 if (aFrom < static_cast<double>(std::numeric_limits<To>::lowest())) { 285 return std::numeric_limits<To>::lowest(); 286 } 287 return static_cast<To>(aFrom); 288 } 289 // Source and destination are of opposite signedness 290 if constexpr (std::is_signed_v<From> != std::is_signed_v<To>) { 291 // Input is negative, output is unsigned, return 0 292 if (std::is_signed_v<From> && aFrom < 0) { 293 return 0; 294 } 295 // At this point the input is positive, cast everything to uint64_t for 296 // simplicity and compare 297 uint64_t inflated = AssertedCast<uint64_t>(aFrom); 298 if (inflated > static_cast<uint64_t>(std::numeric_limits<To>::max())) { 299 return std::numeric_limits<To>::max(); 300 } 301 return static_cast<To>(aFrom); 302 } else { 303 // Regular case: clamp to destination type range 304 if (aFrom > std::numeric_limits<To>::max()) { 305 return std::numeric_limits<To>::max(); 306 } 307 if (aFrom < std::numeric_limits<To>::lowest()) { 308 return std::numeric_limits<To>::lowest(); 309 } 310 return static_cast<To>(aFrom); 311 } 312 } 313 314 namespace detail { 315 316 template <typename From> 317 class LazyAssertedCastT final { 318 const From mVal; 319 320 public: 321 explicit LazyAssertedCastT(const From val) : mVal(val) {} 322 323 template <typename To> 324 operator To() const { 325 return AssertedCast<To>(mVal); 326 } 327 }; 328 329 } // namespace detail 330 331 /** 332 * Like AssertedCast, but infers |To| for AssertedCast lazily based on usage. 333 * > uint8_t foo = LazyAssertedCast(1000); // boom 334 */ 335 template <typename From> 336 inline auto LazyAssertedCast(const From val) { 337 return detail::LazyAssertedCastT<From>(val); 338 } 339 340 } // namespace mozilla 341 342 #endif /* mozilla_Casting_h */