consteval: -0.0 equals 0.0
That's what IEEE 754 requires, and WGSL does not overrule or relax that.
Various tests are updated:
- Don't expect that -0.0 is not equal to 0.0
- A floating point narrowing conversion is not required to preserve
the sign bit on -0.0
- Avoid atan2 cases that have unbounded error, particularly around +/- 0.0
Suppress VertexFormatTests for Arm Android devices when -0.0 is used.
There are similar suppressed failures on Qualcomm Android devices.
Bug: dawn:1566
Fixed: tint:2001
Change-Id: Idc6c92473a798add3f2fe3f5478e91323dd76474
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/144383
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: David Neto <dneto@google.com>
diff --git a/src/dawn/tests/end2end/VertexFormatTests.cpp b/src/dawn/tests/end2end/VertexFormatTests.cpp
index 22a85c0..a62c38f 100644
--- a/src/dawn/tests/end2end/VertexFormatTests.cpp
+++ b/src/dawn/tests/end2end/VertexFormatTests.cpp
@@ -261,7 +261,7 @@
return bitcast<f32>(fp32u);
}
- // NaN defination in IEEE 754-1985 is :
+ // NaN definition in IEEE 754-1985 is :
// - sign = either 0 or 1.
// - biased exponent = all 1 bits.
// - fraction = anything except all 0 bits (since all 0 bits represents infinity).
@@ -288,7 +288,7 @@
// Declare expected values.
vs << "var expected : array<array<" << expectedDataType << ", "
- << std::to_string(componentCount) << ">, " << std::to_string(kVertexNum) << ">;";
+ << std::to_string(componentCount) << ">, " << std::to_string(kVertexNum) << ">;\n";
// Assign each elements in expected values
// e.g. expected[0][0] = u32(1u);
// expected[0][1] = u32(2u);
@@ -307,7 +307,7 @@
<< ") / " << std::to_string(std::numeric_limits<T>::max())
<< ".0 , -1.0));\n";
} else if (isHalf) {
- // Becasue Vulkan and D3D12 handle -0.0f through bitcast have different
+ // Because Vulkan and D3D12 handle -0.0f through bitcast have different
// result (Vulkan take -0.0f as -0.0 but D3D12 take -0.0f as 0), add workaround
// for -0.0f.
if (static_cast<uint16_t>(expectedData[i * componentCount + j]) ==
@@ -339,12 +339,15 @@
if (!isInputTypeFloat) { // Integer / unsigned integer need to match exactly.
vs << " success = success && (" << testVal << " == " << expectedVal << ");\n";
} else {
+ vs << " success = success && (isNaNCustom(" << expectedVal << ") == isNaNCustom("
+ << testVal << "));\n";
+ vs << " if (!isNaNCustom(" << expectedVal << ")) {\n";
+ // Fold -0.0 into 0.0.
+ vs << " if (" << testVal << " == 0) { " << testVal << " = 0; }\n";
+ vs << " if (" << expectedVal << " == 0) { " << expectedVal << " = 0; }\n";
// TODO(shaobo.yan@intel.com) : a difference of 8 ULPs is allowed in this test
// because it is required on MacbookPro 11.5,AMD Radeon HD 8870M(on macOS 10.13.6),
// but that it might be possible to tighten.
- vs << " if (isNaNCustom(" << expectedVal << ")) {\n";
- vs << " success = success && isNaNCustom(" << testVal << ");\n";
- vs << " } else {\n";
vs << " let testValFloatToUint : u32 = bitcast<u32>(" << testVal << ");\n";
vs << " let expectedValFloatToUint : u32 = bitcast<u32>(" << expectedVal
<< ");\n";
@@ -703,6 +706,8 @@
TEST_P(VertexFormatTest, Float16x2) {
// Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
+ // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
std::vector<uint16_t> vertexData =
Float32ToFloat16(std::vector<float>({14.8f, -0.0f, 22.5f, 1.3f, +0.0f, -24.8f}));
@@ -713,22 +718,28 @@
TEST_P(VertexFormatTest, Float16x4) {
// Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
+ // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
std::vector<uint16_t> vertexData = Float32ToFloat16(std::vector<float>(
{+0.0f, -16.8f, 18.2f, -0.0f, 12.5f, 1.3f, 14.8f, -12.4f, 22.5f, -48.8f, 47.4f, -24.8f}));
DoVertexFormatTest(wgpu::VertexFormat::Float16x4, vertexData, vertexData);
}
-
-TEST_P(VertexFormatTest, Float32) {
- // TODO(dawn:1549) Fails on Qualcomm-based Android devices.
+TEST_P(VertexFormatTest, Float32_Zeros) {
+ // TODO(dawn:1566) Fails on Qualcomm-based and Arm-base Android devices. When using -0.0.
DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsQualcomm());
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
std::vector<float> vertexData = {1.3f, +0.0f, -0.0f};
DoVertexFormatTest(wgpu::VertexFormat::Float32, vertexData, vertexData);
+}
- vertexData = std::vector<float>{+1.0f, -1.0f, 18.23f};
+TEST_P(VertexFormatTest, Float32_Plain) {
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsQualcomm());
+
+ std::vector<float> vertexData = {+1.0f, -1.0f, 18.23f};
DoVertexFormatTest(wgpu::VertexFormat::Float32, vertexData, vertexData);
}
@@ -737,8 +748,10 @@
// Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
- // TODO(dawn:1549) Fails on Qualcomm-based Android devices.
+ // TODO(dawn:1566) Fails on Qualcomm-based Android devices.
DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsQualcomm());
+ // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
std::vector<float> vertexData = {18.23f, -0.0f, +0.0f, +1.0f, 1.3f, -1.0f};
@@ -748,6 +761,8 @@
TEST_P(VertexFormatTest, Float32x3) {
// Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
+ // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
std::vector<float> vertexData = {
+0.0f, -1.0f, -0.0f, 1.0f, 1.3f, 99.45f, 23.6f, -81.2f, 55.0f,
@@ -757,6 +772,8 @@
}
TEST_P(VertexFormatTest, Float32x4) {
+ // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+ DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
std::vector<float> vertexData = {
19.2f, -19.3f, +0.0f, 1.0f, -0.0f, 1.0f, 1.3f, -1.0f, 13.078f, 21.1965f, -1.1f, -1.2f,
};
diff --git a/src/tint/lang/core/constant/composite_test.cc b/src/tint/lang/core/constant/composite_test.cc
index 2f2bb10..763f357 100644
--- a/src/tint/lang/core/constant/composite_test.cc
+++ b/src/tint/lang/core/constant/composite_test.cc
@@ -30,14 +30,17 @@
auto* fPos0 = constants.Get(0_f);
auto* fNeg0 = constants.Get(-0_f);
auto* fPos1 = constants.Get(1_f);
+ auto* fNeg1 = constants.Get(-1_f);
- auto* compositeAll = constants.Composite(vec3f, Vector{fPos0, fPos0});
- auto* compositeAny = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
- auto* compositeNone = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+ auto* compositePosZeros = constants.Composite(vec3f, Vector{fPos0, fPos0});
+ auto* compositeNegZeros = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+ auto* compositeMixed = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
+ auto* compositePosNeg = constants.Composite(vec3f, Vector{fNeg1, fPos1, fPos1});
- EXPECT_TRUE(compositeAll->AllZero());
- EXPECT_FALSE(compositeAny->AllZero());
- EXPECT_FALSE(compositeNone->AllZero());
+ EXPECT_TRUE(compositePosZeros->AllZero());
+ EXPECT_TRUE(compositeNegZeros->AllZero());
+ EXPECT_FALSE(compositeMixed->AllZero());
+ EXPECT_FALSE(compositePosNeg->AllZero());
}
TEST_F(ConstantTest_Composite, AnyZero) {
@@ -46,14 +49,17 @@
auto* fPos0 = constants.Get(0_f);
auto* fNeg0 = constants.Get(-0_f);
auto* fPos1 = constants.Get(1_f);
+ auto* fNeg1 = constants.Get(-1_f);
- auto* compositeAll = constants.Composite(vec3f, Vector{fPos0, fPos0});
- auto* compositeAny = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
- auto* compositeNone = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+ auto* compositePosZeros = constants.Composite(vec3f, Vector{fPos0, fPos0});
+ auto* compositeNegZeros = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+ auto* compositeMixed = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
+ auto* compositePosNeg = constants.Composite(vec3f, Vector{fNeg1, fPos1, fPos1});
- EXPECT_TRUE(compositeAll->AnyZero());
- EXPECT_TRUE(compositeAny->AnyZero());
- EXPECT_FALSE(compositeNone->AnyZero());
+ EXPECT_TRUE(compositePosZeros->AnyZero());
+ EXPECT_TRUE(compositeNegZeros->AnyZero());
+ EXPECT_TRUE(compositeMixed->AnyZero());
+ EXPECT_FALSE(compositePosNeg->AllZero());
}
TEST_F(ConstantTest_Composite, Index) {
diff --git a/src/tint/lang/core/constant/eval.cc b/src/tint/lang/core/constant/eval.cc
index daac5a9..0840065 100644
--- a/src/tint/lang/core/constant/eval.cc
+++ b/src/tint/lang/core/constant/eval.cc
@@ -255,7 +255,7 @@
using FROM = T;
if constexpr (std::is_same_v<TO, bool>) {
// [x -> bool]
- return ctx.mgr.Get<Scalar<TO>>(target_ty, !scalar->IsPositiveZero());
+ return ctx.mgr.Get<Scalar<TO>>(target_ty, !scalar->IsZero());
} else if constexpr (std::is_same_v<FROM, bool>) {
// [bool -> x]
return ctx.mgr.Get<Scalar<TO>>(target_ty, TO(scalar->value ? 1 : 0));
diff --git a/src/tint/lang/core/constant/eval_builtin_test.cc b/src/tint/lang/core/constant/eval_builtin_test.cc
index 22e8bf4..80f87ed 100644
--- a/src/tint/lang/core/constant/eval_builtin_test.cc
+++ b/src/tint/lang/core/constant/eval_builtin_test.cc
@@ -184,14 +184,39 @@
}
}
-INSTANTIATE_TEST_SUITE_P( //
- MixedAbstractArgs,
- ConstEvalBuiltinTest,
- testing::Combine(testing::Values(core::Function::kAtan2),
- testing::ValuesIn(std::vector{
- C({0_a, -0.0_a}, kPi<AFloat>),
- C({1.0_a, 0_a}, kPiOver2<AFloat>),
- })));
+INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs_Atan2,
+ ConstEvalBuiltinTest,
+ testing::Combine(testing::Values(core::Function::kAtan2),
+ testing::ValuesIn(std::vector{
+ C({0.0_a, 1_a}, AFloat{0}),
+ C({0_a, 1.0_a}, AFloat{0}),
+ C({1_a, 0.0_a}, kPiOver2<AFloat>),
+ C({1.0_a, 0_a}, kPiOver2<AFloat>),
+ })));
+
+INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs_Max,
+ ConstEvalBuiltinTest,
+ testing::Combine(testing::Values(core::Function::kMax),
+ testing::ValuesIn(std::vector{
+ // AbstractInt first, AbstractFloat second
+ C({1_a, 2.0_a}, AFloat{2}),
+ C({-1_a, -2.0_a}, AFloat{-1}),
+ C({2_a, 0.0_a}, AFloat{2}),
+ C({-2_a, 0.0_a}, AFloat{0}),
+ C({0_a, 0.0_a}, AFloat{0}),
+ C({0_a, -0.0_a}, AFloat{0}),
+ C({-0_a, 0.0_a}, AFloat{0}),
+ C({-0_a, -0.0_a}, AFloat{0}),
+ // AbstractFloat first, AbstractInt second
+ C({1.0_a, 2_a}, AFloat{2}),
+ C({-1.0_a, -2_a}, AFloat{-1}),
+ C({2.0_a, 0_a}, AFloat{2}),
+ C({-2.0_a, 0_a}, AFloat{0}),
+ C({0.0_a, 0_a}, AFloat{0}),
+ C({0.0_a, -0_a}, AFloat{0}),
+ C({-0.0_a, 0_a}, AFloat{0}),
+ C({-0.0_a, -0_a}, AFloat{0}),
+ })));
template <typename T>
std::vector<Case> AbsCases() {
@@ -291,11 +316,14 @@
template <typename T>
std::vector<Case> Atan2Cases() {
return {
- // If y is +/-0 and x is negative or -0, +/-PI is returned
- C({T(0.0), -T(0.0)}, kPi<T>).PosOrNeg().FloatComp(),
-
- // If y is +/-0 and x is positive or +0, +/-0 is returned
- C({T(0.0), T(0.0)}, T(0.0)).PosOrNeg(),
+ // Beware that y is first: atan2(y,x)
+ //
+ // Beware of regions where the error is unbounded:
+ // At the orgin (0,0)
+ // When x has very small magnitude, since atan(y/x) is a
+ // valid formulation, and floating point division can have
+ // large error when the divisor is subnormal.
+ // When y is subnormal or infinite.
// If x is +/-0 and y is negative, -PI/2 is returned
C({-T(1.0), T(0.0)}, -kPiOver2<T>).FloatComp(), //
@@ -305,11 +333,14 @@
C({T(1.0), T(0.0)}, kPiOver2<T>).FloatComp(), //
C({T(1.0), -T(0.0)}, kPiOver2<T>).FloatComp(),
+ // If x is positive and y is 0, 0 is returned
+ C({T(0.0), T(1.0)}, T(0.0)).FloatComp(), //
+ C({-T(0.0), T(1.0)}, T(0.0)).FloatComp(),
+
// Vector tests
- C({Vec(T(0.0), T(0.0)), Vec(-T(0.0), T(0.0))}, Vec(kPi<T>, T(0.0))).PosOrNeg().FloatComp(),
- C({Vec(-T(1.0), -T(1.0)), Vec(T(0.0), -T(0.0))}, Vec(-kPiOver2<T>, -kPiOver2<T>))
- .FloatComp(),
- C({Vec(T(1.0), T(1.0)), Vec(T(0.0), -T(0.0))}, Vec(kPiOver2<T>, kPiOver2<T>)).FloatComp(),
+ C({Vec(-T(1.0), T(1.0)), Vec(T(0.0), -T(0.0))}, Vec(-kPiOver2<T>, kPiOver2<T>)).FloatComp(),
+ C({Vec(T(1.0), -T(1.0)), Vec(-T(0.0), T(0.0))}, Vec(kPiOver2<T>, -kPiOver2<T>)).FloatComp(),
+ C({Vec(T(0.0), -T(0.0)), Vec(T(1.0), T(1.0))}, Vec(T(0.0), T(0.0))).FloatComp(),
};
}
INSTANTIATE_TEST_SUITE_P( //
diff --git a/src/tint/lang/core/constant/eval_construction_test.cc b/src/tint/lang/core/constant/eval_construction_test.cc
index 4e9ae4ea..5d9cdc2 100644
--- a/src/tint/lang/core/constant/eval_construction_test.cc
+++ b/src/tint/lang/core/constant/eval_construction_test.cc
@@ -868,19 +868,19 @@
EXPECT_TRUE(vec->type()->Is<core::type::F32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
- EXPECT_FALSE(sem->ConstantValue()->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->AllZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), -0_f);
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), -0_f);
- EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), -0_f);
}
@@ -898,14 +898,14 @@
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue()->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->AllZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 0_f);
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), -0_f);
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
@@ -1021,19 +1021,19 @@
EXPECT_TRUE(vec->type()->Is<core::type::F16>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
- EXPECT_FALSE(sem->ConstantValue()->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->AllZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), -0_h);
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), -0_h);
- EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f16>(), -0_h);
}
@@ -1053,14 +1053,14 @@
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue()->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->AllZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), 0_h);
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), -0_h);
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
diff --git a/src/tint/lang/core/constant/eval_conversion_test.cc b/src/tint/lang/core/constant/eval_conversion_test.cc
index 23279d4..179f046 100644
--- a/src/tint/lang/core/constant/eval_conversion_test.cc
+++ b/src/tint/lang/core/constant/eval_conversion_test.cc
@@ -416,6 +416,10 @@
TEST_F(ConstEvalTest, Vec3_Convert_Small_f32_to_f16) {
Enable(core::Extension::kF16);
+ // These values are chosen to force underflow when converting to f16.
+ // When the result is zero, the sign bit is *not* guaranteed to be preserved.
+ // - IEEE 754 does not require it.
+ // - C++14 does not require it.
auto* expr = Call<vec3<f16>>(Call<vec3<f32>>(1e-20_f, -2e-30_f, 3e-40_f));
WrapInFunction(expr);
@@ -429,22 +433,19 @@
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue()->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->AllZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 0.0);
- EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(0)->ValueAs<AFloat>().value));
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
- EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+ EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), -0.0);
- EXPECT_TRUE(std::signbit(sem->ConstantValue()->Index(1)->ValueAs<AFloat>().value));
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 0.0);
- EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(2)->ValueAs<AFloat>().value));
}
TEST_F(ConstEvalTest, StructAbstractSplat_to_StructDifferentTypes) {
diff --git a/src/tint/lang/core/constant/scalar.h b/src/tint/lang/core/constant/scalar.h
index 2389d44..8fd51ad 100644
--- a/src/tint/lang/core/constant/scalar.h
+++ b/src/tint/lang/core/constant/scalar.h
@@ -58,10 +58,10 @@
size_t NumElements() const override { return 1; }
/// @copydoc Value::AllZero()
- bool AllZero() const override { return IsPositiveZero(); }
+ bool AllZero() const override { return IsZero(); }
/// @copydoc Value::AnyZero()
- bool AnyZero() const override { return IsPositiveZero(); }
+ bool AnyZero() const override { return IsZero(); }
/// @copydoc Value::Hash()
size_t Hash() const override { return tint::Hash(type, ValueOf()); }
@@ -84,10 +84,11 @@
}
}
- /// @returns true if `value` is a positive zero.
- inline bool IsPositiveZero() const {
+ /// @returns true if `value` is zero.
+ /// For floating point -0.0 equals 0.0, according to IEEE 754.
+ inline bool IsZero() const {
using N = UnwrapNumber<T>;
- return Number<N>(value) == Number<N>(0); // Considers sign bit
+ return Number<N>(value) == Number<N>(0);
}
/// The scalar type
diff --git a/src/tint/lang/core/constant/scalar_test.cc b/src/tint/lang/core/constant/scalar_test.cc
index e4651de..999da12 100644
--- a/src/tint/lang/core/constant/scalar_test.cc
+++ b/src/tint/lang/core/constant/scalar_test.cc
@@ -61,12 +61,12 @@
EXPECT_FALSE(u1->AllZero());
EXPECT_TRUE(fPos0->AllZero());
- EXPECT_FALSE(fNeg0->AllZero());
+ EXPECT_TRUE(fNeg0->AllZero());
EXPECT_FALSE(fPos1->AllZero());
EXPECT_FALSE(fNeg1->AllZero());
EXPECT_TRUE(f16Pos0->AllZero());
- EXPECT_FALSE(f16Neg0->AllZero());
+ EXPECT_TRUE(f16Neg0->AllZero());
EXPECT_FALSE(f16Pos1->AllZero());
EXPECT_FALSE(f16Neg1->AllZero());
@@ -74,7 +74,7 @@
EXPECT_FALSE(bt->AllZero());
EXPECT_TRUE(afPos0->AllZero());
- EXPECT_FALSE(afNeg0->AllZero());
+ EXPECT_TRUE(afNeg0->AllZero());
EXPECT_FALSE(afPos1->AllZero());
EXPECT_FALSE(afNeg1->AllZero());
@@ -121,12 +121,12 @@
EXPECT_FALSE(u1->AnyZero());
EXPECT_TRUE(fPos0->AnyZero());
- EXPECT_FALSE(fNeg0->AnyZero());
+ EXPECT_TRUE(fNeg0->AnyZero());
EXPECT_FALSE(fPos1->AnyZero());
EXPECT_FALSE(fNeg1->AnyZero());
EXPECT_TRUE(f16Pos0->AnyZero());
- EXPECT_FALSE(f16Neg0->AnyZero());
+ EXPECT_TRUE(f16Neg0->AnyZero());
EXPECT_FALSE(f16Pos1->AnyZero());
EXPECT_FALSE(f16Neg1->AnyZero());
@@ -134,7 +134,7 @@
EXPECT_FALSE(bt->AnyZero());
EXPECT_TRUE(afPos0->AnyZero());
- EXPECT_FALSE(afNeg0->AnyZero());
+ EXPECT_TRUE(afNeg0->AnyZero());
EXPECT_FALSE(afPos1->AnyZero());
EXPECT_FALSE(afNeg1->AnyZero());
diff --git a/src/tint/lang/core/constant/splat_test.cc b/src/tint/lang/core/constant/splat_test.cc
index 8e3fa47..1e7cdc3 100644
--- a/src/tint/lang/core/constant/splat_test.cc
+++ b/src/tint/lang/core/constant/splat_test.cc
@@ -36,7 +36,7 @@
auto* SpfPos1 = constants.Splat(vec3f, fPos1, 3);
EXPECT_TRUE(SpfPos0->AllZero());
- EXPECT_FALSE(SpfNeg0->AllZero());
+ EXPECT_TRUE(SpfNeg0->AllZero());
EXPECT_FALSE(SpfPos1->AllZero());
}
@@ -52,7 +52,7 @@
auto* SpfPos1 = constants.Splat(vec3f, fPos1, 3);
EXPECT_TRUE(SpfPos0->AnyZero());
- EXPECT_FALSE(SpfNeg0->AnyZero());
+ EXPECT_TRUE(SpfNeg0->AnyZero());
EXPECT_FALSE(SpfPos1->AnyZero());
}
diff --git a/src/tint/lang/core/number.h b/src/tint/lang/core/number.h
index ea2f94a..463bcb4 100644
--- a/src/tint/lang/core/number.h
+++ b/src/tint/lang/core/number.h
@@ -356,7 +356,9 @@
/// Equality operator.
/// @param a the LHS number
/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly equal. Also considers sign bit.
+/// @returns true if the numbers `a` and `b` are exactly equal.
+/// For floating point types, negative zero equals zero.
+/// IEEE 754 says "Comparison shall ignore the sign of zero (so +0 = -0)."
template <typename A, typename B>
bool operator==(Number<A> a, Number<B> b) {
// Use the highest-precision integer or floating-point type to perform the comparisons.
@@ -364,11 +366,6 @@
std::conditional_t<IsFloatingPoint<A> || IsFloatingPoint<B>, AFloat::type, AInt::type>;
auto va = static_cast<T>(a.value);
auto vb = static_cast<T>(b.value);
- if constexpr (IsFloatingPoint<T>) {
- if (std::signbit(va) != std::signbit(vb)) {
- return false;
- }
- }
return std::equal_to<T>()(va, vb);
}
@@ -577,7 +574,7 @@
template <typename FloatingPointT,
typename = tint::traits::EnableIf<IsFloatingPoint<FloatingPointT>>>
inline std::optional<FloatingPointT> CheckedDiv(FloatingPointT a, FloatingPointT b) {
- if (b == FloatingPointT{0.0} || b == FloatingPointT{-0.0}) {
+ if (b == FloatingPointT{0.0}) {
return {};
}
auto result = FloatingPointT{a.value / b.value};
@@ -624,7 +621,7 @@
template <typename FloatingPointT,
typename = tint::traits::EnableIf<IsFloatingPoint<FloatingPointT>>>
inline std::optional<FloatingPointT> CheckedMod(FloatingPointT a, FloatingPointT b) {
- if (b == FloatingPointT{0.0} || b == FloatingPointT{-0.0}) {
+ if (b == FloatingPointT{0.0}) {
return {};
}
auto result = FloatingPointT{tint::detail::Mod(a.value, b.value)};
diff --git a/src/tint/lang/core/number_test.cc b/src/tint/lang/core/number_test.cc
index be5cab5..9039841 100644
--- a/src/tint/lang/core/number_test.cc
+++ b/src/tint/lang/core/number_test.cc
@@ -70,16 +70,22 @@
EXPECT_TRUE(10_u == 10_u);
EXPECT_TRUE(0._a == 0._a);
+ EXPECT_TRUE(0._a == -0._a);
+ EXPECT_TRUE(-0._a == 0._a);
EXPECT_TRUE(-0._a == -0._a);
EXPECT_TRUE(10._a == 10._a);
EXPECT_TRUE(-10._a == -10._a);
EXPECT_TRUE(0_f == 0_f);
+ EXPECT_TRUE(0._f == -0._f);
+ EXPECT_TRUE(-0._f == 0._f);
EXPECT_TRUE(-0_f == -0_f);
EXPECT_TRUE(10_f == 10_f);
EXPECT_TRUE(-10_f == -10_f);
EXPECT_TRUE(0_h == 0_h);
+ EXPECT_TRUE(0._h == -0._h);
+ EXPECT_TRUE(-0._h == 0._h);
EXPECT_TRUE(-0_h == -0_h);
EXPECT_TRUE(10_h == 10_h);
EXPECT_TRUE(-10_h == -10_h);
@@ -104,15 +110,15 @@
EXPECT_TRUE(10_u != 11_u);
EXPECT_TRUE(11_u != 10_u);
- EXPECT_TRUE(0._a != -0._a);
- EXPECT_TRUE(-0._a != 0._a);
+ EXPECT_FALSE(0._a != -0._a);
+ EXPECT_FALSE(-0._a != 0._a);
EXPECT_TRUE(10._a != 11._a);
EXPECT_TRUE(11._a != 10._a);
EXPECT_TRUE(-10._a != -11._a);
EXPECT_TRUE(-11._a != -10._a);
- EXPECT_TRUE(0_f != -0_f);
- EXPECT_TRUE(-0_f != 0_f);
+ EXPECT_FALSE(0_f != -0_f);
+ EXPECT_FALSE(-0_f != 0_f);
EXPECT_TRUE(-0_f != -1_f);
EXPECT_TRUE(-1_f != -0_f);
EXPECT_TRUE(10_f != -10_f);
@@ -120,8 +126,8 @@
EXPECT_TRUE(10_f != 11_f);
EXPECT_TRUE(-10_f != -11_f);
- EXPECT_TRUE(0_h != -0_h);
- EXPECT_TRUE(-0_h != 0_h);
+ EXPECT_FALSE(0_h != -0_h);
+ EXPECT_FALSE(-0_h != 0_h);
EXPECT_TRUE(-0_h != -1_h);
EXPECT_TRUE(-1_h != -0_h);
EXPECT_TRUE(10_h != -10_h);
@@ -389,8 +395,10 @@
#define OVERFLOW \
{}
+// An error value. IEEE 754 exceptions map to this, including overflow,
+// invalid operation, and division by zero.
template <typename T>
-auto Overflow = std::optional<T>{};
+auto Error = std::optional<T>{};
using BinaryCheckedCase_AInt = std::tuple<std::optional<AInt>, AInt, AInt>;
using CheckedAddTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
@@ -483,8 +491,8 @@
{T(0x100), T(-0x100), T(0x200)},
{T::Highest(), T::Highest(), T(0)},
{T::Lowest(), T::Lowest(), T(0)},
- {Overflow<T>, T::Highest(), T::Highest()},
- {Overflow<T>, T::Lowest(), T::Lowest()},
+ {Error<T>, T::Highest(), T::Highest()},
+ {Error<T>, T::Lowest(), T::Lowest()},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedAddTest_Float,
@@ -552,7 +560,7 @@
{T(-0x300), T(-0x100), T(0x200)},
{T::Highest(), T::Highest(), T(0)},
{T::Lowest(), T::Lowest(), T(0)},
- {Overflow<T>, T::Lowest(), T::Highest()},
+ {Error<T>, T::Lowest(), T::Highest()},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedSubTest_Float,
@@ -631,8 +639,8 @@
{T(-2), T(-2), T(1)},
{T(0), T::Highest(), T(0)},
{T(0), T::Lowest(), -T(0)},
- {Overflow<T>, T::Highest(), T::Highest()},
- {Overflow<T>, T::Lowest(), T::Lowest()},
+ {Error<T>, T::Highest(), T::Highest()},
+ {Error<T>, T::Lowest(), T::Lowest()},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedMulTest_Float,
@@ -692,10 +700,10 @@
{T(1), T::Highest(), T::Highest()},
{T(0), T(0), T::Highest()},
{-T(0), T(0), T::Lowest()},
- {Overflow<T>, T(123), T(0)},
- {Overflow<T>, T(123), T(-0)},
- {Overflow<T>, T(-123), T(0)},
- {Overflow<T>, T(-123), T(-0)},
+ {Error<T>, T(123), T(0.0)},
+ {Error<T>, T(123), T(-0.0)},
+ {Error<T>, T(-123), T(0.0)},
+ {Error<T>, T(-123), T(-0.0)},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedDivTest_Float,
@@ -763,10 +771,10 @@
{T{2}, T{10}, -T{4}}, //
{-T{1}, -T{10}, -T{3}}, //
{-T{2}, -T{10}, -T{4}}, //
- {Overflow<T>, T(123), T(0)}, //
- {Overflow<T>, T(123), T(-0)}, //
- {Overflow<T>, T(-123), T(0)}, //
- {Overflow<T>, T(-123), T(-0)},
+ {Error<T>, T(123), T(0.0)}, //
+ {Error<T>, T(123), T(-0.0)}, //
+ {Error<T>, T(-123), T(0.0)}, //
+ {Error<T>, T(-123), T(-0.0)},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedModTest_Float,
@@ -790,28 +798,40 @@
template <typename T>
std::vector<BinaryCheckedCase_Float> CheckedPowTest_FloatCases() {
return {
- {T(0), T(0), T(1)}, //
- {T(0), T(0), T::Highest()}, //
- {T(1), T(1), T(1)}, //
- {T(1), T(1), T::Lowest()}, //
- {T(4), T(2), T(2)}, //
- {T(8), T(2), T(3)}, //
- {T(1), T(1), T::Highest()}, //
- {T(1), T(1), -T(1)}, //
- {T(0.25), T(2), -T(2)}, //
- {T(0.125), T(2), -T(3)}, //
- {T(15.625), T(2.5), T(3)}, //
- {T(11.313708498), T(2), T(3.5)}, //
- {T(24.705294220), T(2.5), T(3.5)}, //
- {T(0.0883883476), T(2), -T(3.5)}, //
- {Overflow<T>, -T(1), T(1)}, //
- {Overflow<T>, -T(1), T::Highest()}, //
- {Overflow<T>, T::Lowest(), T(1)}, //
- {Overflow<T>, T::Lowest(), T::Highest()}, //
- {Overflow<T>, T::Lowest(), T::Lowest()}, //
- {Overflow<T>, T(0), T(0)}, //
- {Overflow<T>, T(0), -T(1)}, //
- {Overflow<T>, T(0), T::Lowest()}, //
+ {T(0), T(0), T(1)}, //
+ {T(0), T(0), T::Highest()}, //
+ {T(1), T(1), T(1)}, //
+ {T(1), T(1), T::Lowest()}, //
+ {T(4), T(2), T(2)}, //
+ {T(8), T(2), T(3)}, //
+ {T(1), T(1), T::Highest()}, //
+ {T(1), T(1), -T(1)}, //
+ {T(0.25), T(2), -T(2)}, //
+ {T(0.125), T(2), -T(3)}, //
+ {T(15.625), T(2.5), T(3)}, //
+ {T(11.313708498), T(2), T(3.5)}, //
+ {T(24.705294220), T(2.5), T(3.5)}, //
+ {T(0.0883883476), T(2), -T(3.5)}, //
+ {Error<T>, -T(1), T(1)}, //
+ {Error<T>, -T(1), T::Highest()}, //
+ {Error<T>, T::Lowest(), T(1)}, //
+ {Error<T>, T::Lowest(), T::Highest()}, //
+ {Error<T>, T::Lowest(), T::Lowest()}, //
+ {Error<T>, T(0), T(0)}, //
+ {Error<T>, T(0), -T(1)}, //
+ {Error<T>, T(0), T::Lowest()}, //
+ // Check exceptional cases from IEEE 754 `powr` function
+ // which has its exceptions derived from the formulation
+ // powr(x,y) = exp(y * log(x))
+ {Error<T>, T(0.0f), T(-2)}, // (0, less-than-zero)
+ {Error<T>, T(-0.0f), T(-2)}, // (0, less-than-zero)
+ {Error<T>, T(-2), T(5)}, // (less-than-zero, finite)
+ {Error<T>, T(-2), T(0)}, // (less-than-zero, finite)
+ {Error<T>, T(-2), T(-5)}, // (less-than-zero, finite)
+ {Error<T>, T(0.0f), T(0.0f)}, // (0,0)
+ {Error<T>, T(-0.0f), T(0.0f)}, // (0,0)
+ {Error<T>, T(0.0f), T(-0.0f)}, // (0,0)
+ {Error<T>, T(-0.0), T(-0.0f)}, // (0,0)
};
}
INSTANTIATE_TEST_SUITE_P(CheckedPowTest_Float,