1 // Copyright 2017 The Chromium Authors
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 PARTITION_ALLOC_PARTITION_ALLOC_BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
6 #define PARTITION_ALLOC_PARTITION_ALLOC_BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
7 
8 #include <climits>
9 #include <cmath>
10 #include <cstddef>
11 #include <cstdint>
12 #include <cstdlib>
13 #include <limits>
14 #include <type_traits>
15 
16 #include "partition_alloc/partition_alloc_base/numerics/checked_math.h"
17 #include "partition_alloc/partition_alloc_base/numerics/safe_conversions.h"
18 #include "partition_alloc/partition_alloc_base/numerics/safe_math_shared_impl.h"
19 
20 namespace partition_alloc::internal::base::internal {
21 
22 template <typename T,
23           typename std::enable_if<std::is_integral_v<T> &&
24                                   std::is_signed_v<T>>::type* = nullptr>
SaturatedNegWrapper(T value)25 constexpr T SaturatedNegWrapper(T value) {
26   return PA_IsConstantEvaluated() || !ClampedNegFastOp<T>::is_supported
27              ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
28                     ? NegateWrapper(value)
29                     : std::numeric_limits<T>::max())
30              : ClampedNegFastOp<T>::Do(value);
31 }
32 
33 template <typename T,
34           typename std::enable_if<std::is_integral_v<T> &&
35                                   !std::is_signed_v<T>>::type* = nullptr>
SaturatedNegWrapper(T value)36 constexpr T SaturatedNegWrapper(T value) {
37   return T(0);
38 }
39 
40 template <typename T,
41           typename std::enable_if<std::is_floating_point_v<T>>::type* = nullptr>
SaturatedNegWrapper(T value)42 constexpr T SaturatedNegWrapper(T value) {
43   return -value;
44 }
45 
46 template <typename T,
47           typename std::enable_if<std::is_integral_v<T>>::type* = nullptr>
SaturatedAbsWrapper(T value)48 constexpr T SaturatedAbsWrapper(T value) {
49   // The calculation below is a static identity for unsigned types, but for
50   // signed integer types it provides a non-branching, saturated absolute value.
51   // This works because SafeUnsignedAbs() returns an unsigned type, which can
52   // represent the absolute value of all negative numbers of an equal-width
53   // integer type. The call to IsValueNegative() then detects overflow in the
54   // special case of numeric_limits<T>::min(), by evaluating the bit pattern as
55   // a signed integer value. If it is the overflow case, we end up subtracting
56   // one from the unsigned result, thus saturating to numeric_limits<T>::max().
57   return static_cast<T>(
58       SafeUnsignedAbs(value) -
59       IsValueNegative<T>(static_cast<T>(SafeUnsignedAbs(value))));
60 }
61 
62 template <typename T,
63           typename std::enable_if<std::is_floating_point_v<T>>::type* = nullptr>
SaturatedAbsWrapper(T value)64 constexpr T SaturatedAbsWrapper(T value) {
65   return value < 0 ? -value : value;
66 }
67 
68 template <typename T, typename U, class Enable = void>
69 struct ClampedAddOp {};
70 
71 template <typename T, typename U>
72 struct ClampedAddOp<T,
73                     U,
74                     typename std::enable_if<std::is_integral_v<T> &&
75                                             std::is_integral_v<U>>::type> {
76   using result_type = typename MaxExponentPromotion<T, U>::type;
77   template <typename V = result_type>
78   static constexpr V Do(T x, U y) {
79     if (!PA_IsConstantEvaluated() && ClampedAddFastOp<T, U>::is_supported) {
80       return ClampedAddFastOp<T, U>::template Do<V>(x, y);
81     }
82 
83     static_assert(std::is_same_v<V, result_type> ||
84                       IsTypeInRangeForNumericType<U, V>::value,
85                   "The saturation result cannot be determined from the "
86                   "provided types.");
87     const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
88     V result = {};
89     return PA_BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
90                ? result
91                : saturated;
92   }
93 };
94 
95 template <typename T, typename U, class Enable = void>
96 struct ClampedSubOp {};
97 
98 template <typename T, typename U>
99 struct ClampedSubOp<T,
100                     U,
101                     typename std::enable_if<std::is_integral_v<T> &&
102                                             std::is_integral_v<U>>::type> {
103   using result_type = typename MaxExponentPromotion<T, U>::type;
104   template <typename V = result_type>
105   static constexpr V Do(T x, U y) {
106     if (!PA_IsConstantEvaluated() && ClampedSubFastOp<T, U>::is_supported) {
107       return ClampedSubFastOp<T, U>::template Do<V>(x, y);
108     }
109 
110     static_assert(std::is_same_v<V, result_type> ||
111                       IsTypeInRangeForNumericType<U, V>::value,
112                   "The saturation result cannot be determined from the "
113                   "provided types.");
114     const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
115     V result = {};
116     return PA_BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
117                ? result
118                : saturated;
119   }
120 };
121 
122 template <typename T, typename U, class Enable = void>
123 struct ClampedMulOp {};
124 
125 template <typename T, typename U>
126 struct ClampedMulOp<T,
127                     U,
128                     typename std::enable_if<std::is_integral_v<T> &&
129                                             std::is_integral_v<U>>::type> {
130   using result_type = typename MaxExponentPromotion<T, U>::type;
131   template <typename V = result_type>
132   static constexpr V Do(T x, U y) {
133     if (!PA_IsConstantEvaluated() && ClampedMulFastOp<T, U>::is_supported) {
134       return ClampedMulFastOp<T, U>::template Do<V>(x, y);
135     }
136 
137     V result = {};
138     const V saturated =
139         CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
140     return PA_BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
141                ? result
142                : saturated;
143   }
144 };
145 
146 template <typename T, typename U, class Enable = void>
147 struct ClampedDivOp {};
148 
149 template <typename T, typename U>
150 struct ClampedDivOp<T,
151                     U,
152                     typename std::enable_if<std::is_integral_v<T> &&
153                                             std::is_integral_v<U>>::type> {
154   using result_type = typename MaxExponentPromotion<T, U>::type;
155   template <typename V = result_type>
156   static constexpr V Do(T x, U y) {
157     V result = {};
158     if (PA_BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result)))) {
159       return result;
160     }
161     // Saturation goes to max, min, or NaN (if x is zero).
162     return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
163              : SaturationDefaultLimits<V>::NaN();
164   }
165 };
166 
167 template <typename T, typename U, class Enable = void>
168 struct ClampedModOp {};
169 
170 template <typename T, typename U>
171 struct ClampedModOp<T,
172                     U,
173                     typename std::enable_if<std::is_integral_v<T> &&
174                                             std::is_integral_v<U>>::type> {
175   using result_type = typename MaxExponentPromotion<T, U>::type;
176   template <typename V = result_type>
177   static constexpr V Do(T x, U y) {
178     V result = {};
179     return PA_BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
180                ? result
181                : x;
182   }
183 };
184 
185 template <typename T, typename U, class Enable = void>
186 struct ClampedLshOp {};
187 
188 // Left shift. Non-zero values saturate in the direction of the sign. A zero
189 // shifted by any value always results in zero.
190 template <typename T, typename U>
191 struct ClampedLshOp<T,
192                     U,
193                     typename std::enable_if<std::is_integral_v<T> &&
194                                             std::is_integral_v<U>>::type> {
195   using result_type = T;
196   template <typename V = result_type>
197   static constexpr V Do(T x, U shift) {
198     static_assert(!std::is_signed_v<U>, "Shift value must be unsigned.");
199     if (PA_BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
200       // Shift as unsigned to avoid undefined behavior.
201       V result = static_cast<V>(as_unsigned(x) << shift);
202       // If the shift can be reversed, we know it was valid.
203       if (PA_BASE_NUMERICS_LIKELY(result >> shift == x)) {
204         return result;
205       }
206     }
207     return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
208   }
209 };
210 
211 template <typename T, typename U, class Enable = void>
212 struct ClampedRshOp {};
213 
214 // Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
215 template <typename T, typename U>
216 struct ClampedRshOp<T,
217                     U,
218                     typename std::enable_if<std::is_integral_v<T> &&
219                                             std::is_integral_v<U>>::type> {
220   using result_type = T;
221   template <typename V = result_type>
222   static constexpr V Do(T x, U shift) {
223     static_assert(!std::is_signed_v<U>, "Shift value must be unsigned.");
224     // Signed right shift is odd, because it saturates to -1 or 0.
225     const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
226     return PA_BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
227                ? saturated_cast<V>(x >> shift)
228                : saturated;
229   }
230 };
231 
232 template <typename T, typename U, class Enable = void>
233 struct ClampedAndOp {};
234 
235 template <typename T, typename U>
236 struct ClampedAndOp<T,
237                     U,
238                     typename std::enable_if<std::is_integral_v<T> &&
239                                             std::is_integral_v<U>>::type> {
240   using result_type = typename std::make_unsigned<
241       typename MaxExponentPromotion<T, U>::type>::type;
242   template <typename V>
243   static constexpr V Do(T x, U y) {
244     return static_cast<result_type>(x) & static_cast<result_type>(y);
245   }
246 };
247 
248 template <typename T, typename U, class Enable = void>
249 struct ClampedOrOp {};
250 
251 // For simplicity we promote to unsigned integers.
252 template <typename T, typename U>
253 struct ClampedOrOp<T,
254                    U,
255                    typename std::enable_if<std::is_integral_v<T> &&
256                                            std::is_integral_v<U>>::type> {
257   using result_type = typename std::make_unsigned<
258       typename MaxExponentPromotion<T, U>::type>::type;
259   template <typename V>
260   static constexpr V Do(T x, U y) {
261     return static_cast<result_type>(x) | static_cast<result_type>(y);
262   }
263 };
264 
265 template <typename T, typename U, class Enable = void>
266 struct ClampedXorOp {};
267 
268 // For simplicity we support only unsigned integers.
269 template <typename T, typename U>
270 struct ClampedXorOp<T,
271                     U,
272                     typename std::enable_if<std::is_integral_v<T> &&
273                                             std::is_integral_v<U>>::type> {
274   using result_type = typename std::make_unsigned<
275       typename MaxExponentPromotion<T, U>::type>::type;
276   template <typename V>
277   static constexpr V Do(T x, U y) {
278     return static_cast<result_type>(x) ^ static_cast<result_type>(y);
279   }
280 };
281 
282 template <typename T, typename U, class Enable = void>
283 struct ClampedMaxOp {};
284 
285 template <typename T, typename U>
286 struct ClampedMaxOp<T,
287                     U,
288                     typename std::enable_if<std::is_arithmetic_v<T> &&
289                                             std::is_arithmetic_v<U>>::type> {
290   using result_type = typename MaxExponentPromotion<T, U>::type;
291   template <typename V = result_type>
292   static constexpr V Do(T x, U y) {
293     return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
294                                        : saturated_cast<V>(y);
295   }
296 };
297 
298 template <typename T, typename U, class Enable = void>
299 struct ClampedMinOp {};
300 
301 template <typename T, typename U>
302 struct ClampedMinOp<T,
303                     U,
304                     typename std::enable_if<std::is_arithmetic_v<T> &&
305                                             std::is_arithmetic_v<U>>::type> {
306   using result_type = typename LowestValuePromotion<T, U>::type;
307   template <typename V = result_type>
308   static constexpr V Do(T x, U y) {
309     return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
310                                     : saturated_cast<V>(y);
311   }
312 };
313 
314 // This is just boilerplate that wraps the standard floating point arithmetic.
315 // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
316 #define PA_BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)                      \
317   template <typename T, typename U>                                 \
318   struct Clamped##NAME##Op<                                         \
319       T, U,                                                         \
320       typename std::enable_if<std::is_floating_point_v<T> ||        \
321                               std::is_floating_point_v<U>>::type> { \
322     using result_type = typename MaxExponentPromotion<T, U>::type;  \
323     template <typename V = result_type>                             \
324     static constexpr V Do(T x, U y) {                               \
325       return saturated_cast<V>(x OP y);                             \
326     }                                                               \
327   };
328 
329 PA_BASE_FLOAT_ARITHMETIC_OPS(Add, +)
330 PA_BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
331 PA_BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
332 PA_BASE_FLOAT_ARITHMETIC_OPS(Div, /)
333 
334 #undef PA_BASE_FLOAT_ARITHMETIC_OPS
335 
336 }  // namespace partition_alloc::internal::base::internal
337 
338 #endif  // PARTITION_ALLOC_PARTITION_ALLOC_BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
339