v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
clamped_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_CLAMPED_MATH_IMPL_H_
9#define V8_BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
10
11// IWYU pragma: private, include "src/base/numerics/clamped_math.h"
12
13#include <concepts>
14#include <limits>
15#include <type_traits>
16
19#include "src/base/numerics/safe_math_shared_impl.h" // IWYU pragma: export
20
21namespace v8::base {
22namespace internal {
23
24template <typename T>
25 requires(std::signed_integral<T>)
26constexpr T SaturatedNegWrapper(T value) {
27 return std::is_constant_evaluated() || !ClampedNegFastOp<T>::is_supported
28 ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
29 ? NegateWrapper(value)
30 : std::numeric_limits<T>::max())
32}
33
34template <typename T>
35 requires(std::unsigned_integral<T>)
36constexpr T SaturatedNegWrapper(T value) {
37 return T(0);
38}
39
40template <typename T>
41 requires(std::floating_point<T>)
42constexpr T SaturatedNegWrapper(T value) {
43 return -value;
44}
45
46template <typename T>
47 requires(std::integral<T>)
48constexpr 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
62template <typename T>
63 requires(std::floating_point<T>)
64constexpr T SaturatedAbsWrapper(T value) {
65 return value < 0 ? -value : value;
66}
67
68template <typename T, typename U>
69struct ClampedAddOp {};
70
71template <typename T, typename U>
72 requires(std::integral<T> && std::integral<U>)
75 template <typename V = result_type>
76 requires(std::same_as<V, result_type> || kIsTypeInRangeForNumericType<U, V>)
77 static constexpr V Do(T x, U y) {
78 if (!std::is_constant_evaluated() && ClampedAddFastOp<T, U>::is_supported) {
80 }
81 const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
82 V result = {};
83 if (CheckedAddOp<T, U>::Do(x, y, &result)) [[likely]] {
84 return result;
85 }
86 return saturated;
87 }
88};
89
90template <typename T, typename U>
91struct ClampedSubOp {};
92
93template <typename T, typename U>
94 requires(std::integral<T> && std::integral<U>)
97 template <typename V = result_type>
98 requires(std::same_as<V, result_type> || kIsTypeInRangeForNumericType<U, V>)
99 static constexpr V Do(T x, U y) {
100 if (!std::is_constant_evaluated() && ClampedSubFastOp<T, U>::is_supported) {
101 return ClampedSubFastOp<T, U>::template Do<V>(x, y);
102 }
103 const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
104 V result = {};
105 if (CheckedSubOp<T, U>::Do(x, y, &result)) [[likely]] {
106 return result;
107 }
108 return saturated;
109 }
110};
111
112template <typename T, typename U>
113struct ClampedMulOp {};
114
115template <typename T, typename U>
116 requires(std::integral<T> && std::integral<U>)
119 template <typename V = result_type>
120 static constexpr V Do(T x, U y) {
121 if (!std::is_constant_evaluated() && ClampedMulFastOp<T, U>::is_supported) {
122 return ClampedMulFastOp<T, U>::template Do<V>(x, y);
123 }
124 V result = {};
125 const V saturated =
127 if (CheckedMulOp<T, U>::Do(x, y, &result)) [[likely]] {
128 return result;
129 }
130 return saturated;
131 }
132};
133
134template <typename T, typename U>
135struct ClampedDivOp {};
136
137template <typename T, typename U>
138 requires(std::integral<T> && std::integral<U>)
141 template <typename V = result_type>
142 static constexpr V Do(T x, U y) {
143 V result = {};
144 if ((CheckedDivOp<T, U>::Do(x, y, &result))) [[likely]] {
145 return result;
146 }
147 // Saturation goes to max, min, or NaN (if x is zero).
150 }
151};
152
153template <typename T, typename U>
154struct ClampedModOp {};
155
156template <typename T, typename U>
157 requires(std::integral<T> && std::integral<U>)
160 template <typename V = result_type>
161 static constexpr V Do(T x, U y) {
162 V result = {};
163 if (CheckedModOp<T, U>::Do(x, y, &result)) [[likely]] {
164 return result;
165 }
166 return x;
167 }
168};
169
170template <typename T, typename U>
171struct ClampedLshOp {};
172
173// Left shift. Non-zero values saturate in the direction of the sign. A zero
174// shifted by any value always results in zero.
175template <typename T, typename U>
176 requires(std::integral<T> && std::unsigned_integral<U>)
178 using result_type = T;
179 template <typename V = result_type>
180 static constexpr V Do(T x, U shift) {
181 if (shift < std::numeric_limits<T>::digits) [[likely]] {
182 // Shift as unsigned to avoid undefined behavior.
183 V result = static_cast<V>(as_unsigned(x) << shift);
184 // If the shift can be reversed, we know it was valid.
185 if (result >> shift == x) [[likely]] {
186 return result;
187 }
188 }
189 return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
190 }
191};
192
193template <typename T, typename U>
194struct ClampedRshOp {};
195
196// Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
197template <typename T, typename U>
198 requires(std::integral<T> && std::unsigned_integral<U>)
200 using result_type = T;
201 template <typename V = result_type>
202 static constexpr V Do(T x, U shift) {
203 // Signed right shift is odd, because it saturates to -1 or 0.
204 const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
205 if (shift < kIntegerBitsPlusSign<T>) [[likely]] {
206 return saturated_cast<V>(x >> shift);
207 }
208 return saturated;
209 }
210};
211
212template <typename T, typename U>
213struct ClampedAndOp {};
214
215template <typename T, typename U>
216 requires(std::integral<T> && std::integral<U>)
218 using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
219 template <typename V>
220 static constexpr V Do(T x, U y) {
221 return static_cast<result_type>(x) & static_cast<result_type>(y);
222 }
223};
224
225template <typename T, typename U>
226struct ClampedOrOp {};
227
228// For simplicity we promote to unsigned integers.
229template <typename T, typename U>
230 requires(std::integral<T> && std::integral<U>)
232 using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
233 template <typename V>
234 static constexpr V Do(T x, U y) {
235 return static_cast<result_type>(x) | static_cast<result_type>(y);
236 }
237};
238
239template <typename T, typename U>
240struct ClampedXorOp {};
241
242// For simplicity we support only unsigned integers.
243template <typename T, typename U>
244 requires(std::integral<T> && std::integral<U>)
246 using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
247 template <typename V>
248 static constexpr V Do(T x, U y) {
249 return static_cast<result_type>(x) ^ static_cast<result_type>(y);
250 }
251};
252
253template <typename T, typename U>
254struct ClampedMaxOp {};
255
256template <typename T, typename U>
257 requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
260 template <typename V = result_type>
261 static constexpr V Do(T x, U y) {
264 }
265};
266
267template <typename T, typename U>
268struct ClampedMinOp {};
269
270template <typename T, typename U>
271 requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
274 template <typename V = result_type>
275 static constexpr V Do(T x, U y) {
278 }
279};
280
281// This is just boilerplate that wraps the standard floating point arithmetic.
282// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
283#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
284 template <typename T, typename U> \
285 requires(std::floating_point<T> || std::floating_point<U>) \
286 struct Clamped##NAME##Op<T, U> { \
287 using result_type = MaxExponentPromotion<T, U>; \
288 template <typename V = result_type> \
289 static constexpr V Do(T x, U y) { \
290 return saturated_cast<V>(x OP y); \
291 } \
292 };
293
298
299#undef BASE_FLOAT_ARITHMETIC_OPS
300
301} // namespace internal
302} // namespace v8::base
303
304#endif // V8_BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
#define V(Name)
#define T
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)
ZoneVector< RpoNumber > & result
int y
int x
constexpr auto SafeUnsignedAbs(T value)
constexpr bool IsValueNegative(T value)
constexpr T NegateWrapper(T value)
constexpr int kIntegerBitsPlusSign
constexpr auto as_unsigned(Src value)
constexpr T SaturatedAbsWrapper(T value)
constexpr T SaturatedNegWrapper(T value)
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 Dst CommonMaxOrMin(bool is_min)
std::conditional_t<(kMaxExponent< Lhs > > kMaxExponent< Rhs >), Lhs, Rhs > MaxExponentPromotion
constexpr Dst saturated_cast(Src value)
MaxExponentPromotion< T, U > result_type
std::make_unsigned_t< MaxExponentPromotion< T, U > > result_type
MaxExponentPromotion< T, U > result_type
static constexpr V Do(T x, U shift)
MaxExponentPromotion< T, U > result_type
LowestValuePromotion< T, U > result_type
MaxExponentPromotion< T, U > result_type
MaxExponentPromotion< T, U > result_type
std::make_unsigned_t< MaxExponentPromotion< T, U > > result_type
static constexpr V Do(T x, U shift)
MaxExponentPromotion< T, U > result_type
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