Backed out 3 changesets (bug 1794159) for causing bc failures on browser_browserGlue_...
[gecko.git] / mfbt / Casting.h
blob76bf8b799c22c7012f18ece16f81672348970d6d
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/. */
7 /* Cast operations to supplement the built-in casting operations. */
9 #ifndef mozilla_Casting_h
10 #define mozilla_Casting_h
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Sprintf.h"
15 #include <cinttypes>
16 #include <cstring>
17 #include <type_traits>
18 #include <limits>
19 #include <cmath>
21 namespace mozilla {
23 /**
24 * Sets the outparam value of type |To| with the same underlying bit pattern of
25 * |aFrom|.
27 * |To| and |From| must be types of the same size; be careful of cross-platform
28 * size differences, or this might fail to compile on some but not all
29 * platforms.
31 * There is also a variant that returns the value directly. In most cases, the
32 * two variants should be identical. However, in the specific case of x86
33 * chips, the behavior differs: returning floating-point values directly is done
34 * through the x87 stack, and x87 loads and stores turn signaling NaNs into
35 * quiet NaNs... silently. Returning floating-point values via outparam,
36 * however, is done entirely within the SSE registers when SSE2 floating-point
37 * is enabled in the compiler, which has semantics-preserving behavior you would
38 * expect.
40 * If preserving the distinction between signaling NaNs and quiet NaNs is
41 * important to you, you should use the outparam version. In all other cases,
42 * you should use the direct return version.
44 template <typename To, typename From>
45 inline void BitwiseCast(const From aFrom, To* aResult) {
46 static_assert(sizeof(From) == sizeof(To),
47 "To and From must have the same size");
49 // We could maybe downgrade these to std::is_trivially_copyable, but the
50 // various STLs we use don't all provide it.
51 static_assert(std::is_trivial<From>::value,
52 "shouldn't bitwise-copy a type having non-trivial "
53 "initialization");
54 static_assert(std::is_trivial<To>::value,
55 "shouldn't bitwise-copy a type having non-trivial "
56 "initialization");
58 std::memcpy(static_cast<void*>(aResult), static_cast<const void*>(&aFrom),
59 sizeof(From));
62 template <typename To, typename From>
63 inline To BitwiseCast(const From aFrom) {
64 To temp;
65 BitwiseCast<To, From>(aFrom, &temp);
66 return temp;
69 namespace detail {
71 template <typename T>
72 constexpr int64_t safe_integer() {
73 static_assert(std::is_floating_point_v<T>);
74 return std::pow(2, std::numeric_limits<T>::digits);
77 template <typename T>
78 constexpr uint64_t safe_integer_unsigned() {
79 static_assert(std::is_floating_point_v<T>);
80 return std::pow(2, std::numeric_limits<T>::digits);
83 template <typename T>
84 const char* TypeToString();
85 template <typename T>
86 const char* TypeToFormatString();
88 #define T2S(type, formatstring) \
89 template <> \
90 inline constexpr const char* TypeToString<type>() { \
91 return #type; \
92 } \
93 template <> \
94 inline constexpr const char* TypeToFormatString<type>() { \
95 return "%" formatstring; \
98 T2S(uint8_t, PRIu8);
99 T2S(uint16_t, PRIu16);
100 T2S(uint32_t, PRIu32);
101 T2S(uint64_t, PRIu64);
102 T2S(int8_t, PRId8);
103 T2S(int16_t, PRId16);
104 T2S(int32_t, PRId32);
105 T2S(int64_t, PRId64);
106 T2S(char16_t, PRIu16); // print as a number
107 T2S(char32_t, PRIu32); // print as a number
108 #if defined(XP_DARWIN) || defined(XP_WIN) || defined(__wasm__)
109 T2S(long, "ld");
110 T2S(unsigned long, "lu");
111 #endif
112 T2S(float, "f");
113 T2S(double, "lf");
115 #undef T2S
117 template <typename In, typename Out>
118 inline void DiagnosticMessage(In aIn, char aDiagnostic[1024]) {
119 char number[128];
120 SprintfBuf(number, 128, TypeToFormatString<In>(), aIn);
121 SprintfBuf(aDiagnostic, 1024, "Cannot cast %s from %s to %s: out of range",
122 number, TypeToString<In>(), TypeToString<Out>());
125 // This is working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81676,
126 // fixed in gcc-10
127 #pragma GCC diagnostic push
128 #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
129 template <typename In, typename Out>
130 bool IsInBounds(In aIn) {
131 constexpr bool inSigned = std::is_signed_v<In>;
132 constexpr bool outSigned = std::is_signed_v<Out>;
133 constexpr bool bothSigned = inSigned && outSigned;
134 constexpr bool bothUnsigned = !inSigned && !outSigned;
135 constexpr bool inFloat = std::is_floating_point_v<In>;
136 constexpr bool outFloat = std::is_floating_point_v<Out>;
137 constexpr bool bothFloat = inFloat && outFloat;
138 constexpr bool noneFloat = !inFloat && !outFloat;
139 constexpr Out outMax = std::numeric_limits<Out>::max();
140 constexpr Out outMin = std::numeric_limits<Out>::lowest();
142 // This selects the widest of two types, and is used to cast throughout.
143 using select_widest = std::conditional_t<(sizeof(In) > sizeof(Out)), In, Out>;
145 if constexpr (bothFloat) {
146 if (aIn > select_widest(outMax) || aIn < select_widest(outMin)) {
147 return false;
150 // Normal casting applies, the floating point number is floored.
151 if constexpr (inFloat && !outFloat) {
152 static_assert(sizeof(aIn) <= sizeof(int64_t));
153 // Check if the input floating point is larger than the output bounds. This
154 // catches situations where the input is a float larger than the max of the
155 // output type.
156 if (aIn < static_cast<double>(outMin) ||
157 aIn > static_cast<double>(outMax)) {
158 return false;
160 // At this point we know that the input can be converted to an integer.
161 // Check if it's larger than the bounds of the target integer.
162 if (outSigned) {
163 int64_t asInteger = static_cast<int64_t>(aIn);
164 if (asInteger < outMin || asInteger > outMax) {
165 return false;
167 } else {
168 uint64_t asInteger = static_cast<uint64_t>(aIn);
169 if (asInteger > outMax) {
170 return false;
175 // Checks if the integer is representable exactly as a floating point value of
176 // a specific width.
177 if constexpr (!inFloat && outFloat) {
178 if constexpr (inSigned) {
179 if (aIn < -safe_integer<Out>() || aIn > safe_integer<Out>()) {
180 return false;
182 } else {
183 if (aIn >= safe_integer_unsigned<Out>()) {
184 return false;
189 if constexpr (noneFloat) {
190 if constexpr (bothUnsigned) {
191 if (aIn > select_widest(outMax)) {
192 return false;
195 if constexpr (bothSigned) {
196 if (aIn > select_widest(outMax) || aIn < select_widest(outMin)) {
197 return false;
200 if constexpr (inSigned && !outSigned) {
201 if (aIn < 0 || std::make_unsigned_t<In>(aIn) > outMax) {
202 return false;
205 if constexpr (!inSigned && outSigned) {
206 if (aIn > select_widest(outMax)) {
207 return false;
211 return true;
213 #pragma GCC diagnostic pop
215 } // namespace detail
218 * Cast a value of type |From| to a value of type |To|, asserting that the cast
219 * will be a safe cast per C++ (that is, that |to| is in the range of values
220 * permitted for the type |From|).
221 * In particular, this will fail if a integer cannot be represented exactly as a
222 * floating point value, because it's too large.
224 template <typename To, typename From>
225 inline To AssertedCast(const From aFrom) {
226 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
227 #ifdef DEBUG
228 if (!detail::IsInBounds<From, To>(aFrom)) {
229 char buf[1024];
230 detail::DiagnosticMessage<From, To>(aFrom, buf);
231 fprintf(stderr, "AssertedCast error: %s\n", buf);
232 MOZ_CRASH();
234 #endif
235 return static_cast<To>(aFrom);
239 * Cast a value of numeric type |From| to a value of numeric type |To|, release
240 * asserting that the cast will be a safe cast per C++ (that is, that |to| is in
241 * the range of values permitted for the type |From|).
242 * In particular, this will fail if a integer cannot be represented exactly as a
243 * floating point value, because it's too large.
245 template <typename To, typename From>
246 inline To ReleaseAssertedCast(const From aFrom) {
247 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
248 MOZ_RELEASE_ASSERT((detail::IsInBounds<From, To>(aFrom)));
249 return static_cast<To>(aFrom);
253 * Cast from type From to type To, clamping to minimum and maximum value of the
254 * destination type if needed.
256 template <typename To, typename From>
257 inline To SaturatingCast(const From aFrom) {
258 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
259 // This implementation works up to 64-bits integers.
260 static_assert(sizeof(From) <= 8 && sizeof(To) <= 8);
261 constexpr bool fromFloat = std::is_floating_point_v<From>;
262 constexpr bool toFloat = std::is_floating_point_v<To>;
264 // It's not clear what the caller wants here, it could be round, truncate,
265 // closest value, etc.
266 static_assert((fromFloat && !toFloat) || (!fromFloat && !toFloat),
267 "Handle manually depending on desired behaviour");
269 // If the source is floating point and the destination isn't, it can be that
270 // casting changes the value unexpectedly. Casting to double and clamping to
271 // the max of the destination type is correct, this also handles infinity.
272 if constexpr (fromFloat) {
273 if (aFrom > static_cast<double>(std::numeric_limits<To>::max())) {
274 return std::numeric_limits<To>::max();
276 if (aFrom < static_cast<double>(std::numeric_limits<To>::lowest())) {
277 return std::numeric_limits<To>::lowest();
279 return static_cast<To>(aFrom);
281 // Source and destination are of opposite signedness
282 if constexpr (std::is_signed_v<From> != std::is_signed_v<To>) {
283 // Input is negative, output is unsigned, return 0
284 if (std::is_signed_v<From> && aFrom < 0) {
285 return 0;
287 // At this point the input is positive, cast everything to uint64_t for
288 // simplicity and compare
289 uint64_t inflated = AssertedCast<uint64_t>(aFrom);
290 if (inflated > static_cast<uint64_t>(std::numeric_limits<To>::max())) {
291 return std::numeric_limits<To>::max();
293 return static_cast<To>(aFrom);
294 } else {
295 // Regular case: clamp to destination type range
296 if (aFrom > std::numeric_limits<To>::max()) {
297 return std::numeric_limits<To>::max();
299 if (aFrom < std::numeric_limits<To>::lowest()) {
300 return std::numeric_limits<To>::lowest();
302 return static_cast<To>(aFrom);
306 namespace detail {
308 template <typename From>
309 class LazyAssertedCastT final {
310 const From mVal;
312 public:
313 explicit LazyAssertedCastT(const From val) : mVal(val) {}
315 template <typename To>
316 operator To() const {
317 return AssertedCast<To>(mVal);
321 } // namespace detail
324 * Like AssertedCast, but infers |To| for AssertedCast lazily based on usage.
325 * > uint8_t foo = LazyAssertedCast(1000); // boom
327 template <typename From>
328 inline auto LazyAssertedCast(const From val) {
329 return detail::LazyAssertedCastT<From>(val);
332 } // namespace mozilla
334 #endif /* mozilla_Casting_h */