mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-11 22:44:04 +00:00
const eval of atan2
Bug: tint:1581 Change-Id: I6268e291540bc8c712f6925f0bcb82c5873359c7 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/96902 Commit-Queue: Antonio Maiorano <amaiorano@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
committed by
Dawn LUCI CQ
parent
f8fb94d9e8
commit
4de90f0bb1
@@ -135,6 +135,7 @@ match scalar_no_i32: f32 | f16 | u32 | bool
|
||||
match scalar_no_u32: f32 | f16 | i32 | bool
|
||||
match scalar_no_bool: f32 | f16 | i32 | u32
|
||||
match fia_fi32_f16: fa | ia | f32 | i32 | f16
|
||||
match fa_f32: fa | f32
|
||||
match fa_f32_f16: fa | f32 | f16
|
||||
match ia_iu32: ia | i32 | u32
|
||||
match fiu32_f16: f32 | i32 | u32 | f16
|
||||
@@ -370,8 +371,8 @@ fn asinh(f32) -> f32
|
||||
fn asinh<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn atan(f32) -> f32
|
||||
fn atan<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn atan2(f32, f32) -> f32
|
||||
fn atan2<N: num>(vec<N, f32>, vec<N, f32>) -> vec<N, f32>
|
||||
@const fn atan2<T: fa_f32>(T, T) -> T
|
||||
@const fn atan2<N: num, T: fa_f32>(vec<N, T>, vec<N, T>) -> vec<N, T>
|
||||
fn atanh(f32) -> f32
|
||||
fn atanh<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn ceil(f32) -> f32
|
||||
|
||||
@@ -1068,6 +1068,70 @@ TEST_P(ResolverBuiltinTest_TwoParam, Error_NoTooManyParams) {
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: no matching call to " + std::string(param.name) +
|
||||
"(i32, i32, i32)\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) + "(T, T) -> T where: T is abstract-float or f32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<T>, vecN<T>) -> vecN<T> where: T is abstract-float or f32\n");
|
||||
}
|
||||
|
||||
TEST_P(ResolverBuiltinTest_TwoParam, Error_NoParams) {
|
||||
auto param = GetParam();
|
||||
|
||||
auto* call = Call(param.name);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) + "(T, T) -> T where: T is abstract-float or f32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<T>, vecN<T>) -> vecN<T> where: T is abstract-float or f32\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverTest,
|
||||
ResolverBuiltinTest_TwoParam,
|
||||
testing::Values(BuiltinData{"atan2", BuiltinType::kAtan2}));
|
||||
|
||||
using ResolverBuiltinTest_TwoParam_NoConstEval = ResolverTestWithParam<BuiltinData>;
|
||||
TEST_P(ResolverBuiltinTest_TwoParam_NoConstEval, Scalar) {
|
||||
auto param = GetParam();
|
||||
|
||||
auto* call = Call(param.name, 1_f, 1_f);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(call), nullptr);
|
||||
EXPECT_TRUE(TypeOf(call)->is_float_scalar());
|
||||
}
|
||||
|
||||
TEST_P(ResolverBuiltinTest_TwoParam_NoConstEval, Vector) {
|
||||
auto param = GetParam();
|
||||
|
||||
auto* call = Call(param.name, vec3<f32>(1_f, 1_f, 3_f), vec3<f32>(1_f, 1_f, 3_f));
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(call), nullptr);
|
||||
EXPECT_TRUE(TypeOf(call)->is_float_vector());
|
||||
EXPECT_EQ(TypeOf(call)->As<sem::Vector>()->Width(), 3u);
|
||||
}
|
||||
|
||||
TEST_P(ResolverBuiltinTest_TwoParam_NoConstEval, Error_NoTooManyParams) {
|
||||
auto param = GetParam();
|
||||
|
||||
auto* call = Call(param.name, 1_i, 2_i, 3_i);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "error: no matching call to " + std::string(param.name) +
|
||||
"(i32, i32, i32)\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
@@ -1075,7 +1139,7 @@ TEST_P(ResolverBuiltinTest_TwoParam, Error_NoTooManyParams) {
|
||||
std::string(param.name) + "(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
|
||||
}
|
||||
|
||||
TEST_P(ResolverBuiltinTest_TwoParam, Error_NoParams) {
|
||||
TEST_P(ResolverBuiltinTest_TwoParam_NoConstEval, Error_NoParams) {
|
||||
auto param = GetParam();
|
||||
|
||||
auto* call = Call(param.name);
|
||||
@@ -1091,9 +1155,8 @@ TEST_P(ResolverBuiltinTest_TwoParam, Error_NoParams) {
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverTest,
|
||||
ResolverBuiltinTest_TwoParam,
|
||||
testing::Values(BuiltinData{"atan2", BuiltinType::kAtan2},
|
||||
BuiltinData{"pow", BuiltinType::kPow},
|
||||
ResolverBuiltinTest_TwoParam_NoConstEval,
|
||||
testing::Values(BuiltinData{"pow", BuiltinType::kPow},
|
||||
BuiltinData{"step", BuiltinType::kStep}));
|
||||
|
||||
TEST_F(ResolverBuiltinTest, Distance_Scalar) {
|
||||
|
||||
@@ -46,29 +46,49 @@ namespace tint::resolver {
|
||||
|
||||
namespace {
|
||||
|
||||
/// Helper that calls 'f' passing in `c`'s value
|
||||
template <typename F>
|
||||
auto Dispatch_ia_iu32(const sem::Constant* c, F&& f) {
|
||||
return Switch(
|
||||
c->Type(), [&](const sem::AbstractInt*) { return f(c->As<AInt>()); },
|
||||
[&](const sem::I32*) { return f(c->As<i32>()); },
|
||||
[&](const sem::U32*) { return f(c->As<u32>()); });
|
||||
/// Returns the first element of a parameter pack
|
||||
template <typename T>
|
||||
T First(T&& first, ...) {
|
||||
return std::forward<T>(first);
|
||||
}
|
||||
|
||||
/// Helper that calls 'f' passing in `c`'s value
|
||||
template <typename F>
|
||||
auto Dispatch_fia_fi32_f16(const sem::Constant* c, F&& f) {
|
||||
/// Helper that calls `f` passing in the value of all `cs`.
|
||||
/// Assumes all `cs` are of the same type.
|
||||
template <typename F, typename... CONSTANTS>
|
||||
auto Dispatch_ia_iu32(F&& f, CONSTANTS&&... cs) {
|
||||
return Switch(
|
||||
c->Type(), [&](const sem::AbstractInt*) { return f(c->As<AInt>()); },
|
||||
[&](const sem::AbstractFloat*) { return f(c->As<AFloat>()); },
|
||||
[&](const sem::F32*) { return f(c->As<f32>()); },
|
||||
[&](const sem::I32*) { return f(c->As<i32>()); },
|
||||
First(cs...)->Type(), //
|
||||
[&](const sem::AbstractInt*) { return f(cs->template As<AInt>()...); },
|
||||
[&](const sem::I32*) { return f(cs->template As<i32>()...); },
|
||||
[&](const sem::U32*) { return f(cs->template As<u32>()...); });
|
||||
}
|
||||
|
||||
/// Helper that calls `f` passing in the value of all `cs`.
|
||||
/// Assumes all `cs` are of the same type.
|
||||
template <typename F, typename... CONSTANTS>
|
||||
auto Dispatch_fia_fi32_f16(F&& f, CONSTANTS&&... cs) {
|
||||
return Switch(
|
||||
First(cs...)->Type(), //
|
||||
[&](const sem::AbstractInt*) { return f(cs->template As<AInt>()...); },
|
||||
[&](const sem::AbstractFloat*) { return f(cs->template As<AFloat>()...); },
|
||||
[&](const sem::F32*) { return f(cs->template As<f32>()...); },
|
||||
[&](const sem::I32*) { return f(cs->template As<i32>()...); },
|
||||
[&](const sem::F16*) {
|
||||
// TODO(crbug.com/tint/1502): Support const eval for f16
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
/// Helper that calls `f` passing in the value of all `cs`.
|
||||
/// Assumes all `cs` are of the same type.
|
||||
template <typename F, typename... CONSTANTS>
|
||||
auto Dispatch_fa_f32(F&& f, CONSTANTS&&... cs) {
|
||||
return Switch(
|
||||
First(cs...)->Type(), //
|
||||
[&](const sem::AbstractFloat*) { return f(cs->template As<AFloat>()...); },
|
||||
[&](const sem::F32*) { return f(cs->template As<f32>()...); });
|
||||
}
|
||||
|
||||
/// ZeroTypeDispatch is a helper for calling the function `f`, passing a single zero-value argument
|
||||
/// of the C++ type that corresponds to the sem::Type `type`. For example, calling
|
||||
/// `ZeroTypeDispatch()` with a type of `sem::I32*` will call the function f with a single argument
|
||||
@@ -427,21 +447,21 @@ const Constant* CreateComposite(ProgramBuilder& builder,
|
||||
}
|
||||
|
||||
/// TransformElements constructs a new constant by applying the transformation function 'f' on each
|
||||
/// of the most deeply nested elements of 'c'.
|
||||
template <typename F>
|
||||
const Constant* TransformElements(ProgramBuilder& builder, const sem::Constant* c, F&& f) {
|
||||
/// of the most deeply nested elements of 'cs'.
|
||||
template <typename F, typename... CONSTANTS>
|
||||
const Constant* TransformElements(ProgramBuilder& builder, F&& f, CONSTANTS&&... cs) {
|
||||
uint32_t n = 0;
|
||||
auto* ty = c->Type();
|
||||
auto* ty = First(cs...)->Type();
|
||||
auto* el_ty = sem::Type::ElementOf(ty, &n);
|
||||
if (el_ty == ty) {
|
||||
return f(c);
|
||||
return f(cs...);
|
||||
}
|
||||
utils::Vector<const sem::Constant*, 8> els;
|
||||
els.Reserve(n);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
els.Push(TransformElements(builder, c->Index(i), f));
|
||||
els.Push(TransformElements(builder, f, cs->Index(i)...));
|
||||
}
|
||||
return CreateComposite(builder, c->Type(), std::move(els));
|
||||
return CreateComposite(builder, ty, std::move(els));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -658,20 +678,23 @@ const sem::Constant* ConstEval::Bitcast(const sem::Type*, const sem::Expression*
|
||||
|
||||
const sem::Constant* ConstEval::OpComplement(const sem::Type*,
|
||||
utils::ConstVectorRef<const sem::Expression*> args) {
|
||||
return TransformElements(builder, args[0]->ConstantValue(), [&](const sem::Constant* c) {
|
||||
return Dispatch_ia_iu32(c, [&](auto i) { //
|
||||
auto transform = [&](const sem::Constant* c) {
|
||||
auto create = [&](auto i) {
|
||||
return CreateElement(builder, c->Type(), decltype(i)(~i.value));
|
||||
});
|
||||
});
|
||||
};
|
||||
return Dispatch_ia_iu32(create, c);
|
||||
};
|
||||
return TransformElements(builder, transform, args[0]->ConstantValue());
|
||||
}
|
||||
|
||||
const sem::Constant* ConstEval::OpMinus(const sem::Type*,
|
||||
utils::ConstVectorRef<const sem::Expression*> args) {
|
||||
return TransformElements(builder, args[0]->ConstantValue(), [&](const sem::Constant* c) {
|
||||
return Dispatch_fia_fi32_f16(c, [&](auto i) { //
|
||||
// For signed integrals, avoid C++ UB by not negating the smallest negative number. In
|
||||
// WGSL, this operation is well defined to return the same value, see:
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
|
||||
auto transform = [&](const sem::Constant* c) {
|
||||
auto create = [&](auto i) { //
|
||||
// For signed integrals, avoid C++ UB by not negating the
|
||||
// smallest negative number. In WGSL, this operation is well
|
||||
// defined to return the same value, see:
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
|
||||
using T = UnwrapNumber<decltype(i)>;
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
auto v = i.value;
|
||||
@@ -682,8 +705,22 @@ const sem::Constant* ConstEval::OpMinus(const sem::Type*,
|
||||
} else {
|
||||
return CreateElement(builder, c->Type(), decltype(i)(-i.value));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return Dispatch_fia_fi32_f16(create, c);
|
||||
};
|
||||
return TransformElements(builder, transform, args[0]->ConstantValue());
|
||||
}
|
||||
|
||||
const sem::Constant* ConstEval::atan2(const sem::Type*,
|
||||
utils::ConstVectorRef<const sem::Expression*> args) {
|
||||
auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
|
||||
auto create = [&](auto i, auto j) {
|
||||
return CreateElement(builder, c0->Type(), decltype(i)(std::atan2(i.value, j.value)));
|
||||
};
|
||||
return Dispatch_fa_f32(create, c0, c1);
|
||||
};
|
||||
return TransformElements(builder, transform, args[0]->ConstantValue(),
|
||||
args[1]->ConstantValue());
|
||||
}
|
||||
|
||||
utils::Result<const sem::Constant*> ConstEval::Convert(const sem::Type* target_ty,
|
||||
|
||||
@@ -187,6 +187,17 @@ class ConstEval {
|
||||
const sem::Constant* OpMinus(const sem::Type* ty,
|
||||
utils::ConstVectorRef<const sem::Expression*> args);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Builtins
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// atan2 builtin
|
||||
/// @param ty the expression type
|
||||
/// @param args the input arguments
|
||||
/// @return the result value, or null if the value cannot be calculated
|
||||
const sem::Constant* atan2(const sem::Type* ty,
|
||||
utils::ConstVectorRef<const sem::Expression*> args);
|
||||
|
||||
private:
|
||||
/// Adds the given error message to the diagnostics
|
||||
void AddError(const std::string& msg, const Source& source) const;
|
||||
|
||||
@@ -17,16 +17,73 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/builtin_type.h"
|
||||
#include "src/tint/sem/expression.h"
|
||||
#include "src/tint/sem/index_accessor_expression.h"
|
||||
#include "src/tint/sem/member_accessor_expression.h"
|
||||
#include "src/tint/sem/test_helper.h"
|
||||
#include "src/tint/utils/transform.h"
|
||||
|
||||
using namespace tint::number_suffixes; // NOLINT
|
||||
|
||||
namespace tint::resolver {
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
const auto kHighest = T(T::kHighest);
|
||||
|
||||
template <typename T>
|
||||
const auto kLowest = T(T::kLowest);
|
||||
|
||||
template <typename T>
|
||||
const auto kNaN = T(std::numeric_limits<UnwrapNumber<T>>::quiet_NaN());
|
||||
|
||||
template <typename T>
|
||||
const auto kInf = T(std::numeric_limits<UnwrapNumber<T>>::infinity());
|
||||
|
||||
template <typename T>
|
||||
const auto kPi = T(UnwrapNumber<T>(3.14159265358979323846));
|
||||
|
||||
template <typename T>
|
||||
const auto kPiOver2 = T(UnwrapNumber<T>(1.57079632679489661923));
|
||||
|
||||
template <typename T>
|
||||
const auto kPiOver4 = T(UnwrapNumber<T>(0.785398163397448309616));
|
||||
|
||||
template <typename T>
|
||||
const auto k3PiOver4 = T(UnwrapNumber<T>(2.356194490192344928846));
|
||||
|
||||
template <typename T>
|
||||
constexpr auto Negate(const Number<T>& v) {
|
||||
// For signed integrals, avoid C++ UB by not negating the smallest negative number. In
|
||||
// WGSL, this operation is well defined to return the same value, see:
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
|
||||
if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
|
||||
if (v == std::numeric_limits<T>::min()) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return -v;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto Abs(const Number<T>& v) {
|
||||
if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>) {
|
||||
return v;
|
||||
} else {
|
||||
return Number<T>(std::abs(v));
|
||||
}
|
||||
}
|
||||
|
||||
// Concats any number of std::vectors
|
||||
template <typename Vec, typename... Vecs>
|
||||
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);
|
||||
}
|
||||
|
||||
using ResolverConstEvalTest = ResolverTest;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1333,7 +1390,7 @@ TEST_F(ResolverConstEvalTest, Vec3_Convert_Large_f32_to_f16) {
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
constexpr auto kInf = std::numeric_limits<double>::infinity();
|
||||
constexpr auto kInfinity = std::numeric_limits<double>::infinity();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
@@ -1349,17 +1406,17 @@ TEST_F(ResolverConstEvalTest, Vec3_Convert_Large_f32_to_f16) {
|
||||
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
|
||||
EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
|
||||
EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
|
||||
EXPECT_EQ(sem->ConstantValue()->Index(0)->As<AFloat>(), kInf);
|
||||
EXPECT_EQ(sem->ConstantValue()->Index(0)->As<AFloat>(), kInfinity);
|
||||
|
||||
EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
|
||||
EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
|
||||
EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
|
||||
EXPECT_EQ(sem->ConstantValue()->Index(1)->As<AFloat>(), -kInf);
|
||||
EXPECT_EQ(sem->ConstantValue()->Index(1)->As<AFloat>(), -kInfinity);
|
||||
|
||||
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
|
||||
EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
|
||||
EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
|
||||
EXPECT_EQ(sem->ConstantValue()->Index(2)->As<AFloat>(), kInf);
|
||||
EXPECT_EQ(sem->ConstantValue()->Index(2)->As<AFloat>(), kInfinity);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstEvalTest, Vec3_Convert_Small_f32_to_f16) {
|
||||
@@ -2930,29 +2987,6 @@ TEST_F(ResolverConstEvalTest, MemberAccess) {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace unary_op {
|
||||
|
||||
template <typename T>
|
||||
auto Highest() {
|
||||
return T(T::kHighest);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto Lowest() {
|
||||
return T(T::kLowest);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto Negate(const Number<T>& v) {
|
||||
// For signed integrals, avoid C++ UB by not negating the smallest negative number. In
|
||||
// WGSL, this operation is well defined to return the same value, see:
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
|
||||
if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
|
||||
if (v == std::numeric_limits<T>::min()) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return -v;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct Values {
|
||||
T input;
|
||||
@@ -3036,37 +3070,37 @@ INSTANTIATE_TEST_SUITE_P(Negation,
|
||||
C(-0_a, 0_a),
|
||||
C(1_a, -1_a),
|
||||
C(-1_a, 1_a),
|
||||
C(Highest<AInt>(), -Highest<AInt>()),
|
||||
C(-Highest<AInt>(), Highest<AInt>()),
|
||||
C(Lowest<AInt>(), Negate(Lowest<AInt>())),
|
||||
C(Negate(Lowest<AInt>()), Lowest<AInt>()),
|
||||
C(kHighest<AInt>, -kHighest<AInt>),
|
||||
C(-kHighest<AInt>, kHighest<AInt>),
|
||||
C(kLowest<AInt>, Negate(kLowest<AInt>)),
|
||||
C(Negate(kLowest<AInt>), kLowest<AInt>),
|
||||
// i32
|
||||
C(0_i, -0_i),
|
||||
C(-0_i, 0_i),
|
||||
C(1_i, -1_i),
|
||||
C(-1_i, 1_i),
|
||||
C(Highest<i32>(), -Highest<i32>()),
|
||||
C(-Highest<i32>(), Highest<i32>()),
|
||||
C(Lowest<i32>(), Negate(Lowest<i32>())),
|
||||
C(Negate(Lowest<i32>()), Lowest<i32>()),
|
||||
C(kHighest<i32>, -kHighest<i32>),
|
||||
C(-kHighest<i32>, kHighest<i32>),
|
||||
C(kLowest<i32>, Negate(kLowest<i32>)),
|
||||
C(Negate(kLowest<i32>), kLowest<i32>),
|
||||
// AFloat
|
||||
C(0.0_a, -0.0_a),
|
||||
C(-0.0_a, 0.0_a),
|
||||
C(1.0_a, -1.0_a),
|
||||
C(-1.0_a, 1.0_a),
|
||||
C(Highest<AFloat>(), -Highest<AFloat>()),
|
||||
C(-Highest<AFloat>(), Highest<AFloat>()),
|
||||
C(Lowest<AFloat>(), Negate(Lowest<AFloat>())),
|
||||
C(Negate(Lowest<AFloat>()), Lowest<AFloat>()),
|
||||
C(kHighest<AFloat>, -kHighest<AFloat>),
|
||||
C(-kHighest<AFloat>, kHighest<AFloat>),
|
||||
C(kLowest<AFloat>, Negate(kLowest<AFloat>)),
|
||||
C(Negate(kLowest<AFloat>), kLowest<AFloat>),
|
||||
// f32
|
||||
C(0.0_f, -0.0_f),
|
||||
C(-0.0_f, 0.0_f),
|
||||
C(1.0_f, -1.0_f),
|
||||
C(-1.0_f, 1.0_f),
|
||||
C(Highest<f32>(), -Highest<f32>()),
|
||||
C(-Highest<f32>(), Highest<f32>()),
|
||||
C(Lowest<f32>(), Negate(Lowest<f32>())),
|
||||
C(Negate(Lowest<f32>()), Lowest<f32>()),
|
||||
C(kHighest<f32>, -kHighest<f32>),
|
||||
C(-kHighest<f32>, kHighest<f32>),
|
||||
C(kLowest<f32>, Negate(kLowest<f32>)),
|
||||
C(Negate(kLowest<f32>), kLowest<f32>),
|
||||
})));
|
||||
|
||||
// Make sure UBSan doesn't trip on C++'s undefined behaviour of negating the smallest negative
|
||||
@@ -3082,5 +3116,145 @@ TEST_F(ResolverConstEvalTest, UnaryNegateLowestAbstract) {
|
||||
|
||||
} // namespace unary_op
|
||||
|
||||
namespace builtin {
|
||||
|
||||
template <typename T>
|
||||
struct Values {
|
||||
std::vector<T> args;
|
||||
T result;
|
||||
bool result_pos_or_neg;
|
||||
};
|
||||
|
||||
struct Case {
|
||||
std::variant<Values<AInt>, Values<AFloat>, Values<u32>, Values<i32>, Values<f32>, Values<f16>>
|
||||
values;
|
||||
};
|
||||
|
||||
static std::ostream& operator<<(std::ostream& o, const Case& c) {
|
||||
std::visit(
|
||||
[&](auto&& v) {
|
||||
for (auto& e : v.args) {
|
||||
o << e << ((&e != &v.args.back()) ? " " : "");
|
||||
}
|
||||
},
|
||||
c.values);
|
||||
return o;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Case C(std::vector<T> args, T result, bool result_pos_or_neg = false) {
|
||||
return Case{Values<T>{std::move(args), result, result_pos_or_neg}};
|
||||
}
|
||||
|
||||
using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<sem::BuiltinType, Case>>;
|
||||
|
||||
TEST_P(ResolverConstEvalBuiltinTest, Test) {
|
||||
Enable(ast::Extension::kF16);
|
||||
|
||||
auto builtin = std::get<0>(GetParam());
|
||||
auto c = std::get<1>(GetParam());
|
||||
std::visit(
|
||||
[&](auto&& values) {
|
||||
using T = decltype(values.result);
|
||||
auto args = utils::Transform(values.args, [&](auto&& a) {
|
||||
return static_cast<const ast::Expression*>(Expr(a));
|
||||
});
|
||||
auto* expr = Call(sem::str(builtin), std::move(args));
|
||||
|
||||
GlobalConst("C", nullptr, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
const sem::Constant* value = sem->ConstantValue();
|
||||
ASSERT_NE(value, nullptr);
|
||||
EXPECT_TYPE(value->Type(), sem->Type());
|
||||
|
||||
auto actual = value->As<T>();
|
||||
|
||||
if constexpr (IsFloatingPoint<UnwrapNumber<T>>) {
|
||||
if (std::isnan(values.result)) {
|
||||
EXPECT_TRUE(std::isnan(actual));
|
||||
} else {
|
||||
EXPECT_FLOAT_EQ(values.result_pos_or_neg ? Abs(actual) : actual, values.result);
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(values.result_pos_or_neg ? Abs(actual) : actual, values.result);
|
||||
}
|
||||
|
||||
if constexpr (IsInteger<UnwrapNumber<T>>) {
|
||||
// Check that the constant's integer doesn't contain unexpected data in the MSBs
|
||||
// that are outside of the bit-width of T.
|
||||
EXPECT_EQ(value->As<AInt>(), AInt(values.result));
|
||||
}
|
||||
},
|
||||
c.values);
|
||||
}
|
||||
|
||||
template <typename T, bool finite_only>
|
||||
std::vector<Case> Atan2Cases() {
|
||||
std::vector<Case> cases = {
|
||||
// If y is +/-0 and x is negative or -0, +/-PI is returned
|
||||
C({T(0.0), -T(0.0)}, kPi<T>, true),
|
||||
|
||||
// If y is +/-0 and x is positive or +0, +/-0 is returned
|
||||
C({T(0.0), T(0.0)}, T(0.0), true),
|
||||
|
||||
// If x is +/-0 and y is negative, -PI/2 is returned
|
||||
C({-T(1.0), T(0.0)}, -kPiOver2<T>),
|
||||
C({-T(1.0), -T(0.0)}, -kPiOver2<T>),
|
||||
|
||||
// If x is +/-0 and y is positive, +PI/2 is returned
|
||||
C({T(1.0), T(0.0)}, kPiOver2<T>),
|
||||
C({T(1.0), -T(0.0)}, kPiOver2<T>),
|
||||
};
|
||||
|
||||
if constexpr (!finite_only) {
|
||||
std::vector<Case> non_finite_cases = {
|
||||
// If y is +/-INF and x is finite, +/-PI/2 is returned
|
||||
C({kInf<T>, T(0.0)}, kPiOver2<T>, true),
|
||||
C({-kInf<T>, T(0.0)}, kPiOver2<T>, true),
|
||||
|
||||
// If y is +/-INF and x is -INF, +/-3PI/4 is returned
|
||||
C({kInf<T>, -kInf<T>}, k3PiOver4<T>, true),
|
||||
C({-kInf<T>, -kInf<T>}, k3PiOver4<T>, true),
|
||||
|
||||
// If y is +/-INF and x is +INF, +/-PI/4 is returned
|
||||
C({kInf<T>, kInf<T>}, kPiOver4<T>, true),
|
||||
C({-kInf<T>, kInf<T>}, kPiOver4<T>, true),
|
||||
|
||||
// If x is -INF and y is finite and positive, +PI is returned
|
||||
C({T(0.0), -kInf<T>}, kPi<T>),
|
||||
|
||||
// If x is -INF and y is finite and negative, -PI is returned
|
||||
C({-T(0.0), -kInf<T>}, -kPi<T>),
|
||||
|
||||
// If x is +INF and y is finite and positive, +0 is returned
|
||||
C({T(0.0), kInf<T>}, T(0.0)),
|
||||
|
||||
// If x is +INF and y is finite and negative, -0 is returned
|
||||
C({-T(0.0), kInf<T>}, -T(0.0)),
|
||||
|
||||
// If either x is NaN or y is NaN, NaN is returned
|
||||
C({kNaN<T>, T(0.0)}, kNaN<T>),
|
||||
C({T(0.0), kNaN<T>}, kNaN<T>),
|
||||
C({kNaN<T>, kNaN<T>}, kNaN<T>),
|
||||
};
|
||||
|
||||
cases = Concat(cases, non_finite_cases);
|
||||
}
|
||||
|
||||
return cases;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P( //
|
||||
Atan2,
|
||||
ResolverConstEvalBuiltinTest,
|
||||
testing::Combine(testing::Values(sem::BuiltinType::kAtan2),
|
||||
testing::ValuesIn(Concat(Atan2Cases<AFloat, true>(), //
|
||||
Atan2Cases<f32, false>()))));
|
||||
|
||||
} // namespace builtin
|
||||
|
||||
} // namespace
|
||||
} // namespace tint::resolver
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -918,9 +918,11 @@ OpFunctionEnd
|
||||
using Builtin_Builtin_ThreeParam_Float_Test = BuiltinBuilderTestWithParam<BuiltinData>;
|
||||
TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Scalar) {
|
||||
auto param = GetParam();
|
||||
auto* expr = Call(param.name, 1_f, 1_f, 1_f);
|
||||
auto* decl = Decl(Var("a", nullptr, Expr(1_f)));
|
||||
auto* expr = Call(param.name, Expr("a"), 1_f, 1_f);
|
||||
auto* func = Func("a_func", {}, ty.void_(),
|
||||
{
|
||||
decl,
|
||||
Assign(Phony(), expr),
|
||||
});
|
||||
|
||||
@@ -929,17 +931,23 @@ TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Scalar) {
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
auto got = DumpBuilder(b);
|
||||
auto expect = R"(%7 = OpExtInstImport "GLSL.std.450"
|
||||
auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
|
||||
OpName %3 "a_func"
|
||||
OpName %7 "a"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%8 = OpConstant %6 1
|
||||
%5 = OpTypeFloat 32
|
||||
%6 = OpConstant %5 1
|
||||
%8 = OpTypePointer Function %5
|
||||
%9 = OpConstantNull %5
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpExtInst %6 %7 )" +
|
||||
%7 = OpVariable %8 Function %9
|
||||
OpStore %7 %6
|
||||
%12 = OpLoad %5 %7
|
||||
%10 = OpExtInst %5 %11 )" +
|
||||
param.op +
|
||||
R"( %8 %8 %8
|
||||
R"( %12 %6 %6
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@@ -948,9 +956,11 @@ OpFunctionEnd
|
||||
|
||||
TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Vector) {
|
||||
auto param = GetParam();
|
||||
auto* expr = Call(param.name, vec2<f32>(1_f, 1_f), vec2<f32>(1_f, 1_f), vec2<f32>(1_f, 1_f));
|
||||
auto* decl = Decl(Var("a", nullptr, vec2<f32>(1_f, 1_f)));
|
||||
auto* expr = Call(param.name, Expr("a"), vec2<f32>(1_f, 1_f), vec2<f32>(1_f, 1_f));
|
||||
auto* func = Func("a_func", {}, ty.void_(),
|
||||
{
|
||||
decl,
|
||||
Assign(Phony(), expr),
|
||||
});
|
||||
|
||||
@@ -959,19 +969,25 @@ TEST_P(Builtin_Builtin_ThreeParam_Float_Test, Call_Vector) {
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
auto got = DumpBuilder(b);
|
||||
auto expect = R"(%8 = OpExtInstImport "GLSL.std.450"
|
||||
auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
|
||||
OpName %3 "a_func"
|
||||
OpName %9 "a"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%7 = OpTypeFloat 32
|
||||
%6 = OpTypeVector %7 2
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstantComposite %6 %9 %9
|
||||
%6 = OpTypeFloat 32
|
||||
%5 = OpTypeVector %6 2
|
||||
%7 = OpConstant %6 1
|
||||
%8 = OpConstantComposite %5 %7 %7
|
||||
%10 = OpTypePointer Function %5
|
||||
%11 = OpConstantNull %5
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpExtInst %6 %8 )" +
|
||||
%9 = OpVariable %10 Function %11
|
||||
OpStore %9 %8
|
||||
%14 = OpLoad %5 %9
|
||||
%12 = OpExtInst %5 %13 )" +
|
||||
param.op +
|
||||
R"( %10 %10 %10
|
||||
R"( %14 %8 %8
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@@ -1305,9 +1321,11 @@ INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
|
||||
using Builtin_Builtin_ThreeParam_Sint_Test = BuiltinBuilderTestWithParam<BuiltinData>;
|
||||
TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Scalar) {
|
||||
auto param = GetParam();
|
||||
auto* expr = Call(param.name, 1_i, 1_i, 1_i);
|
||||
auto* decl = Decl(Var("a", nullptr, Expr(1_i)));
|
||||
auto* expr = Call(param.name, Expr("a"), 1_i, 1_i);
|
||||
auto* func = Func("a_func", {}, ty.void_(),
|
||||
{
|
||||
decl,
|
||||
Assign(Phony(), expr),
|
||||
});
|
||||
|
||||
@@ -1316,17 +1334,23 @@ TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Scalar) {
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
auto got = DumpBuilder(b);
|
||||
auto expect = R"(%7 = OpExtInstImport "GLSL.std.450"
|
||||
auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
|
||||
OpName %3 "a_func"
|
||||
OpName %7 "a"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 1
|
||||
%8 = OpConstant %6 1
|
||||
%5 = OpTypeInt 32 1
|
||||
%6 = OpConstant %5 1
|
||||
%8 = OpTypePointer Function %5
|
||||
%9 = OpConstantNull %5
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpExtInst %6 %7 )" +
|
||||
%7 = OpVariable %8 Function %9
|
||||
OpStore %7 %6
|
||||
%12 = OpLoad %5 %7
|
||||
%10 = OpExtInst %5 %11 )" +
|
||||
param.op +
|
||||
R"( %8 %8 %8
|
||||
R"( %12 %6 %6
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@@ -1335,9 +1359,11 @@ OpFunctionEnd
|
||||
|
||||
TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Vector) {
|
||||
auto param = GetParam();
|
||||
auto* expr = Call(param.name, vec2<i32>(1_i, 1_i), vec2<i32>(1_i, 1_i), vec2<i32>(1_i, 1_i));
|
||||
auto* decl = Decl(Var("a", nullptr, vec2<i32>(1_i, 1_i)));
|
||||
auto* expr = Call(param.name, Expr("a"), vec2<i32>(1_i, 1_i), vec2<i32>(1_i, 1_i));
|
||||
auto* func = Func("a_func", {}, ty.void_(),
|
||||
{
|
||||
decl,
|
||||
Assign(Phony(), expr),
|
||||
});
|
||||
|
||||
@@ -1346,19 +1372,25 @@ TEST_P(Builtin_Builtin_ThreeParam_Sint_Test, Call_Vector) {
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
auto got = DumpBuilder(b);
|
||||
auto expect = R"(%8 = OpExtInstImport "GLSL.std.450"
|
||||
auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
|
||||
OpName %3 "a_func"
|
||||
OpName %9 "a"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%7 = OpTypeInt 32 1
|
||||
%6 = OpTypeVector %7 2
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstantComposite %6 %9 %9
|
||||
%6 = OpTypeInt 32 1
|
||||
%5 = OpTypeVector %6 2
|
||||
%7 = OpConstant %6 1
|
||||
%8 = OpConstantComposite %5 %7 %7
|
||||
%10 = OpTypePointer Function %5
|
||||
%11 = OpConstantNull %5
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpExtInst %6 %8 )" +
|
||||
%9 = OpVariable %10 Function %11
|
||||
OpStore %9 %8
|
||||
%14 = OpLoad %5 %9
|
||||
%12 = OpExtInst %5 %13 )" +
|
||||
param.op +
|
||||
R"( %10 %10 %10
|
||||
R"( %14 %8 %8
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@@ -1371,9 +1403,11 @@ INSTANTIATE_TEST_SUITE_P(BuiltinBuilderTest,
|
||||
using Builtin_Builtin_ThreeParam_Uint_Test = BuiltinBuilderTestWithParam<BuiltinData>;
|
||||
TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Scalar) {
|
||||
auto param = GetParam();
|
||||
auto* expr = Call(param.name, 1_u, 1_u, 1_u);
|
||||
auto* decl = Decl(Var("a", nullptr, Expr(1_u)));
|
||||
auto* expr = Call(param.name, Expr("a"), 1_u, 1_u);
|
||||
auto* func = Func("a_func", {}, ty.void_(),
|
||||
{
|
||||
decl,
|
||||
Assign(Phony(), expr),
|
||||
});
|
||||
|
||||
@@ -1382,17 +1416,23 @@ TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Scalar) {
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
auto got = DumpBuilder(b);
|
||||
auto expect = R"(%7 = OpExtInstImport "GLSL.std.450"
|
||||
auto expect = R"(%11 = OpExtInstImport "GLSL.std.450"
|
||||
OpName %3 "a_func"
|
||||
OpName %7 "a"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 0
|
||||
%8 = OpConstant %6 1
|
||||
%5 = OpTypeInt 32 0
|
||||
%6 = OpConstant %5 1
|
||||
%8 = OpTypePointer Function %5
|
||||
%9 = OpConstantNull %5
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpExtInst %6 %7 )" +
|
||||
%7 = OpVariable %8 Function %9
|
||||
OpStore %7 %6
|
||||
%12 = OpLoad %5 %7
|
||||
%10 = OpExtInst %5 %11 )" +
|
||||
param.op +
|
||||
R"( %8 %8 %8
|
||||
R"( %12 %6 %6
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@@ -1401,9 +1441,11 @@ OpFunctionEnd
|
||||
|
||||
TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Vector) {
|
||||
auto param = GetParam();
|
||||
auto* expr = Call(param.name, vec2<u32>(1_u, 1_u), vec2<u32>(1_u, 1_u), vec2<u32>(1_u, 1_u));
|
||||
auto* decl = Decl(Var("a", nullptr, vec2<u32>(1_u, 1_u)));
|
||||
auto* expr = Call(param.name, Expr("a"), vec2<u32>(1_u, 1_u), vec2<u32>(1_u, 1_u));
|
||||
auto* func = Func("a_func", {}, ty.void_(),
|
||||
{
|
||||
decl,
|
||||
Assign(Phony(), expr),
|
||||
});
|
||||
|
||||
@@ -1412,19 +1454,25 @@ TEST_P(Builtin_Builtin_ThreeParam_Uint_Test, Call_Vector) {
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
auto got = DumpBuilder(b);
|
||||
auto expect = R"(%8 = OpExtInstImport "GLSL.std.450"
|
||||
auto expect = R"(%13 = OpExtInstImport "GLSL.std.450"
|
||||
OpName %3 "a_func"
|
||||
OpName %9 "a"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%7 = OpTypeInt 32 0
|
||||
%6 = OpTypeVector %7 2
|
||||
%9 = OpConstant %7 1
|
||||
%10 = OpConstantComposite %6 %9 %9
|
||||
%6 = OpTypeInt 32 0
|
||||
%5 = OpTypeVector %6 2
|
||||
%7 = OpConstant %6 1
|
||||
%8 = OpConstantComposite %5 %7 %7
|
||||
%10 = OpTypePointer Function %5
|
||||
%11 = OpConstantNull %5
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpExtInst %6 %8 )" +
|
||||
%9 = OpVariable %10 Function %11
|
||||
OpStore %9 %8
|
||||
%14 = OpLoad %5 %9
|
||||
%12 = OpExtInst %5 %13 )" +
|
||||
param.op +
|
||||
R"( %10 %10 %10
|
||||
R"( %14 %8 %8
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
Reference in New Issue
Block a user