tint: Clamp constants to type's limits when number is unrepresentable

When converting values between two concrete types, handle the case that
the value is unrepresentable by the target type.

For integers, the converted value will be either the maximum or minimum
value for the integer type.

For floats, the converted value will be positive or negative infinity.

Bug: tint:1504
Change-Id: Ia56fb8170c0ea994632194f166062823d9507249
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91621
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
Ben Clayton 2022-05-26 08:31:45 +00:00 committed by Dawn LUCI CQ
parent d42a809e8c
commit 09373989ec
2 changed files with 109 additions and 5 deletions

View File

@ -89,14 +89,34 @@ sem::Constant::Elements Transform(const sem::Constant::Elements& in,
in); in);
} }
/// Converts and returns all the elements in `in` to the type `el_ty`, by performing a `static_cast` /// Converts and returns all the elements in `in` to the type `el_ty`.
/// on each element value. No checks will be performed that the value fits in the target type. /// If the value does not fit in the target type, and:
/// * the target type is an integer type, then the resulting value will be clamped to the integer's
/// highest or lowest value.
/// * the target type is an float type, then the resulting value will be either positive or
/// negative infinity, based on the sign of the input value.
/// @param in the input elements /// @param in the input elements
/// @param el_ty the target element type /// @param el_ty the target element type
/// @returns the elements converted to `el_ty` /// @returns the elements converted to `el_ty`
sem::Constant::Elements ConvertElements(const sem::Constant::Elements& in, const sem::Type* el_ty) { sem::Constant::Elements ConvertElements(const sem::Constant::Elements& in, const sem::Type* el_ty) {
return Transform(in, el_ty, [](auto& el_out, auto el_in) { return Transform(in, el_ty, [](auto& el_out, auto el_in) {
el_out = std::decay_t<decltype(el_out)>(el_in); using OUT = std::decay_t<decltype(el_out)>;
if (auto conv = CheckedConvert<OUT>(el_in)) {
el_out = conv.Get();
} else {
constexpr auto kInf = std::numeric_limits<double>::infinity();
switch (conv.Failure()) {
case ConversionFailure::kExceedsNegativeLimit:
el_out = IsFloatingPoint<UnwrapNumber<OUT>> ? OUT(-kInf) : OUT::kLowest;
break;
case ConversionFailure::kExceedsPositiveLimit:
el_out = IsFloatingPoint<UnwrapNumber<OUT>> ? OUT(kInf) : OUT::kHighest;
break;
case ConversionFailure::kTooSmall:
el_out = OUT(el_in < 0 ? -0.0 : 0.0);
break;
}
}
}); });
} }

View File

@ -389,7 +389,7 @@ TEST_F(ResolverConstantsTest, Vec3_MixConstruct_bool) {
EXPECT_EQ(sem->ConstantValue().Element<bool>(2), true); EXPECT_EQ(sem->ConstantValue().Element<bool>(2), true);
} }
TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_i32) { TEST_F(ResolverConstantsTest, Vec3_Convert_f32_to_i32) {
auto* expr = vec3<i32>(vec3<f32>(1.1_f, 2.2_f, 3.3_f)); auto* expr = vec3<i32>(vec3<f32>(1.1_f, 2.2_f, 3.3_f));
WrapInFunction(expr); WrapInFunction(expr);
@ -408,7 +408,7 @@ TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_i32) {
EXPECT_EQ(sem->ConstantValue().Element<AInt>(2).value, 3); EXPECT_EQ(sem->ConstantValue().Element<AInt>(2).value, 3);
} }
TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) { TEST_F(ResolverConstantsTest, Vec3_Convert_u32_to_f32) {
auto* expr = vec3<f32>(vec3<u32>(10_u, 20_u, 30_u)); auto* expr = vec3<f32>(vec3<u32>(10_u, 20_u, 30_u));
WrapInFunction(expr); WrapInFunction(expr);
@ -427,5 +427,89 @@ TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) {
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(2).value, 30.f); EXPECT_EQ(sem->ConstantValue().Element<AFloat>(2).value, 30.f);
} }
TEST_F(ResolverConstantsTest, Vec3_Convert_Large_f32_to_i32) {
auto* expr = vec3<i32>(vec3<f32>(1e10_f, -1e20_f, 1e30_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 3u);
EXPECT_EQ(sem->ConstantValue().Element<AInt>(0).value, i32::kHighest);
EXPECT_EQ(sem->ConstantValue().Element<AInt>(1).value, i32::kLowest);
EXPECT_EQ(sem->ConstantValue().Element<AInt>(2).value, i32::kHighest);
}
TEST_F(ResolverConstantsTest, Vec3_Convert_Large_f32_to_u32) {
auto* expr = vec3<u32>(vec3<f32>(1e10_f, -1e20_f, 1e30_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 3u);
EXPECT_EQ(sem->ConstantValue().Element<AInt>(0).value, u32::kHighest);
EXPECT_EQ(sem->ConstantValue().Element<AInt>(1).value, u32::kLowest);
EXPECT_EQ(sem->ConstantValue().Element<AInt>(2).value, u32::kHighest);
}
// TODO(crbug.com/tint/1502): Enable when f16 overloads are implemented
TEST_F(ResolverConstantsTest, DISABLED_Vec3_Convert_Large_f32_to_f16) {
Enable(ast::Extension::kF16);
auto* expr = vec3<f16>(vec3<f32>(0.00001_f, -0.00002_f, 0.00003_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
constexpr auto kInf = std::numeric_limits<double>::infinity();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F16>());
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F16>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 3u);
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(0).value, kInf);
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(1).value, -kInf);
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(2).value, kInf);
}
// TODO(crbug.com/tint/1502): Enable when f16 overloads are implemented
TEST_F(ResolverConstantsTest, DISABLED_Vec3_Convert_Small_f32_to_f16) {
Enable(ast::Extension::kF16);
auto* expr = vec3<f16>(vec3<f32>(1e-10_f, -1e20_f, 1e30_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F16>());
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F16>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 3u);
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(0).value, 0.0);
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(1).value, -0.0);
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(2).value, 0.0);
}
} // namespace } // namespace
} // namespace tint::resolver } // namespace tint::resolver