xref: /aosp_15_r20/external/angle/src/common/base/anglebase/numerics/safe_conversions.h (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_NUMERICS_SAFE_CONVERSIONS_H_
6 #define BASE_NUMERICS_SAFE_CONVERSIONS_H_
7 
8 #include <stddef.h>
9 
10 #include <cmath>
11 #include <limits>
12 #include <type_traits>
13 
14 #include "anglebase/numerics/safe_conversions_impl.h"
15 
16 #if defined(__ARMEL__) && !defined(__native_client__)
17 #    include "anglebase/numerics/safe_conversions_arm_impl.h"
18 #    define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
19 #else
20 #    define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
21 #endif
22 
23 #if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
24 #    include <ostream>
25 #endif
26 
27 namespace angle
28 {
29 namespace base
30 {
31 namespace internal
32 {
33 
34 #if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
35 template <typename Dst, typename Src>
36 struct SaturateFastAsmOp
37 {
38     static constexpr bool is_supported = false;
DoSaturateFastAsmOp39     static constexpr Dst Do(Src)
40     {
41         // Force a compile failure if instantiated.
42         return CheckOnFailure::template HandleFailure<Dst>();
43     }
44 };
45 #endif  // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
46 #undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
47 
48 // The following special case a few specific integer conversions where we can
49 // eke out better performance than range checking.
50 template <typename Dst, typename Src, typename Enable = void>
51 struct IsValueInRangeFastOp
52 {
53     static constexpr bool is_supported = false;
DoIsValueInRangeFastOp54     static constexpr bool Do(Src value)
55     {
56         // Force a compile failure if instantiated.
57         return CheckOnFailure::template HandleFailure<bool>();
58     }
59 };
60 
61 // Signed to signed range comparison.
62 template <typename Dst, typename Src>
63 struct IsValueInRangeFastOp<
64     Dst,
65     Src,
66     typename std::enable_if<std::is_integral<Dst>::value && std::is_integral<Src>::value &&
67                             std::is_signed<Dst>::value && std::is_signed<Src>::value &&
68                             !IsTypeInRangeForNumericType<Dst, Src>::value>::type>
69 {
70     static constexpr bool is_supported = true;
71 
72     static constexpr bool Do(Src value)
73     {
74         // Just downcast to the smaller type, sign extend it back to the original
75         // type, and then see if it matches the original value.
76         return value == static_cast<Dst>(value);
77     }
78 };
79 
80 // Signed to unsigned range comparison.
81 template <typename Dst, typename Src>
82 struct IsValueInRangeFastOp<
83     Dst,
84     Src,
85     typename std::enable_if<std::is_integral<Dst>::value && std::is_integral<Src>::value &&
86                             !std::is_signed<Dst>::value && std::is_signed<Src>::value &&
87                             !IsTypeInRangeForNumericType<Dst, Src>::value>::type>
88 {
89     static constexpr bool is_supported = true;
90 
91     static constexpr bool Do(Src value)
92     {
93         // We cast a signed as unsigned to overflow negative values to the top,
94         // then compare against whichever maximum is smaller, as our upper bound.
95         return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
96     }
97 };
98 
99 // Convenience function that returns true if the supplied value is in range
100 // for the destination type.
101 template <typename Dst, typename Src>
102 constexpr bool IsValueInRangeForNumericType(Src value)
103 {
104     using SrcType = typename internal::UnderlyingType<Src>::type;
105     return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
106                ? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
107                : internal::DstRangeRelationToSrcRange<Dst>(static_cast<SrcType>(value)).IsValid();
108 }
109 
110 // checked_cast<> is analogous to static_cast<> for numeric types,
111 // except that it CHECKs that the specified numeric conversion will not
112 // overflow or underflow. NaN source will always trigger a CHECK.
113 template <typename Dst, class CheckHandler = internal::CheckOnFailure, typename Src>
114 constexpr Dst checked_cast(Src value)
115 {
116     // This throws a compile-time error on evaluating the constexpr if it can be
117     // determined at compile-time as failing, otherwise it will CHECK at runtime.
118     using SrcType = typename internal::UnderlyingType<Src>::type;
119     return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
120                ? static_cast<Dst>(static_cast<SrcType>(value))
121                : CheckHandler::template HandleFailure<Dst>();
122 }
123 
124 // Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
125 // You may provide your own limits (e.g. to saturated_cast) so long as you
126 // implement all of the static constexpr member functions in the class below.
127 template <typename T>
128 struct SaturationDefaultLimits : public std::numeric_limits<T>
129 {
130     static constexpr T NaN()
131     {
132         return std::numeric_limits<T>::has_quiet_NaN ? std::numeric_limits<T>::quiet_NaN() : T();
133     }
134     using std::numeric_limits<T>::max;
135     static constexpr T Overflow()
136     {
137         return std::numeric_limits<T>::has_infinity ? std::numeric_limits<T>::infinity()
138                                                     : std::numeric_limits<T>::max();
139     }
140     using std::numeric_limits<T>::lowest;
141     static constexpr T Underflow()
142     {
143         return std::numeric_limits<T>::has_infinity ? std::numeric_limits<T>::infinity() * -1
144                                                     : std::numeric_limits<T>::lowest();
145     }
146 };
147 
148 template <typename Dst, template <typename> class S, typename Src>
149 constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint)
150 {
151     // For some reason clang generates much better code when the branch is
152     // structured exactly this way, rather than a sequence of checks.
153     return !constraint.IsOverflowFlagSet()
154                ? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value) : S<Dst>::Underflow())
155                // Skip this check for integral Src, which cannot be NaN.
156                : (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
157                       ? S<Dst>::Overflow()
158                       : S<Dst>::NaN());
159 }
160 
161 // We can reduce the number of conditions and get slightly better performance
162 // for normal signed and unsigned integer ranges. And in the specific case of
163 // Arm, we can use the optimized saturation instructions.
164 template <typename Dst, typename Src, typename Enable = void>
165 struct SaturateFastOp
166 {
167     static constexpr bool is_supported = false;
168     static constexpr Dst Do(Src value)
169     {
170         // Force a compile failure if instantiated.
171         return CheckOnFailure::template HandleFailure<Dst>();
172     }
173 };
174 
175 template <typename Dst, typename Src>
176 struct SaturateFastOp<
177     Dst,
178     Src,
179     typename std::enable_if<std::is_integral<Src>::value && std::is_integral<Dst>::value &&
180                             SaturateFastAsmOp<Dst, Src>::is_supported>::type>
181 {
182     static constexpr bool is_supported = true;
183     static constexpr Dst Do(Src value) { return SaturateFastAsmOp<Dst, Src>::Do(value); }
184 };
185 
186 template <typename Dst, typename Src>
187 struct SaturateFastOp<
188     Dst,
189     Src,
190     typename std::enable_if<std::is_integral<Src>::value && std::is_integral<Dst>::value &&
191                             !SaturateFastAsmOp<Dst, Src>::is_supported>::type>
192 {
193     static constexpr bool is_supported = true;
194     static constexpr Dst Do(Src value)
195     {
196         // The exact order of the following is structured to hit the correct
197         // optimization heuristics across compilers. Do not change without
198         // checking the emitted code.
199         const Dst saturated = CommonMaxOrMin<Dst, Src>(
200             IsMaxInRangeForNumericType<Dst, Src>() ||
201             (!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
202         return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
203                    ? static_cast<Dst>(value)
204                    : saturated;
205     }
206 };
207 
208 // saturated_cast<> is analogous to static_cast<> for numeric types, except
209 // that the specified numeric conversion will saturate by default rather than
210 // overflow or underflow, and NaN assignment to an integral will return 0.
211 // All boundary condition behaviors can be overriden with a custom handler.
212 template <typename Dst,
213           template <typename> class SaturationHandler = SaturationDefaultLimits,
214           typename Src>
215 constexpr Dst saturated_cast(Src value)
216 {
217     using SrcType = typename UnderlyingType<Src>::type;
218     return !IsCompileTimeConstant(value) && SaturateFastOp<Dst, SrcType>::is_supported &&
219                    std::is_same<SaturationHandler<Dst>, SaturationDefaultLimits<Dst>>::value
220                ? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
221                : saturated_cast_impl<Dst, SaturationHandler, SrcType>(
222                      static_cast<SrcType>(value),
223                      DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
224                          static_cast<SrcType>(value)));
225 }
226 
227 // strict_cast<> is analogous to static_cast<> for numeric types, except that
228 // it will cause a compile failure if the destination type is not large enough
229 // to contain any value in the source type. It performs no runtime checking.
230 template <typename Dst, typename Src>
231 constexpr Dst strict_cast(Src value)
232 {
233     using SrcType = typename UnderlyingType<Src>::type;
234     static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
235     static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
236 
237     // If you got here from a compiler error, it's because you tried to assign
238     // from a source type to a destination type that has insufficient range.
239     // The solution may be to change the destination type you're assigning to,
240     // and use one large enough to represent the source.
241     // Alternatively, you may be better served with the checked_cast<> or
242     // saturated_cast<> template functions for your particular use case.
243     static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value == NUMERIC_RANGE_CONTAINED,
244                   "The source type is out of range for the destination type. "
245                   "Please see strict_cast<> comments for more information.");
246 
247     return static_cast<Dst>(static_cast<SrcType>(value));
248 }
249 
250 // Some wrappers to statically check that a type is in range.
251 template <typename Dst, typename Src, class Enable = void>
252 struct IsNumericRangeContained
253 {
254     static constexpr bool value = false;
255 };
256 
257 template <typename Dst, typename Src>
258 struct IsNumericRangeContained<
259     Dst,
260     Src,
261     typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
262                             ArithmeticOrUnderlyingEnum<Src>::value>::type>
263 {
264     static constexpr bool value =
265         StaticDstRangeRelationToSrcRange<Dst, Src>::value == NUMERIC_RANGE_CONTAINED;
266 };
267 
268 // StrictNumeric implements compile time range checking between numeric types by
269 // wrapping assignment operations in a strict_cast. This class is intended to be
270 // used for function arguments and return types, to ensure the destination type
271 // can always contain the source type. This is essentially the same as enforcing
272 // -Wconversion in gcc and C4302 warnings on MSVC, but it can be applied
273 // incrementally at API boundaries, making it easier to convert code so that it
274 // compiles cleanly with truncation warnings enabled.
275 // This template should introduce no runtime overhead, but it also provides no
276 // runtime checking of any of the associated mathematical operations. Use
277 // CheckedNumeric for runtime range checks of the actual value being assigned.
278 template <typename T>
279 class StrictNumeric
280 {
281   public:
282     using type = T;
283 
284     constexpr StrictNumeric() : value_(0) {}
285 
286     // Copy constructor.
287     template <typename Src>
288     constexpr StrictNumeric(const StrictNumeric<Src> &rhs) : value_(strict_cast<T>(rhs.value_))
289     {}
290 
291     // This is not an explicit constructor because we implicitly upgrade regular
292     // numerics to StrictNumerics to make them easier to use.
293     template <typename Src>
294     constexpr StrictNumeric(Src value)  // NOLINT(runtime/explicit)
295         : value_(strict_cast<T>(value))
296     {}
297 
298     // If you got here from a compiler error, it's because you tried to assign
299     // from a source type to a destination type that has insufficient range.
300     // The solution may be to change the destination type you're assigning to,
301     // and use one large enough to represent the source.
302     // If you're assigning from a CheckedNumeric<> class, you may be able to use
303     // the AssignIfValid() member function, specify a narrower destination type to
304     // the member value functions (e.g. val.template ValueOrDie<Dst>()), use one
305     // of the value helper functions (e.g. ValueOrDieForType<Dst>(val)).
306     // If you've encountered an _ambiguous overload_ you can use a static_cast<>
307     // to explicitly cast the result to the destination type.
308     // If none of that works, you may be better served with the checked_cast<> or
309     // saturated_cast<> template functions for your particular use case.
310     template <typename Dst,
311               typename std::enable_if<IsNumericRangeContained<Dst, T>::value>::type * = nullptr>
312     constexpr operator Dst() const
313     {
314         return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
315     }
316 
317   private:
318     const T value_;
319 };
320 
321 // Convience wrapper returns a StrictNumeric from the provided arithmetic type.
322 template <typename T>
323 constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(const T value)
324 {
325     return value;
326 }
327 
328 #if !BASE_NUMERICS_DISABLE_OSTREAM_OPERATORS
329 // Overload the ostream output operator to make logging work nicely.
330 template <typename T>
331 std::ostream &operator<<(std::ostream &os, const StrictNumeric<T> &value)
332 {
333     os << static_cast<T>(value);
334     return os;
335 }
336 #endif
337 
338 #define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP)                                     \
339     template <typename L, typename R,                                                          \
340               typename std::enable_if<internal::Is##CLASS##Op<L, R>::value>::type * = nullptr> \
341     constexpr bool operator OP(const L lhs, const R rhs)                                       \
342     {                                                                                          \
343         return SafeCompare<NAME, typename UnderlyingType<L>::type,                             \
344                            typename UnderlyingType<R>::type>(lhs, rhs);                        \
345     }
346 
347 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
348 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=)
349 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >)
350 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=)
351 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==)
352 BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=)
353 
354 }  // namespace internal
355 
356 using internal::as_signed;
357 using internal::as_unsigned;
358 using internal::checked_cast;
359 using internal::IsTypeInRangeForNumericType;
360 using internal::IsValueInRangeForNumericType;
361 using internal::IsValueNegative;
362 using internal::MakeStrictNum;
363 using internal::SafeUnsignedAbs;
364 using internal::saturated_cast;
365 using internal::strict_cast;
366 using internal::StrictNumeric;
367 
368 // Explicitly make a shorter size_t alias for convenience.
369 using SizeT = StrictNumeric<size_t>;
370 
371 // floating -> integral conversions that saturate and thus can actually return
372 // an integral type.  In most cases, these should be preferred over the std::
373 // versions.
374 template <
375     typename Dst = int,
376     typename Src,
377     typename = std::enable_if_t<std::is_integral<Dst>::value && std::is_floating_point<Src>::value>>
378 Dst ClampFloor(Src value)
379 {
380     return saturated_cast<Dst>(std::floor(value));
381 }
382 template <
383     typename Dst = int,
384     typename Src,
385     typename = std::enable_if_t<std::is_integral<Dst>::value && std::is_floating_point<Src>::value>>
386 Dst ClampCeil(Src value)
387 {
388     return saturated_cast<Dst>(std::ceil(value));
389 }
390 template <
391     typename Dst = int,
392     typename Src,
393     typename = std::enable_if_t<std::is_integral<Dst>::value && std::is_floating_point<Src>::value>>
394 Dst ClampRound(Src value)
395 {
396     const Src rounded = (value >= 0.0f) ? std::floor(value + 0.5f) : std::ceil(value - 0.5f);
397     return saturated_cast<Dst>(rounded);
398 }
399 
400 }  // namespace base
401 }  // namespace angle
402 
403 #endif  // BASE_NUMERICS_SAFE_CONVERSIONS_H_
404