/* * Copyright (c) 2021-2023, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace Wasm::Operators { using namespace AK::SIMD; #define DEFINE_BINARY_OPERATOR(Name, operation) \ struct Name { \ template \ auto operator()(Lhs lhs, Rhs rhs) const \ { \ return lhs operation rhs; \ } \ \ static StringView name() \ { \ return #operation##sv; \ } \ } DEFINE_BINARY_OPERATOR(Equals, ==); DEFINE_BINARY_OPERATOR(NotEquals, !=); DEFINE_BINARY_OPERATOR(GreaterThan, >); DEFINE_BINARY_OPERATOR(LessThan, <); DEFINE_BINARY_OPERATOR(LessThanOrEquals, <=); DEFINE_BINARY_OPERATOR(GreaterThanOrEquals, >=); DEFINE_BINARY_OPERATOR(Add, +); DEFINE_BINARY_OPERATOR(Subtract, -); DEFINE_BINARY_OPERATOR(Multiply, *); DEFINE_BINARY_OPERATOR(BitAnd, &); DEFINE_BINARY_OPERATOR(BitOr, |); DEFINE_BINARY_OPERATOR(BitXor, ^); #undef DEFINE_BINARY_OPERATOR struct Divide { template auto operator()(Lhs lhs, Rhs rhs) const { if constexpr (IsFloatingPoint) { return lhs / rhs; } else { Checked value(lhs); value /= rhs; if (value.has_overflow()) return AK::ErrorOr("Integer division overflow"sv); return AK::ErrorOr(value.value()); } } static StringView name() { return "/"sv; } }; struct Modulo { template auto operator()(Lhs lhs, Rhs rhs) const { if (rhs == 0) return AK::ErrorOr("Integer division overflow"sv); if constexpr (IsSigned) { if (rhs == -1) return AK::ErrorOr(0); // Spec weirdness right here, signed division overflow is ignored. } return AK::ErrorOr(lhs % rhs); } static StringView name() { return "%"sv; } }; struct Average { template auto operator()(Lhs lhs, Rhs rhs) const { return static_cast((lhs + rhs + 1) / 2); } static StringView name() { return "avgr"sv; } }; struct Q15Mul { template auto operator()(Lhs lhs, Rhs rhs) const { return (lhs * rhs + 0x4000) >> 15; } static StringView name() { return "q15mul"sv; } }; struct BitShiftLeft { template auto operator()(Lhs lhs, Rhs rhs) const { return lhs << (rhs % (sizeof(lhs) * 8)); } static StringView name() { return "<<"sv; } }; struct BitShiftRight { template auto operator()(Lhs lhs, Rhs rhs) const { return lhs >> (rhs % (sizeof(lhs) * 8)); } static StringView name() { return ">>"sv; } }; struct BitAndNot { template auto operator()(Lhs lhs, Rhs rhs) const { return lhs & ~rhs; } static StringView name() { return "andnot"sv; } }; struct BitNot { template auto operator()(Lhs lhs) const { return ~lhs; } static StringView name() { return "~"sv; } }; struct BitRotateLeft { template auto operator()(Lhs lhs, Rhs rhs) const { // generates a single 'rol' instruction if shift is positive // otherwise generate a `ror` auto const mask = CHAR_BIT * sizeof(Lhs) - 1; rhs &= mask; return (lhs << rhs) | (lhs >> ((-rhs) & mask)); } static StringView name() { return "rotate_left"sv; } }; struct BitRotateRight { template auto operator()(Lhs lhs, Rhs rhs) const { // generates a single 'ror' instruction if shift is positive // otherwise generate a `rol` auto const mask = CHAR_BIT * sizeof(Lhs) - 1; rhs &= mask; return (lhs >> rhs) | (lhs << ((-rhs) & mask)); } static StringView name() { return "rotate_right"sv; } }; template typename SetSign = MakeSigned> struct VectorAllTrue { auto operator()(u128 c) const { using ElementType = NativeIntegralType<128 / VectorSize>; auto any_false = bit_cast>(c) == 0; return bit_cast(any_false) == 0; } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).all_true"sv; case 8: return "vec(16x8).all_true"sv; case 4: return "vec(32x4).all_true"sv; case 2: return "vec(64x2).all_true"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorShiftLeft { auto operator()(u128 lhs, i32 rhs) const { auto shift_value = rhs % (sizeof(lhs) * 8 / VectorSize); return bit_cast(bit_cast, MakeUnsigned>>(lhs) << shift_value); } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16)<<"sv; case 8: return "vec(16x8)<<"sv; case 4: return "vec(32x4)<<"sv; case 2: return "vec(64x2)<<"sv; default: VERIFY_NOT_REACHED(); } } }; template typename SetSign> struct VectorShiftRight { auto operator()(u128 lhs, i32 rhs) const { auto shift_value = rhs % (sizeof(lhs) * 8 / VectorSize); return bit_cast(bit_cast>, SetSign>>(lhs) >> shift_value); } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16)>>"sv; case 8: return "vec(16x8)>>"sv; case 4: return "vec(32x4)>>"sv; case 2: return "vec(64x2)>>"sv; default: VERIFY_NOT_REACHED(); } } }; struct VectorSwizzle { auto operator()(u128 c1, u128 c2) const { // https://webassembly.github.io/spec/core/bikeshed/#-mathsfi8x16hrefsyntax-instr-vecmathsfswizzle%E2%91%A0 auto i = bit_cast>(c1); auto j = bit_cast>(c2); auto result = shuffle_or_0(i, j); return bit_cast(result); } static StringView name() { return "vec(8x16).swizzle"sv; } }; template typename SetSign> struct VectorExtractLane { size_t lane; auto operator()(u128 c) const { auto result = bit_cast, SetSign>>(c); return result[lane]; } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).extract_lane"sv; case 8: return "vec(16x8).extract_lane"sv; case 4: return "vec(32x4).extract_lane"sv; case 2: return "vec(64x2).extract_lane"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorExtractLaneFloat { size_t lane; auto operator()(u128 c) const { auto result = bit_cast>(c); return result[lane]; } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).extract_lane"sv; case 8: return "vec(16x8).extract_lane"sv; case 4: return "vec(32x4).extract_lane"sv; case 2: return "vec(64x2).extract_lane"sv; default: VERIFY_NOT_REACHED(); } } }; template> struct VectorReplaceLane { size_t lane; using ValueType = Conditional, NativeFloatingType<128 / VectorSize>, NativeIntegralType<128 / VectorSize>>; auto operator()(u128 c, TrueValueType value) const { auto result = bit_cast>(c); result[lane] = static_cast(value); return bit_cast(result); } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).replace_lane"sv; case 8: return "vec(16x8).replace_lane"sv; case 4: return "vec(32x4).replace_lane"sv; case 2: return "vec(64x2).replace_lane"sv; default: VERIFY_NOT_REACHED(); } } }; template typename SetSign = MakeSigned> struct VectorCmpOp { auto operator()(u128 c1, u128 c2) const { using ElementType = NativeIntegralType<128 / VectorSize>; auto result = bit_cast>(c1); auto other = bit_cast>(c2); Op op; for (size_t i = 0; i < VectorSize; ++i) { SetSign lhs = result[i]; SetSign rhs = other[i]; result[i] = op(lhs, rhs) ? static_cast>(-1) : 0; } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).cmp"sv; case 8: return "vec(16x8).cmp"sv; case 4: return "vec(32x4).cmp"sv; case 2: return "vec(64x2).cmp"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorFloatCmpOp { auto operator()(u128 c1, u128 c2) const { auto first = bit_cast>>(c1); auto other = bit_cast>>(c2); using ElementType = NativeIntegralType<128 / VectorSize>; Native128ByteVectorOf result; Op op; for (size_t i = 0; i < VectorSize; ++i) result[i] = op(first[i], other[i]) ? static_cast(-1) : 0; return bit_cast(result); } static StringView name() { switch (VectorSize) { case 4: return "vecf(32x4).cmp"sv; case 2: return "vecf(64x2).cmp"sv; default: VERIFY_NOT_REACHED(); } } }; struct Minimum { template auto operator()(Lhs lhs, Rhs rhs) const { if constexpr (IsFloatingPoint || IsFloatingPoint) { if (isnan(lhs) || isnan(rhs)) { return isnan(lhs) ? lhs : rhs; } if (lhs == 0 && rhs == 0) { return signbit(lhs) ? lhs : rhs; } } return min(lhs, rhs); } static StringView name() { return "minimum"sv; } }; struct Maximum { template auto operator()(Lhs lhs, Rhs rhs) const { if constexpr (IsFloatingPoint || IsFloatingPoint) { if (isnan(lhs) || isnan(rhs)) { return isnan(lhs) ? lhs : rhs; } if (lhs == 0 && rhs == 0) { return signbit(lhs) ? rhs : lhs; } } return max(lhs, rhs); } static StringView name() { return "maximum"sv; } }; struct PseudoMinimum { template auto operator()(Lhs lhs, Rhs rhs) const { return rhs < lhs ? rhs : lhs; } static StringView name() { return "pseudo_minimum"sv; } }; struct PseudoMaximum { template auto operator()(Lhs lhs, Rhs rhs) const { return lhs < rhs ? rhs : lhs; } static StringView name() { return "pseudo_maximum"sv; } }; struct CopySign { template auto operator()(Lhs lhs, Rhs rhs) const { if constexpr (IsSame) return copysignf(lhs, rhs); else if constexpr (IsSame) return copysign(lhs, rhs); else static_assert(DependentFalse, "Invalid types to CopySign"); } static StringView name() { return "copysign"sv; } }; // Unary struct EqualsZero { template auto operator()(Lhs lhs) const { return lhs == 0; } static StringView name() { return "== 0"sv; } }; struct CountLeadingZeros { template i32 operator()(Lhs lhs) const { if (lhs == 0) return sizeof(Lhs) * CHAR_BIT; if constexpr (sizeof(Lhs) == 4 || sizeof(Lhs) == 8) return count_leading_zeroes(MakeUnsigned(lhs)); else VERIFY_NOT_REACHED(); } static StringView name() { return "clz"sv; } }; struct CountTrailingZeros { template i32 operator()(Lhs lhs) const { if (lhs == 0) return sizeof(Lhs) * CHAR_BIT; if constexpr (sizeof(Lhs) == 4 || sizeof(Lhs) == 8) return count_trailing_zeroes(MakeUnsigned(lhs)); else VERIFY_NOT_REACHED(); } static StringView name() { return "ctz"sv; } }; struct PopCount { template auto operator()(Lhs lhs) const { if constexpr (sizeof(Lhs) == 1 || sizeof(Lhs) == 2 || sizeof(Lhs) == 4 || sizeof(Lhs) == 8) return popcount(MakeUnsigned(lhs)); else VERIFY_NOT_REACHED(); } static StringView name() { return "popcnt"sv; } }; struct Absolute { template Lhs operator()(Lhs lhs) const { if constexpr (IsFloatingPoint) return AK::abs(lhs); if constexpr (IsSigned) { if (lhs == NumericLimits::min()) return NumericLimits::min(); // Return the negation of _i_ modulo 2^N: https://www.w3.org/TR/wasm-core-2/#-hrefop-iabsmathrmiabs_n-i step 3 } return AK::abs(lhs); } static StringView name() { return "abs"sv; } }; struct Negate { template Lhs operator()(Lhs lhs) const { if constexpr (IsFloatingPoint) return -lhs; if constexpr (IsSigned) { if (lhs == NumericLimits::min()) return NumericLimits::min(); // Return the negation of _i_ modulo 2^N: https://www.w3.org/TR/wasm-core-2/#-hrefop-iabsmathrmiabs_n-i step 3 } return -lhs; } static StringView name() { return "== 0"sv; } }; struct Ceil { template auto operator()(Lhs lhs) const { if constexpr (IsSame) return ceilf(lhs); else if constexpr (IsSame) return ceil(lhs); else VERIFY_NOT_REACHED(); } static StringView name() { return "ceil"sv; } }; template typename SetSign = MakeSigned> struct VectorIntegerExtOpPairwise { auto operator()(u128 c) const { using VectorResult = NativeVectorType<128 / VectorSize, VectorSize, SetSign>; using VectorInput = NativeVectorType<128 / (VectorSize * 2), VectorSize * 2, SetSign>; auto vector = bit_cast(c); VectorResult result; Op op; // FIXME: Find a way to not loop here for (size_t i = 0; i < VectorSize; ++i) { result[i] = op(vector[i * 2], vector[(i * 2) + 1]); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 8: return "vec(16x8).ext_op_pairwise(8x16)"sv; case 4: return "vec(32x4).ext_op_pairwise(16x8)"sv; case 2: return "vec(64x2).ext_op_pairwise(32x4)"sv; default: VERIFY_NOT_REACHED(); } } }; enum class VectorExt { High, Low, }; template typename SetSign = MakeSigned> struct VectorIntegerExt { auto operator()(u128 c) const { using VectorResult = NativeVectorType<128 / VectorSize, VectorSize, SetSign>; using VectorInput = NativeVectorType<128 / (VectorSize * 2), VectorSize * 2, SetSign>; auto vector = bit_cast(c); VectorResult result; // FIXME: Find a way to not loop here for (size_t i = 0; i < VectorSize; ++i) { if constexpr (Mode == VectorExt::High) result[i] = vector[VectorSize + i]; else if constexpr (Mode == VectorExt::Low) result[i] = vector[i]; else VERIFY_NOT_REACHED(); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 8: return "vec(16x8).ext(8x16)"sv; case 4: return "vec(32x4).ext(16x8)"sv; case 2: return "vec(64x2).ext(32x4)"sv; default: VERIFY_NOT_REACHED(); } } }; template typename SetSign = MakeSigned> struct VectorIntegerExtOp { auto operator()(u128 lhs, u128 rhs) const { using VectorResult = NativeVectorType<128 / VectorSize, VectorSize, SetSign>; using VectorInput = NativeVectorType<128 / (VectorSize * 2), VectorSize * 2, SetSign>; auto first = bit_cast(lhs); auto second = bit_cast(rhs); VectorResult result; Op op; using ResultType = SetSign>; // FIXME: Find a way to not loop here for (size_t i = 0; i < VectorSize; ++i) { if constexpr (Mode == VectorExt::High) { ResultType a = first[VectorSize + i]; ResultType b = second[VectorSize + i]; result[i] = op(a, b); } else if constexpr (Mode == VectorExt::Low) { ResultType a = first[i]; ResultType b = second[i]; result[i] = op(a, b); } else VERIFY_NOT_REACHED(); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 8: return "vec(16x8).ext_op(8x16)"sv; case 4: return "vec(32x4).ext_op(16x8)"sv; case 2: return "vec(64x2).ext_op(32x4)"sv; default: VERIFY_NOT_REACHED(); } } }; template typename SetSign = MakeSigned> struct VectorIntegerBinaryOp { auto operator()(u128 lhs, u128 rhs) const { using VectorType = NativeVectorType<128 / VectorSize, VectorSize, SetSign>; auto first = bit_cast(lhs); auto second = bit_cast(rhs); VectorType result; Op op; // FIXME: Find a way to not loop here for (size_t i = 0; i < VectorSize; ++i) { result[i] = op(first[i], second[i]); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).binary_op"sv; case 8: return "vec(16x8).binary_op"sv; case 4: return "vec(32x4).binary_op"sv; case 2: return "vec(64x2).binary_op"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorBitmask { auto operator()(u128 lhs) const { using VectorType = NativeVectorType<128 / VectorSize, VectorSize, MakeSigned>; auto value = bit_cast(lhs); u32 result = 0; for (size_t i = 0; i < VectorSize; ++i) result |= static_cast(value[i] < 0) << i; return result; } static StringView name() { return "bitmask"sv; } }; template struct VectorDotProduct { auto operator()(u128 lhs, u128 rhs) const { using VectorInput = NativeVectorType<128 / (VectorSize * 2), VectorSize * 2, MakeSigned>; using VectorResult = NativeVectorType<128 / VectorSize, VectorSize, MakeSigned>; auto v1 = bit_cast(lhs); auto v2 = bit_cast(rhs); VectorResult result; using ResultType = MakeUnsigned>; for (size_t i = 0; i < VectorSize; ++i) { ResultType low = v1[i * 2] * v2[i * 2]; ResultType high = v1[(i * 2) + 1] * v2[(i * 2) + 1]; result[i] = low + high; } return bit_cast(result); } static StringView name() { return "dot"sv; } }; template struct VectorNarrow { auto operator()(u128 lhs, u128 rhs) const { using VectorInput = NativeVectorType<128 / (VectorSize / 2), VectorSize / 2, MakeSigned>; using VectorResult = NativeVectorType<128 / VectorSize, VectorSize, MakeUnsigned>; auto v1 = bit_cast(lhs); auto v2 = bit_cast(rhs); VectorResult result; for (size_t i = 0; i < (VectorSize / 2); ++i) { if (v1[i] <= NumericLimits::min()) result[i] = NumericLimits::min(); else if (v1[i] >= NumericLimits::max()) result[i] = NumericLimits::max(); else result[i] = v1[i]; } for (size_t i = 0; i < (VectorSize / 2); ++i) { if (v2[i] <= NumericLimits::min()) result[i + VectorSize / 2] = NumericLimits::min(); else if (v2[i] >= NumericLimits::max()) result[i + VectorSize / 2] = NumericLimits::max(); else result[i + VectorSize / 2] = v2[i]; } return bit_cast(result); } static StringView name() { return "narrow"sv; } }; template typename SetSign = MakeSigned> struct VectorIntegerUnaryOp { auto operator()(u128 lhs) const { using VectorType = NativeVectorType<128 / VectorSize, VectorSize, SetSign>; auto value = bit_cast(lhs); VectorType result; Op op; // FIXME: Find a way to not loop here for (size_t i = 0; i < VectorSize; ++i) { result[i] = op(value[i]); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 16: return "vec(8x16).unary_op"sv; case 8: return "vec(16x8).unary_op"sv; case 4: return "vec(32x4).unary_op"sv; case 2: return "vec(64x2).unary_op"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorFloatBinaryOp { auto operator()(u128 lhs, u128 rhs) const { using VectorType = NativeFloatingVectorType<128, VectorSize, NativeFloatingType<128 / VectorSize>>; auto first = bit_cast(lhs); auto second = bit_cast(rhs); VectorType result; Op op; for (size_t i = 0; i < VectorSize; ++i) { result[i] = op(first[i], second[i]); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 4: return "vecf(32x4).binary_op"sv; case 2: return "vecf(64x2).binary_op"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorFloatUnaryOp { auto operator()(u128 lhs) const { using VectorType = NativeFloatingVectorType<128, VectorSize, NativeFloatingType<128 / VectorSize>>; auto value = bit_cast(lhs); VectorType result; Op op; for (size_t i = 0; i < VectorSize; ++i) { result[i] = op(value[i]); } return bit_cast(result); } static StringView name() { switch (VectorSize) { case 4: return "vecf(32x4).unary_op"sv; case 2: return "vecf(64x2).unary_op"sv; default: VERIFY_NOT_REACHED(); } } }; template struct VectorConvertOp { auto operator()(u128 lhs) const { using VectorInput = NativeVectorType<128 / InputSize, InputSize, MakeUnsigned>; using VectorResult = NativeVectorType<128 / ResultSize, ResultSize, MakeUnsigned>; auto value = bit_cast(lhs); VectorResult result; Op op; auto size = min(InputSize, ResultSize); for (size_t i = 0; i < size; ++i) result[i] = bit_cast(op(bit_cast(value[i]))); // FIXME: We shouldn't need this, but the auto-vectorizer sometimes doesn't see that we // need to pad with zeroes when InputSize < ResultSize (i.e. converting from f64x2 -> f32x4). // So we put this here to make sure. Putting [[clang::optnone]] over this function resolves // this issue, but that would be pretty unacceptable... if constexpr (InputSize < ResultSize) { constexpr size_t remaining = ResultSize - InputSize; for (size_t i = 0; i < remaining; ++i) result[i + InputSize] = 0; } return bit_cast(result); } static StringView name() { switch (ResultSize) { case 4: return "vec(32x4).cvt_op"sv; case 2: return "vec(64x2).cvt_op"sv; default: VERIFY_NOT_REACHED(); } } }; struct Floor { template auto operator()(Lhs lhs) const { if constexpr (IsSame) return floorf(lhs); else if constexpr (IsSame) return floor(lhs); else VERIFY_NOT_REACHED(); } static StringView name() { return "floor"sv; } }; struct Truncate { template auto operator()(Lhs lhs) const { if constexpr (IsSame) return truncf(lhs); else if constexpr (IsSame) return trunc(lhs); else VERIFY_NOT_REACHED(); } static StringView name() { return "truncate"sv; } }; struct NearbyIntegral { template auto operator()(Lhs lhs) const { if constexpr (IsSame) return nearbyintf(lhs); else if constexpr (IsSame) return nearbyint(lhs); else VERIFY_NOT_REACHED(); } static StringView name() { return "round"sv; } }; struct SquareRoot { template auto operator()(Lhs lhs) const { if constexpr (IsSame) return sqrtf(lhs); else if constexpr (IsSame) return sqrt(lhs); else VERIFY_NOT_REACHED(); } static StringView name() { return "sqrt"sv; } }; template struct Wrap { template Result operator()(Lhs lhs) const { return static_cast>(bit_cast>(lhs)); } static StringView name() { return "wrap"sv; } }; template struct CheckedTruncate { template AK::ErrorOr operator()(Lhs lhs) const { if (isnan(lhs) || isinf(lhs)) // "undefined", let's just trap. return "Truncation undefined behavior"sv; Lhs truncated; if constexpr (IsSame) truncated = truncf(lhs); else if constexpr (IsSame) truncated = trunc(lhs); else VERIFY_NOT_REACHED(); // FIXME: This function assumes that all values of ResultT are representable in Lhs // the assumption comes from the fact that this was used exclusively by LibJS, // which only considers values that are all representable in 'double'. if (!AK::is_within_range(truncated)) return "Truncation out of range"sv; return static_cast(truncated); } static StringView name() { return "truncate.checked"sv; } }; template struct Extend { template ResultT operator()(Lhs lhs) const { return lhs; } static StringView name() { return "extend"sv; } }; template struct Convert { template ResultT operator()(Lhs lhs) const { auto interpretation = bit_cast(lhs); return static_cast(interpretation); } static StringView name() { return "convert"sv; } }; template struct Reinterpret { template ResultT operator()(Lhs lhs) const { return bit_cast(lhs); } static StringView name() { return "reinterpret"sv; } }; struct Promote { double operator()(float lhs) const { if (isnan(lhs)) return nan(""); // FIXME: Ensure canonical NaN remains canonical return static_cast(lhs); } static StringView name() { return "promote"sv; } }; struct Demote { float operator()(double lhs) const { if (isnan(lhs)) return nanf(""); // FIXME: Ensure canonical NaN remains canonical if (isinf(lhs)) return copysignf(__builtin_huge_valf(), lhs); return static_cast(lhs); } static StringView name() { return "demote"sv; } }; template struct SignExtend { template Lhs operator()(Lhs lhs) const { auto unsigned_representation = bit_cast>(lhs); auto truncated_unsigned_representation = static_cast>(unsigned_representation); auto initial_value = bit_cast(truncated_unsigned_representation); return static_cast(initial_value); } static StringView name() { return "extend"sv; } }; template struct SaturatingTruncate { template ResultT operator()(Lhs lhs) const { if (isnan(lhs)) return 0; if (isinf(lhs)) { if (lhs < 0) return NumericLimits::min(); return NumericLimits::max(); } // FIXME: This assumes that all values in ResultT are representable in 'double'. // that assumption is not correct, which makes this function yield incorrect values // for 'edge' values of type i64. constexpr auto convert = [](ConvertT truncated_value) { if (truncated_value < NumericLimits::min()) return NumericLimits::min(); if constexpr (IsSame) { if (truncated_value >= static_cast(NumericLimits::max())) return NumericLimits::max(); } else { if (static_cast(truncated_value) >= static_cast(NumericLimits::max())) return NumericLimits::max(); } return static_cast(truncated_value); }; if constexpr (IsSame) return convert(truncf(lhs)); else return convert(trunc(lhs)); } static StringView name() { return "truncate.saturating"sv; } }; template struct SaturatingOp { template ResultT operator()(Lhs lhs, Rhs rhs) const { Op op; double result = op(lhs, rhs); if (result <= static_cast(NumericLimits::min())) { return NumericLimits::min(); } if (result >= static_cast(NumericLimits::max())) { return NumericLimits::max(); } return static_cast(result); } static StringView name() { return "saturating_op"sv; } }; }