From 4d89ce1a680162f2239b6d934c4de416a27962f6 Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Wed, 16 Nov 2022 21:51:31 +0000 Subject: [PATCH] tint/number: add Checked* overloads for f32 and f16 Also add missing unit tests for CheckedMul of floats. Bug: tint:1581 Bug: tint:1747 Change-Id: I5d0d5d2b010803d6fd65f6feddc619cf1d071fe2 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/110170 Kokoro: Kokoro Reviewed-by: Ben Clayton Commit-Queue: Antonio Maiorano --- src/tint/number.h | 44 +++---- src/tint/number_test.cc | 252 ++++++++++++++++++++++++++-------------- 2 files changed, 190 insertions(+), 106 deletions(-) diff --git a/src/tint/number.h b/src/tint/number.h index c116ddf56f..4fa6ed7c2e 100644 --- a/src/tint/number.h +++ b/src/tint/number.h @@ -431,13 +431,14 @@ inline std::optional CheckedAdd(AInt a, AInt b) { return AInt(result); } -/// @returns a + b, or an empty optional if the resulting value overflowed the AFloat -inline std::optional CheckedAdd(AFloat a, AFloat b) { - auto result = a.value + b.value; - if (!std::isfinite(result)) { +/// @returns a + b, or an empty optional if the resulting value overflowed the float value +template >> +inline std::optional CheckedAdd(FloatingPointT a, FloatingPointT b) { + auto result = FloatingPointT{a.value + b.value}; + if (!std::isfinite(result.value)) { return {}; } - return AFloat{result}; + return result; } /// @returns a - b, or an empty optional if the resulting value overflowed the AInt @@ -462,13 +463,14 @@ inline std::optional CheckedSub(AInt a, AInt b) { return AInt(result); } -/// @returns a + b, or an empty optional if the resulting value overflowed the AFloat -inline std::optional CheckedSub(AFloat a, AFloat b) { - auto result = a.value - b.value; - if (!std::isfinite(result)) { +/// @returns a + b, or an empty optional if the resulting value overflowed the float value +template >> +inline std::optional CheckedSub(FloatingPointT a, FloatingPointT b) { + auto result = FloatingPointT{a.value - b.value}; + if (!std::isfinite(result.value)) { return {}; } - return AFloat{result}; + return result; } /// @returns a * b, or an empty optional if the resulting value overflowed the AInt @@ -505,13 +507,14 @@ inline std::optional CheckedMul(AInt a, AInt b) { return AInt(result); } -/// @returns a * b, or an empty optional if the resulting value overflowed the AFloat -inline std::optional CheckedMul(AFloat a, AFloat b) { - auto result = a.value * b.value; - if (!std::isfinite(result)) { +/// @returns a * b, or an empty optional if the resulting value overflowed the float value +template >> +inline std::optional CheckedMul(FloatingPointT a, FloatingPointT b) { + auto result = FloatingPointT{a.value * b.value}; + if (!std::isfinite(result.value)) { return {}; } - return AFloat{result}; + return result; } /// @returns a / b, or an empty optional if the resulting value overflowed the AInt @@ -527,13 +530,14 @@ inline std::optional CheckedDiv(AInt a, AInt b) { return AInt{a.value / b.value}; } -/// @returns a / b, or an empty optional if the resulting value overflowed the AFloat -inline std::optional CheckedDiv(AFloat a, AFloat b) { - auto result = a.value / b.value; - if (!std::isfinite(result)) { +/// @returns a / b, or an empty optional if the resulting value overflowed the float value +template >> +inline std::optional CheckedDiv(FloatingPointT a, FloatingPointT b) { + auto result = FloatingPointT{a.value / b.value}; + if (!std::isfinite(result.value)) { return {}; } - return AFloat{result}; + return result; } /// @returns a * b + c, or an empty optional if the value overflowed the AInt diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc index e6ebfe6a0e..f21e5319d8 100644 --- a/src/tint/number_test.cc +++ b/src/tint/number_test.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include "src/tint/program_builder.h" @@ -26,6 +27,15 @@ using namespace tint::number_suffixes; // NOLINT namespace tint { namespace { +// Concats any number of std::vectors +template +[[nodiscard]] inline auto Concat(Vec&& v1, Vecs&&... vs) { + auto total_size = v1.size() + (vs.size() + ...); + v1.reserve(total_size); + (std::move(vs.begin(), vs.end(), std::back_inserter(v1)), ...); + return std::move(v1); +} + // Next ULP up from kHighestF32 for a float64. constexpr double kHighestF32NextULP = 0x1.fffffe0000001p+127; @@ -378,16 +388,23 @@ INSTANTIATE_TEST_SUITE_P( #define OVERFLOW \ {} -using BinaryCheckedCase_AInt = std::tuple, AInt, AInt>; -using BinaryCheckedCase_AFloat = std::tuple, AFloat, AFloat>; +template +auto Overflow = std::optional{}; +using BinaryCheckedCase_AInt = std::tuple, AInt, AInt>; using CheckedAddTest_AInt = testing::TestWithParam; + +using FloatInputTypes = std::variant; +using FloatExpectedTypes = + std::variant, std::optional, std::optional>; +using BinaryCheckedCase_Float = std::tuple; + TEST_P(CheckedAddTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedAdd(a, b), expect) << std::hex << "0x" << a << " + 0x" << b; - EXPECT_EQ(CheckedAdd(b, a), expect) << std::hex << "0x" << a << " + 0x" << b; + EXPECT_TRUE(CheckedAdd(a, b) == expect) << std::hex << "0x" << a << " + 0x" << b; + EXPECT_TRUE(CheckedAdd(b, a) == expect) << std::hex << "0x" << a << " + 0x" << b; } INSTANTIATE_TEST_SUITE_P( CheckedAddTest_AInt, @@ -417,41 +434,50 @@ INSTANTIATE_TEST_SUITE_P( //////////////////////////////////////////////////////////////////////// })); -using CheckedAddTest_AFloat = testing::TestWithParam; -TEST_P(CheckedAddTest_AFloat, Test) { - auto expect = std::get<0>(GetParam()); - auto a = std::get<1>(GetParam()); - auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedAdd(a, b), expect) << std::hex << "0x" << a << " + 0x" << b; - EXPECT_EQ(CheckedAdd(b, a), expect) << std::hex << "0x" << a << " + 0x" << b; +using CheckedAddTest_Float = testing::TestWithParam; +TEST_P(CheckedAddTest_Float, Test) { + auto& p = GetParam(); + std::visit( + [&](auto&& lhs) { + using T = std::decay_t; + auto rhs = std::get(std::get<2>(p)); + auto expect = std::get>(std::get<0>(p)); + EXPECT_TRUE(CheckedAdd(lhs, rhs) == expect) + << std::hex << "0x" << lhs << " + 0x" << rhs; + EXPECT_TRUE(CheckedAdd(rhs, lhs) == expect) + << std::hex << "0x" << lhs << " + 0x" << rhs; + }, + std::get<1>(p)); } -INSTANTIATE_TEST_SUITE_P( - CheckedAddTest_AFloat, - CheckedAddTest_AFloat, - testing::ValuesIn(std::vector{ - {AFloat(0), AFloat(0), AFloat(0)}, - {AFloat(1), AFloat(1), AFloat(0)}, - {AFloat(2), AFloat(1), AFloat(1)}, - {AFloat(0), AFloat(-1), AFloat(1)}, - {AFloat(3), AFloat(2), AFloat(1)}, - {AFloat(-1), AFloat(-2), AFloat(1)}, - {AFloat(0x300), AFloat(0x100), AFloat(0x200)}, - {AFloat(0x100), AFloat(-0x100), AFloat(0x200)}, - {AFloat::Highest(), AFloat(1), AFloat(AFloat::kHighestValue - 1)}, - {AFloat::Lowest(), AFloat(-1), AFloat(AFloat::kLowestValue + 1)}, - {AFloat::Highest(), AFloat::Highest(), AFloat(0)}, - {AFloat::Lowest(), AFloat::Lowest(), AFloat(0)}, - {OVERFLOW, AFloat::Highest(), AFloat::Highest()}, - {OVERFLOW, AFloat::Lowest(), AFloat::Lowest()}, - //////////////////////////////////////////////////////////////////////// - })); +template +std::vector CheckedAddTest_FloatCases() { + return { + {T(0), T(0), T(0)}, + {T(1), T(1), T(0)}, + {T(2), T(1), T(1)}, + {T(0), T(-1), T(1)}, + {T(3), T(2), T(1)}, + {T(-1), T(-2), T(1)}, + {T(0x300), T(0x100), T(0x200)}, + {T(0x100), T(-0x100), T(0x200)}, + {T::Highest(), T::Highest(), T(0)}, + {T::Lowest(), T::Lowest(), T(0)}, + {Overflow, T::Highest(), T::Highest()}, + {Overflow, T::Lowest(), T::Lowest()}, + }; +} +INSTANTIATE_TEST_SUITE_P(CheckedAddTest_Float, + CheckedAddTest_Float, + testing::ValuesIn(Concat(CheckedAddTest_FloatCases(), + CheckedAddTest_FloatCases(), + CheckedAddTest_FloatCases()))); using CheckedSubTest_AInt = testing::TestWithParam; TEST_P(CheckedSubTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedSub(a, b), expect) << std::hex << "0x" << a << " - 0x" << b; + EXPECT_TRUE(CheckedSub(a, b) == expect) << std::hex << "0x" << a << " - 0x" << b; } INSTANTIATE_TEST_SUITE_P( CheckedSubTest_AInt, @@ -480,40 +506,48 @@ INSTANTIATE_TEST_SUITE_P( //////////////////////////////////////////////////////////////////////// })); -using CheckedSubTest_AFloat = testing::TestWithParam; -TEST_P(CheckedSubTest_AFloat, Test) { - auto expect = std::get<0>(GetParam()); - auto a = std::get<1>(GetParam()); - auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedSub(a, b), expect) << std::hex << "0x" << a << " - 0x" << b; +using CheckedSubTest_Float = testing::TestWithParam; +TEST_P(CheckedSubTest_Float, Test) { + auto& p = GetParam(); + std::visit( + [&](auto&& lhs) { + using T = std::decay_t; + auto rhs = std::get(std::get<2>(p)); + auto expect = std::get>(std::get<0>(p)); + EXPECT_TRUE(CheckedSub(lhs, rhs) == expect) + << std::hex << "0x" << lhs << " - 0x" << rhs; + }, + std::get<1>(p)); } -INSTANTIATE_TEST_SUITE_P( - CheckedSubTest_AFloat, - CheckedSubTest_AFloat, - testing::ValuesIn(std::vector{ - {AFloat(0), AFloat(0), AFloat(0)}, - {AFloat(1), AFloat(1), AFloat(0)}, - {AFloat(0), AFloat(1), AFloat(1)}, - {AFloat(-2), AFloat(-1), AFloat(1)}, - {AFloat(1), AFloat(2), AFloat(1)}, - {AFloat(-3), AFloat(-2), AFloat(1)}, - {AFloat(0x100), AFloat(0x300), AFloat(0x200)}, - {AFloat(-0x300), AFloat(-0x100), AFloat(0x200)}, - {AFloat::Highest(), AFloat(AFloat::kHighestValue - 1), AFloat(-1)}, - {AFloat::Lowest(), AFloat(AFloat::kLowestValue + 1), AFloat(1)}, - {AFloat::Highest(), AFloat::Highest(), AFloat(0)}, - {AFloat::Lowest(), AFloat::Lowest(), AFloat(0)}, - {OVERFLOW, AFloat::Lowest(), AFloat::Highest()}, - //////////////////////////////////////////////////////////////////////// - })); +template +std::vector CheckedSubTest_FloatCases() { + return { + {T(0), T(0), T(0)}, + {T(1), T(1), T(0)}, + {T(0), T(1), T(1)}, + {T(-2), T(-1), T(1)}, + {T(1), T(2), T(1)}, + {T(-3), T(-2), T(1)}, + {T(0x100), T(0x300), T(0x200)}, + {T(-0x300), T(-0x100), T(0x200)}, + {T::Highest(), T::Highest(), T(0)}, + {T::Lowest(), T::Lowest(), T(0)}, + {Overflow, T::Lowest(), T::Highest()}, + }; +} +INSTANTIATE_TEST_SUITE_P(CheckedSubTest_Float, + CheckedSubTest_Float, + testing::ValuesIn(Concat(CheckedSubTest_FloatCases(), + CheckedSubTest_FloatCases(), + CheckedSubTest_FloatCases()))); using CheckedMulTest_AInt = testing::TestWithParam; TEST_P(CheckedMulTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedMul(a, b), expect) << std::hex << "0x" << a << " * 0x" << b; - EXPECT_EQ(CheckedMul(b, a), expect) << std::hex << "0x" << a << " * 0x" << b; + EXPECT_TRUE(CheckedMul(a, b) == expect) << std::hex << "0x" << a << " * 0x" << b; + EXPECT_TRUE(CheckedMul(b, a) == expect) << std::hex << "0x" << a << " * 0x" << b; } INSTANTIATE_TEST_SUITE_P( CheckedMulTest_AInt, @@ -553,12 +587,48 @@ INSTANTIATE_TEST_SUITE_P( //////////////////////////////////////////////////////////////////////// })); +using CheckedMulTest_Float = testing::TestWithParam; +TEST_P(CheckedMulTest_Float, Test) { + auto& p = GetParam(); + std::visit( + [&](auto&& lhs) { + using T = std::decay_t; + auto rhs = std::get(std::get<2>(p)); + auto expect = std::get>(std::get<0>(p)); + EXPECT_TRUE(CheckedMul(lhs, rhs) == expect) + << std::hex << "0x" << lhs << " * 0x" << rhs; + EXPECT_TRUE(CheckedMul(rhs, lhs) == expect) + << std::hex << "0x" << lhs << " * 0x" << rhs; + }, + std::get<1>(p)); +} +template +std::vector CheckedMulTest_FloatCases() { + return { + {T(0), T(0), T(0)}, + {T(0), T(1), T(0)}, + {T(1), T(1), T(1)}, + {T(-1), T(-1), T(1)}, + {T(2), T(2), T(1)}, + {T(-2), T(-2), T(1)}, + {T(0), T::Highest(), T(0)}, + {T(0), T::Lowest(), -T(0)}, + {Overflow, T::Highest(), T::Highest()}, + {Overflow, T::Lowest(), T::Lowest()}, + }; +} +INSTANTIATE_TEST_SUITE_P(CheckedMulTest_Float, + CheckedMulTest_Float, + testing::ValuesIn(Concat(CheckedMulTest_FloatCases(), + CheckedMulTest_FloatCases(), + CheckedMulTest_FloatCases()))); + using CheckedDivTest_AInt = testing::TestWithParam; TEST_P(CheckedDivTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedDiv(a, b), expect) << std::hex << "0x" << a << " - 0x" << b; + EXPECT_TRUE(CheckedDiv(a, b) == expect) << std::hex << "0x" << a << " - 0x" << b; } INSTANTIATE_TEST_SUITE_P( CheckedDivTest_AInt, @@ -579,33 +649,43 @@ INSTANTIATE_TEST_SUITE_P( //////////////////////////////////////////////////////////////////////// })); -using CheckedDivTest_AFloat = testing::TestWithParam; -TEST_P(CheckedDivTest_AFloat, Test) { - auto expect = std::get<0>(GetParam()); - auto a = std::get<1>(GetParam()); - auto b = std::get<2>(GetParam()); - EXPECT_EQ(CheckedDiv(a, b), expect) << std::hex << "0x" << a << " - 0x" << b; +using CheckedDivTest_Float = testing::TestWithParam; +TEST_P(CheckedDivTest_Float, Test) { + auto& p = GetParam(); + std::visit( + [&](auto&& lhs) { + using T = std::decay_t; + auto rhs = std::get(std::get<2>(p)); + auto expect = std::get>(std::get<0>(p)); + EXPECT_TRUE(CheckedDiv(lhs, rhs) == expect) + << std::hex << "0x" << lhs << " / 0x" << rhs; + }, + std::get<1>(p)); } -INSTANTIATE_TEST_SUITE_P( - CheckedDivTest_AFloat, - CheckedDivTest_AFloat, - testing::ValuesIn(std::vector{ - {AFloat(0), AFloat(0), AFloat(1)}, - {AFloat(1), AFloat(1), AFloat(1)}, - {AFloat(1), AFloat(1), AFloat(1)}, - {AFloat(2), AFloat(2), AFloat(1)}, - {AFloat(2), AFloat(4), AFloat(2)}, - {AFloat::Highest(), AFloat::Highest(), AFloat(1)}, - {AFloat::Lowest(), AFloat::Lowest(), AFloat(1)}, - {AFloat(1), AFloat::Highest(), AFloat::Highest()}, - {AFloat(0), AFloat(0), AFloat::Highest()}, - {-AFloat(0), AFloat(0), AFloat::Lowest()}, - {OVERFLOW, AFloat(123), AFloat(0)}, - {OVERFLOW, AFloat(123), AFloat(-0)}, - {OVERFLOW, AFloat(-123), AFloat(0)}, - {OVERFLOW, AFloat(-123), AFloat(-0)}, - //////////////////////////////////////////////////////////////////////// - })); +template +std::vector CheckedDivTest_FloatCases() { + return { + {T(0), T(0), T(1)}, + {T(1), T(1), T(1)}, + {T(1), T(1), T(1)}, + {T(2), T(2), T(1)}, + {T(2), T(4), T(2)}, + {T::Highest(), T::Highest(), T(1)}, + {T::Lowest(), T::Lowest(), T(1)}, + {T(1), T::Highest(), T::Highest()}, + {T(0), T(0), T::Highest()}, + {-T(0), T(0), T::Lowest()}, + {Overflow, T(123), T(0)}, + {Overflow, T(123), T(-0)}, + {Overflow, T(-123), T(0)}, + {Overflow, T(-123), T(-0)}, + }; +} +INSTANTIATE_TEST_SUITE_P(CheckedDivTest_Float, + CheckedDivTest_Float, + testing::ValuesIn(Concat(CheckedDivTest_FloatCases(), + CheckedDivTest_FloatCases(), + CheckedDivTest_FloatCases()))); using TernaryCheckedCase = std::tuple, AInt, AInt, AInt>;