v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
checked_math_impl.h
Go to the documentation of this file.
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// Slightly adapted for inclusion in V8.
6// Copyright 2025 the V8 project authors. All rights reserved.
7
8#ifndef V8_BASE_NUMERICS_CHECKED_MATH_IMPL_H_
9#define V8_BASE_NUMERICS_CHECKED_MATH_IMPL_H_
10
11// IWYU pragma: private, include "src/base/numerics/checked_math.h"
12
13#include <stdint.h>
14
15#include <cmath>
16#include <concepts>
17#include <limits>
18#include <type_traits>
19
21#include "src/base/numerics/safe_math_shared_impl.h" // IWYU pragma: export
22
23namespace v8::base {
24namespace internal {
25
26template <typename T>
27constexpr bool CheckedAddImpl(T x, T y, T* result) {
28 static_assert(std::integral<T>, "Type must be integral");
29 // Since the value of x+y is undefined if we have a signed type, we compute
30 // it using the unsigned type of the same size.
31 using UnsignedDst = typename std::make_unsigned<T>::type;
32 using SignedDst = typename std::make_signed<T>::type;
33 const UnsignedDst ux = static_cast<UnsignedDst>(x);
34 const UnsignedDst uy = static_cast<UnsignedDst>(y);
35 const UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
36 // Addition is valid if the sign of (x + y) is equal to either that of x or
37 // that of y.
38 if (std::is_signed_v<T>
39 ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) < 0
40 : uresult < uy) { // Unsigned is either valid or underflow.
41 return false;
42 }
43 *result = static_cast<T>(uresult);
44 return true;
45}
46
47template <typename T, typename U>
48struct CheckedAddOp {};
49
50template <typename T, typename U>
51 requires(std::integral<T> && std::integral<U>)
54 template <typename V>
55 static constexpr bool Do(T x, U y, V* result) {
58 }
59
60 // Double the underlying type up to a full machine word.
61 using FastPromotion = FastIntegerArithmeticPromotion<T, U>;
62 using Promotion =
63 std::conditional_t<(kIntegerBitsPlusSign<FastPromotion> >
65 BigEnoughPromotion<T, U>, FastPromotion>;
66 // Fail if either operand is out of range for the promoted type.
67 // TODO(jschuh): This could be made to work for a broader range of values.
68 if (!IsValueInRangeForNumericType<Promotion>(x) ||
69 !IsValueInRangeForNumericType<Promotion>(y)) [[unlikely]] {
70 return false;
71 }
72
73 Promotion presult = {};
74 bool is_valid = true;
76 presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
77 } else {
78 is_valid = CheckedAddImpl(static_cast<Promotion>(x),
79 static_cast<Promotion>(y), &presult);
80 }
81 if (!is_valid || !IsValueInRangeForNumericType<V>(presult)) {
82 return false;
83 }
84 *result = static_cast<V>(presult);
85 return true;
86 }
87};
88
89template <typename T>
90constexpr bool CheckedSubImpl(T x, T y, T* result) {
91 static_assert(std::integral<T>, "Type must be integral");
92 // Since the value of x+y is undefined if we have a signed type, we compute
93 // it using the unsigned type of the same size.
94 using UnsignedDst = typename std::make_unsigned<T>::type;
95 using SignedDst = typename std::make_signed<T>::type;
96 const UnsignedDst ux = static_cast<UnsignedDst>(x);
97 const UnsignedDst uy = static_cast<UnsignedDst>(y);
98 const UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
99 // Subtraction is valid if either x and y have same sign, or (x-y) and x have
100 // the same sign.
101 if (std::is_signed_v<T>
102 ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) < 0
103 : x < y) {
104 return false;
105 }
106 *result = static_cast<T>(uresult);
107 return true;
108}
109
110template <typename T, typename U>
111struct CheckedSubOp {};
112
113template <typename T, typename U>
114 requires(std::integral<T> && std::integral<U>)
117 template <typename V>
118 static constexpr bool Do(T x, U y, V* result) {
121 }
122
123 // Double the underlying type up to a full machine word.
124 using FastPromotion = FastIntegerArithmeticPromotion<T, U>;
125 using Promotion =
126 std::conditional_t<(kIntegerBitsPlusSign<FastPromotion> >
128 BigEnoughPromotion<T, U>, FastPromotion>;
129 // Fail if either operand is out of range for the promoted type.
130 // TODO(jschuh): This could be made to work for a broader range of values.
131 if (!IsValueInRangeForNumericType<Promotion>(x) ||
132 !IsValueInRangeForNumericType<Promotion>(y)) [[unlikely]] {
133 return false;
134 }
135
136 Promotion presult = {};
137 bool is_valid = true;
139 presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
140 } else {
141 is_valid = CheckedSubImpl(static_cast<Promotion>(x),
142 static_cast<Promotion>(y), &presult);
143 }
144 if (!is_valid || !IsValueInRangeForNumericType<V>(presult)) {
145 return false;
146 }
147 *result = static_cast<V>(presult);
148 return true;
149 }
150};
151
152template <typename T>
153constexpr bool CheckedMulImpl(T x, T y, T* result) {
154 static_assert(std::integral<T>, "Type must be integral");
155 // Since the value of x*y is potentially undefined if we have a signed type,
156 // we compute it using the unsigned type of the same size.
157 using UnsignedDst = typename std::make_unsigned<T>::type;
158 using SignedDst = typename std::make_signed<T>::type;
159 const UnsignedDst ux = SafeUnsignedAbs(x);
160 const UnsignedDst uy = SafeUnsignedAbs(y);
161 const UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
162 const bool is_negative =
163 std::is_signed_v<T> && static_cast<SignedDst>(x ^ y) < 0;
164 // We have a fast out for unsigned identity or zero on the second operand.
165 // After that it's an unsigned overflow check on the absolute value, with
166 // a +1 bound for a negative result.
167 if (uy > UnsignedDst(!std::is_signed_v<T> || is_negative) &&
168 ux > (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy) {
169 return false;
170 }
171 *result = static_cast<T>(is_negative ? 0 - uresult : uresult);
172 return true;
173}
174
175template <typename T, typename U>
176struct CheckedMulOp {};
177
178template <typename T, typename U>
179 requires(std::integral<T> && std::integral<U>)
182 template <typename V>
183 static constexpr bool Do(T x, U y, V* result) {
186 }
187
188 using Promotion = FastIntegerArithmeticPromotion<T, U>;
189 // Verify the destination type can hold the result (always true for 0).
190 if ((!IsValueInRangeForNumericType<Promotion>(x) ||
191 !IsValueInRangeForNumericType<Promotion>(y)) &&
192 x && y) [[unlikely]] {
193 return false;
194 }
195
196 Promotion presult = {};
197 bool is_valid = true;
199 // The fast op may be available with the promoted type.
200 // The casts here are safe because of the "value in range" conditional
201 // above.
203 static_cast<Promotion>(x), static_cast<Promotion>(y), &presult);
204 } else if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
205 presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
206 } else {
207 is_valid = CheckedMulImpl(static_cast<Promotion>(x),
208 static_cast<Promotion>(y), &presult);
209 }
210 if (!is_valid || !IsValueInRangeForNumericType<V>(presult)) {
211 return false;
212 }
213 *result = static_cast<V>(presult);
214 return true;
215 }
216};
217
218// Division just requires a check for a zero denominator or an invalid negation
219// on signed min/-1.
220template <typename T, typename U>
221struct CheckedDivOp {};
222
223template <typename T, typename U>
224 requires(std::integral<T> && std::integral<U>)
227 template <typename V>
228 static constexpr bool Do(T x, U y, V* result) {
229 if (!y) [[unlikely]] {
230 return false;
231 }
232
233 // The overflow check can be compiled away if we don't have the exact
234 // combination of types needed to trigger this case.
235 using Promotion = BigEnoughPromotion<T, U>;
236 if (std::is_signed_v<T> && std::is_signed_v<U> &&
237 kIsTypeInRangeForNumericType<T, Promotion> &&
238 static_cast<Promotion>(x) == std::numeric_limits<Promotion>::lowest() &&
239 y == static_cast<U>(-1)) [[unlikely]] {
240 return false;
241 }
242
243 // This branch always compiles away if the above branch wasn't removed.
244 if ((!IsValueInRangeForNumericType<Promotion>(x) ||
245 !IsValueInRangeForNumericType<Promotion>(y)) &&
246 x) [[unlikely]] {
247 return false;
248 }
249
250 const Promotion presult = Promotion(x) / Promotion(y);
251 if (!IsValueInRangeForNumericType<V>(presult)) {
252 return false;
253 }
254 *result = static_cast<V>(presult);
255 return true;
256 }
257};
258
259template <typename T, typename U>
260struct CheckedModOp {};
261
262template <typename T, typename U>
263 requires(std::integral<T> && std::integral<U>)
266 template <typename V>
267 static constexpr bool Do(T x, U y, V* result) {
268 if (!y) [[unlikely]] {
269 return false;
270 }
271
272 using Promotion = BigEnoughPromotion<T, U>;
273 if (std::is_signed_v<T> && std::is_signed_v<U> &&
274 kIsTypeInRangeForNumericType<T, Promotion> &&
275 static_cast<Promotion>(x) == std::numeric_limits<Promotion>::lowest() &&
276 y == static_cast<U>(-1)) [[unlikely]] {
277 *result = 0;
278 return true;
279 }
280
281 const Promotion presult =
282 static_cast<Promotion>(x) % static_cast<Promotion>(y);
283 if (!IsValueInRangeForNumericType<V>(presult)) {
284 return false;
285 }
286 *result = static_cast<Promotion>(presult);
287 return true;
288 }
289};
290
291template <typename T, typename U>
292struct CheckedLshOp {};
293
294// Left shift. Shifts less than 0 or greater than or equal to the number
295// of bits in the promoted type are undefined. Shifts of negative values
296// are undefined. Otherwise it is defined when the result fits.
297template <typename T, typename U>
298 requires(std::integral<T> && std::integral<U>)
300 using result_type = T;
301 template <typename V>
302 static constexpr bool Do(T x, U shift, V* result) {
303 // Disallow negative numbers and verify the shift is in bounds.
304 if (!IsValueNegative(x) &&
305 as_unsigned(shift) < as_unsigned(std::numeric_limits<T>::digits))
306 [[likely]] {
307 // Shift as unsigned to avoid undefined behavior.
308 *result = static_cast<V>(as_unsigned(x) << shift);
309 // If the shift can be reversed, we know it was valid.
310 return *result >> shift == x;
311 }
312
313 // Handle the legal corner-case of a full-width signed shift of zero.
314 if (!std::is_signed_v<T> || x ||
315 as_unsigned(shift) != as_unsigned(std::numeric_limits<T>::digits)) {
316 return false;
317 }
318 *result = 0;
319 return true;
320 }
321};
322
323template <typename T, typename U>
324struct CheckedRshOp {};
325
326// Right shift. Shifts less than 0 or greater than or equal to the number
327// of bits in the promoted type are undefined. Otherwise, it is always defined,
328// but a right shift of a negative value is implementation-dependent.
329template <typename T, typename U>
330 requires(std::integral<T> && std::integral<U>)
332 using result_type = T;
333 template <typename V>
334 static constexpr bool Do(T x, U shift, V* result) {
335 // Use sign conversion to push negative values out of range.
336 if (as_unsigned(shift) >= kIntegerBitsPlusSign<T>) [[unlikely]] {
337 return false;
338 }
339
340 const T tmp = x >> shift;
341 if (!IsValueInRangeForNumericType<V>(tmp)) {
342 return false;
343 }
344 *result = static_cast<V>(tmp);
345 return true;
346 }
347};
348
349template <typename T, typename U>
350struct CheckedAndOp {};
351
352// For simplicity we support only unsigned integer results.
353template <typename T, typename U>
354 requires(std::integral<T> && std::integral<U>)
356 using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
357 template <typename V>
358 static constexpr bool Do(T x, U y, V* result) {
359 const result_type tmp =
360 static_cast<result_type>(x) & static_cast<result_type>(y);
361 if (!IsValueInRangeForNumericType<V>(tmp)) {
362 return false;
363 }
364 *result = static_cast<V>(tmp);
365 return true;
366 }
367};
368
369template <typename T, typename U>
370struct CheckedOrOp {};
371
372// For simplicity we support only unsigned integers.
373template <typename T, typename U>
374 requires(std::integral<T> && std::integral<U>)
376 using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
377 template <typename V>
378 static constexpr bool Do(T x, U y, V* result) {
379 const result_type tmp =
380 static_cast<result_type>(x) | static_cast<result_type>(y);
381 if (!IsValueInRangeForNumericType<V>(tmp)) {
382 return false;
383 }
384 *result = static_cast<V>(tmp);
385 return true;
386 }
387};
388
389template <typename T, typename U>
390struct CheckedXorOp {};
391
392// For simplicity we support only unsigned integers.
393template <typename T, typename U>
394 requires(std::integral<T> && std::integral<U>)
396 using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
397 template <typename V>
398 static constexpr bool Do(T x, U y, V* result) {
399 const result_type tmp =
400 static_cast<result_type>(x) ^ static_cast<result_type>(y);
401 if (!IsValueInRangeForNumericType<V>(tmp)) {
402 return false;
403 }
404 *result = static_cast<V>(tmp);
405 return true;
406 }
407};
408
409// Max doesn't really need to be implemented this way because it can't fail,
410// but it makes the code much cleaner to use the MathOp wrappers.
411template <typename T, typename U>
412struct CheckedMaxOp {};
413
414template <typename T, typename U>
415 requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
418 template <typename V>
419 static constexpr bool Do(T x, U y, V* result) {
421 ? static_cast<result_type>(x)
422 : static_cast<result_type>(y);
423 if (!IsValueInRangeForNumericType<V>(tmp)) {
424 return false;
425 }
426 *result = static_cast<V>(tmp);
427 return true;
428 }
429};
430
431// Min doesn't really need to be implemented this way because it can't fail,
432// but it makes the code much cleaner to use the MathOp wrappers.
433template <typename T, typename U>
434struct CheckedMinOp {};
435
436template <typename T, typename U>
437 requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
440 template <typename V>
441 static constexpr bool Do(T x, U y, V* result) {
442 const result_type tmp = IsLess<T, U>::Test(x, y)
443 ? static_cast<result_type>(x)
444 : static_cast<result_type>(y);
445 if (!IsValueInRangeForNumericType<V>(tmp)) {
446 return false;
447 }
448 *result = static_cast<V>(tmp);
449 return true;
450 }
451};
452
453// This is just boilerplate that wraps the standard floating point arithmetic.
454// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
455#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
456 template <typename T, typename U> \
457 requires(std::floating_point<T> || std::floating_point<U>) \
458 struct Checked##NAME##Op<T, U> { \
459 using result_type = MaxExponentPromotion<T, U>; \
460 template <typename V> \
461 static constexpr bool Do(T x, U y, V* result) { \
462 const result_type presult = x OP y; \
463 if (!IsValueInRangeForNumericType<V>(presult)) return false; \
464 *result = static_cast<V>(presult); \
465 return true; \
466 } \
467 };
468
473
474#undef BASE_FLOAT_ARITHMETIC_OPS
475
476// Floats carry around their validity state with them, but integers do not. So,
477// we wrap the underlying value in a specialization in order to hide that detail
478// and expose an interface via accessors.
484
485template <typename NumericType>
487 static const NumericRepresentation value =
488 std::integral<NumericType>
490 : (std::floating_point<NumericType> ? NUMERIC_FLOATING
492};
493
494template <typename T,
497
498// Integrals require quite a bit of additional housekeeping to manage state.
499template <typename T>
501 public:
502 template <typename Src = int>
503 constexpr explicit CheckedNumericState(Src value = 0, bool is_valid = true)
504 : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
505 value_(WellDefinedConversionOrZero(value, is_valid_)) {
506 static_assert(std::is_arithmetic_v<Src>, "Argument must be numeric.");
507 }
508
509 template <typename Src>
511 : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
512
513 constexpr bool is_valid() const { return is_valid_; }
514
515 constexpr T value() const { return value_; }
516
517 private:
518 // Ensures that a type conversion does not trigger undefined behavior.
519 template <typename Src>
520 static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid) {
521 return (std::integral<UnderlyingType<Src>> || is_valid)
522 ? static_cast<T>(value)
523 : 0;
524 }
525
526 // is_valid_ precedes value_ because member initializers in the constructors
527 // are evaluated in field order, and is_valid_ must be read when initializing
528 // value_.
531};
532
533// Floating points maintain their own validity, but need translation wrappers.
534template <typename T>
536 public:
537 template <typename Src = double>
538 constexpr explicit CheckedNumericState(Src value = 0.0, bool is_valid = true)
539 : value_(WellDefinedConversionOrNaN(
540 value, is_valid && IsValueInRangeForNumericType<T>(value))) {}
541
542 template <typename Src>
544 : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
545
546 constexpr bool is_valid() const {
547 // Written this way because std::isfinite is not constexpr before C++23.
548 // TODO(C++23): Use `std::isfinite()` unconditionally.
549 return std::is_constant_evaluated()
550 ? value_ <= std::numeric_limits<T>::max() &&
551 value_ >= std::numeric_limits<T>::lowest()
552 : std::isfinite(value_);
553 }
554
555 constexpr T value() const { return value_; }
556
557 private:
558 // Ensures that a type conversion does not trigger undefined behavior.
559 template <typename Src>
560 static constexpr T WellDefinedConversionOrNaN(Src value, bool is_valid) {
563 is_valid)
564 ? static_cast<T>(value)
565 : std::numeric_limits<T>::quiet_NaN();
566 }
567
569};
570
571} // namespace internal
572} // namespace v8::base
573
574#endif // V8_BASE_NUMERICS_CHECKED_MATH_IMPL_H_
#define V(Name)
#define T
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)
static constexpr T WellDefinedConversionOrNaN(Src value, bool is_valid)
constexpr CheckedNumericState(Src value=0.0, bool is_valid=true)
constexpr CheckedNumericState(const CheckedNumericState< Src > &rhs)
constexpr CheckedNumericState(const CheckedNumericState< Src > &rhs)
constexpr CheckedNumericState(Src value=0, bool is_valid=true)
static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid)
Register const value_
ZoneVector< RpoNumber > & result
int y
int x
constexpr auto SafeUnsignedAbs(T value)
constexpr bool IsValueNegative(T value)
constexpr int kIntegerBitsPlusSign
constexpr bool CheckedAddImpl(T x, T y, T *result)
constexpr auto kStaticDstRangeRelationToSrcRange
constexpr auto as_unsigned(Src value)
UnderlyingTypeImpl< T >::type UnderlyingType
constexpr bool CheckedMulImpl(T x, T y, T *result)
BigEnoughPromotionImpl< Lhs, Rhs >::type BigEnoughPromotion
constexpr bool kIsIntegerArithmeticSafe
std::conditional_t< std::is_signed_v< Lhs > ?(!std::is_signed_v< Rhs >||kMaxExponent< Lhs > > kMaxExponent< Rhs >) :(!std::is_signed_v< Rhs > &&kMaxExponent< Lhs >< kMaxExponent< Rhs >), Lhs, Rhs > LowestValuePromotion
constexpr bool CheckedSubImpl(T x, T y, T *result)
FastIntegerArithmeticPromotionImpl< Lhs, Rhs >::type FastIntegerArithmeticPromotion
std::conditional_t<(kMaxExponent< Lhs > > kMaxExponent< Rhs >), Lhs, Rhs > MaxExponentPromotion
static constexpr bool Do(T, U, V *)
MaxExponentPromotion< T, U > result_type
static constexpr bool Do(T x, U y, V *result)
std::make_unsigned_t< MaxExponentPromotion< T, U > > result_type
static constexpr bool Do(T x, U y, V *result)
MaxExponentPromotion< T, U > result_type
static constexpr bool Do(T x, U y, V *result)
static constexpr bool Do(T x, U shift, V *result)
static constexpr bool Do(T x, U y, V *result)
MaxExponentPromotion< T, U > result_type
LowestValuePromotion< T, U > result_type
static constexpr bool Do(T x, U y, V *result)
MaxExponentPromotion< T, U > result_type
static constexpr bool Do(T x, U y, V *result)
static constexpr bool Do(T, U, V *)
MaxExponentPromotion< T, U > result_type
static constexpr bool Do(T x, U y, V *result)
static constexpr bool Do(T x, U y, V *result)
std::make_unsigned_t< MaxExponentPromotion< T, U > > result_type
static constexpr bool Do(T x, U shift, V *result)
static constexpr bool Do(T, U, V *)
MaxExponentPromotion< T, U > result_type
static constexpr bool Do(T x, U y, V *result)
static constexpr bool Do(T x, U y, V *result)
std::make_unsigned_t< MaxExponentPromotion< T, U > > result_type
static constexpr bool Test(L lhs, R rhs)
static constexpr bool Test(L lhs, R rhs)
std::unique_ptr< ValueMirror > value