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