tint: split up const eval unit tests into multiple files

The single file was taking too long to compile, and was becoming
difficult to work in.

Bug: tint:1711
Change-Id: Ibaaa5dd72aef02cdffe80156848d010ff84c9553
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105740
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
Antonio Maiorano 2022-10-13 19:27:30 +00:00 committed by Dawn LUCI CQ
parent c84d06e860
commit 723da2aac5
11 changed files with 4757 additions and 4604 deletions

View File

@ -1115,7 +1115,14 @@ if (tint_build_unittests) {
"resolver/call_validation_test.cc",
"resolver/compound_assignment_validation_test.cc",
"resolver/compound_statement_test.cc",
"resolver/const_eval_test.cc",
"resolver/const_eval_binary_op_test.cc",
"resolver/const_eval_builtin_test.cc",
"resolver/const_eval_construction_test.cc",
"resolver/const_eval_conversion_test.cc",
"resolver/const_eval_indexing_test.cc",
"resolver/const_eval_member_access_test.cc",
"resolver/const_eval_test.h",
"resolver/const_eval_unary_op_test.cc",
"resolver/control_block_validation_test.cc",
"resolver/dependency_graph_test.cc",
"resolver/entry_point_validation_test.cc",

View File

@ -794,7 +794,14 @@ if(TINT_BUILD_TESTS)
resolver/call_validation_test.cc
resolver/compound_assignment_validation_test.cc
resolver/compound_statement_test.cc
resolver/const_eval_test.cc
resolver/const_eval_binary_op_test.cc
resolver/const_eval_builtin_test.cc
resolver/const_eval_construction_test.cc
resolver/const_eval_conversion_test.cc
resolver/const_eval_indexing_test.cc
resolver/const_eval_member_access_test.cc
resolver/const_eval_test.h
resolver/const_eval_unary_op_test.cc
resolver/control_block_validation_test.cc
resolver/dependency_graph_test.cc
resolver/entry_point_validation_test.cc

View File

@ -0,0 +1,918 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/resolver/const_eval_test.h"
using namespace tint::number_suffixes; // NOLINT
using ::testing::HasSubstr;
namespace tint::resolver {
namespace {
// Bring in std::ostream& operator<<(std::ostream& o, const Types& types)
using resolver::operator<<;
struct Case {
Types lhs;
Types rhs;
Types expected;
bool overflow;
};
/// Creates a Case with Values of any type
template <typename T, typename U, typename V>
Case C(Value<T> lhs, Value<U> rhs, Value<V> expected, bool overflow = false) {
return Case{std::move(lhs), std::move(rhs), std::move(expected), overflow};
}
/// Convenience overload that creates a Case with just scalars
template <typename T, typename U, typename V, typename = std::enable_if_t<!IsValue<T>>>
Case C(T lhs, U rhs, V expected, bool overflow = false) {
return Case{Val(lhs), Val(rhs), Val(expected), overflow};
}
static std::ostream& operator<<(std::ostream& o, const Case& c) {
o << "lhs: " << c.lhs << ", rhs: " << c.rhs << ", expected: " << c.expected
<< ", overflow: " << c.overflow;
return o;
}
using ResolverConstEvalBinaryOpTest = ResolverTestWithParam<std::tuple<ast::BinaryOp, Case>>;
TEST_P(ResolverConstEvalBinaryOpTest, Test) {
Enable(ast::Extension::kF16);
auto op = std::get<0>(GetParam());
auto& c = std::get<1>(GetParam());
std::visit(
[&](auto&& expected) {
using T = typename std::decay_t<decltype(expected)>::ElementType;
if constexpr (std::is_same_v<T, AInt> || std::is_same_v<T, AFloat>) {
if (c.overflow) {
// Overflow is not allowed for abstract types. This is tested separately.
return;
}
}
auto* lhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.lhs);
auto* rhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.rhs);
auto* expr = create<ast::BinaryExpression>(op, lhs_expr, rhs_expr);
GlobalConst("C", expr);
auto* expected_expr = expected.Expr(*this);
GlobalConst("E", expected_expr);
ASSERT_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* expected_sem = Sem().Get(expected_expr);
const sem::Constant* expected_value = expected_sem->ConstantValue();
ASSERT_NE(expected_value, nullptr);
EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
ForEachElemPair(value, expected_value,
[&](const sem::Constant* a, const sem::Constant* b) {
EXPECT_EQ(a->As<T>(), b->As<T>());
if constexpr (IsIntegral<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(a->As<AInt>(), b->As<AInt>());
}
return HasFailure() ? Action::kStop : Action::kContinue;
});
},
c.expected);
}
INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs,
ResolverConstEvalBinaryOpTest,
testing::Combine(testing::Values(ast::BinaryOp::kAdd),
testing::ValuesIn(std::vector{
// Mixed abstract type args
C(1_a, 2.3_a, 3.3_a),
C(2.3_a, 1_a, 3.3_a),
})));
template <typename T>
std::vector<Case> OpAddIntCases() {
static_assert(IsIntegral<T>);
return {
C(T{0}, T{0}, T{0}),
C(T{1}, T{2}, T{3}),
C(T::Lowest(), T{1}, T{T::Lowest() + 1}),
C(T::Highest(), Negate(T{1}), T{T::Highest() - 1}),
C(T::Lowest(), T::Highest(), Negate(T{1})),
C(T::Highest(), T{1}, T::Lowest(), true),
C(T::Lowest(), Negate(T{1}), T::Highest(), true),
};
}
template <typename T>
std::vector<Case> OpAddFloatCases() {
static_assert(IsFloatingPoint<T>);
return {
C(T{0}, T{0}, T{0}),
C(T{1}, T{2}, T{3}),
C(T::Lowest(), T{1}, T{T::Lowest() + 1}),
C(T::Highest(), Negate(T{1}), T{T::Highest() - 1}),
C(T::Lowest(), T::Highest(), T{0}),
C(T::Highest(), T::Highest(), T::Inf(), true),
C(T::Lowest(), Negate(T::Highest()), -T::Inf(), true),
};
}
INSTANTIATE_TEST_SUITE_P(Add,
ResolverConstEvalBinaryOpTest,
testing::Combine(testing::Values(ast::BinaryOp::kAdd),
testing::ValuesIn(Concat( //
OpAddIntCases<AInt>(),
OpAddIntCases<i32>(),
OpAddIntCases<u32>(),
OpAddFloatCases<AFloat>(),
OpAddFloatCases<f32>(),
OpAddFloatCases<f16>()))));
template <typename T>
std::vector<Case> OpSubIntCases() {
static_assert(IsIntegral<T>);
return {
C(T{0}, T{0}, T{0}),
C(T{3}, T{2}, T{1}),
C(T{T::Lowest() + 1}, T{1}, T::Lowest()),
C(T{T::Highest() - 1}, Negate(T{1}), T::Highest()),
C(Negate(T{1}), T::Highest(), T::Lowest()),
C(T::Lowest(), T{1}, T::Highest(), true),
C(T::Highest(), Negate(T{1}), T::Lowest(), true),
};
}
template <typename T>
std::vector<Case> OpSubFloatCases() {
static_assert(IsFloatingPoint<T>);
return {
C(T{0}, T{0}, T{0}),
C(T{3}, T{2}, T{1}),
C(T::Highest(), T{1}, T{T::Highest() - 1}),
C(T::Lowest(), Negate(T{1}), T{T::Lowest() + 1}),
C(T{0}, T::Highest(), T::Lowest()),
C(T::Highest(), Negate(T::Highest()), T::Inf(), true),
C(T::Lowest(), T::Highest(), -T::Inf(), true),
};
}
INSTANTIATE_TEST_SUITE_P(Sub,
ResolverConstEvalBinaryOpTest,
testing::Combine(testing::Values(ast::BinaryOp::kSubtract),
testing::ValuesIn(Concat( //
OpSubIntCases<AInt>(),
OpSubIntCases<i32>(),
OpSubIntCases<u32>(),
OpSubFloatCases<AFloat>(),
OpSubFloatCases<f32>(),
OpSubFloatCases<f16>()))));
template <typename T>
std::vector<Case> OpMulScalarCases() {
return {
C(T{0}, T{0}, T{0}),
C(T{1}, T{2}, T{2}),
C(T{2}, T{3}, T{6}),
C(Negate(T{2}), T{3}, Negate(T{6})),
C(T::Highest(), T{1}, T::Highest()),
C(T::Lowest(), T{1}, T::Lowest()),
C(T::Highest(), T::Highest(), Mul(T::Highest(), T::Highest()), true),
C(T::Lowest(), T::Lowest(), Mul(T::Lowest(), T::Lowest()), true),
};
}
template <typename T>
std::vector<Case> OpMulVecCases() {
return {
// s * vec3 = vec3
C(Val(T{2.0}), Vec(T{1.25}, T{2.25}, T{3.25}), Vec(T{2.5}, T{4.5}, T{6.5})),
// vec3 * s = vec3
C(Vec(T{1.25}, T{2.25}, T{3.25}), Val(T{2.0}), Vec(T{2.5}, T{4.5}, T{6.5})),
// vec3 * vec3 = vec3
C(Vec(T{1.25}, T{2.25}, T{3.25}), Vec(T{2.0}, T{2.0}, T{2.0}), Vec(T{2.5}, T{4.5}, T{6.5})),
};
}
template <typename T>
std::vector<Case> OpMulMatCases() {
return {
// s * mat3x2 = mat3x2
C(Val(T{2.25}),
Mat({T{1.0}, T{4.0}}, //
{T{2.0}, T{5.0}}, //
{T{3.0}, T{6.0}}),
Mat({T{2.25}, T{9.0}}, //
{T{4.5}, T{11.25}}, //
{T{6.75}, T{13.5}})),
// mat3x2 * s = mat3x2
C(Mat({T{1.0}, T{4.0}}, //
{T{2.0}, T{5.0}}, //
{T{3.0}, T{6.0}}),
Val(T{2.25}),
Mat({T{2.25}, T{9.0}}, //
{T{4.5}, T{11.25}}, //
{T{6.75}, T{13.5}})),
// vec3 * mat2x3 = vec2
C(Vec(T{1.25}, T{2.25}, T{3.25}), //
Mat({T{1.0}, T{2.0}, T{3.0}}, //
{T{4.0}, T{5.0}, T{6.0}}), //
Vec(T{15.5}, T{35.75})),
// mat2x3 * vec2 = vec3
C(Mat({T{1.0}, T{2.0}, T{3.0}}, //
{T{4.0}, T{5.0}, T{6.0}}), //
Vec(T{1.25}, T{2.25}), //
Vec(T{10.25}, T{13.75}, T{17.25})),
// mat3x2 * mat2x3 = mat2x2
C(Mat({T{1.0}, T{2.0}}, //
{T{3.0}, T{4.0}}, //
{T{5.0}, T{6.0}}), //
Mat({T{1.25}, T{2.25}, T{3.25}}, //
{T{4.25}, T{5.25}, T{6.25}}), //
Mat({T{24.25}, T{31.0}}, //
{T{51.25}, T{67.0}})), //
};
}
INSTANTIATE_TEST_SUITE_P(Mul,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kMultiply),
testing::ValuesIn(Concat( //
OpMulScalarCases<AInt>(),
OpMulScalarCases<i32>(),
OpMulScalarCases<u32>(),
OpMulScalarCases<AFloat>(),
OpMulScalarCases<f32>(),
OpMulScalarCases<f16>(),
OpMulVecCases<AInt>(),
OpMulVecCases<i32>(),
OpMulVecCases<u32>(),
OpMulVecCases<AFloat>(),
OpMulVecCases<f32>(),
OpMulVecCases<f16>(),
OpMulMatCases<AFloat>(),
OpMulMatCases<f32>(),
OpMulMatCases<f16>()))));
template <typename T>
std::vector<Case> OpDivIntCases() {
std::vector<Case> r = {
C(Val(T{0}), Val(T{1}), Val(T{0})),
C(Val(T{1}), Val(T{1}), Val(T{1})),
C(Val(T{1}), Val(T{1}), Val(T{1})),
C(Val(T{2}), Val(T{1}), Val(T{2})),
C(Val(T{4}), Val(T{2}), Val(T{2})),
C(Val(T::Highest()), Val(T{1}), Val(T::Highest())),
C(Val(T::Lowest()), Val(T{1}), Val(T::Lowest())),
C(Val(T::Highest()), Val(T::Highest()), Val(T{1})),
C(Val(T{0}), Val(T::Highest()), Val(T{0})),
C(Val(T{0}), Val(T::Lowest()), Val(T{0})),
};
ConcatIntoIf<IsIntegral<T>>( //
r, std::vector<Case>{
// e1, when e2 is zero.
C(T{123}, T{0}, T{123}, true),
});
ConcatIntoIf<IsSignedIntegral<T>>( //
r, std::vector<Case>{
// e1, when e1 is the most negative value in T, and e2 is -1.
C(T::Smallest(), T{-1}, T::Smallest(), true),
});
return r;
}
template <typename T>
std::vector<Case> OpDivFloatCases() {
return {
C(Val(T{0}), Val(T{1}), Val(T{0})),
C(Val(T{1}), Val(T{1}), Val(T{1})),
C(Val(T{1}), Val(T{1}), Val(T{1})),
C(Val(T{2}), Val(T{1}), Val(T{2})),
C(Val(T{4}), Val(T{2}), Val(T{2})),
C(Val(T::Highest()), Val(T{1}), Val(T::Highest())),
C(Val(T::Lowest()), Val(T{1}), Val(T::Lowest())),
C(Val(T::Highest()), Val(T::Highest()), Val(T{1})),
C(Val(T{0}), Val(T::Highest()), Val(T{0})),
C(Val(T{0}), Val(T::Lowest()), Val(-T{0})),
C(T{123}, T{0}, T::Inf(), true),
C(T{-123}, -T{0}, T::Inf(), true),
C(T{-123}, T{0}, -T::Inf(), true),
C(T{123}, -T{0}, -T::Inf(), true),
};
}
INSTANTIATE_TEST_SUITE_P(Div,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kDivide),
testing::ValuesIn(Concat( //
OpDivIntCases<AInt>(),
OpDivIntCases<i32>(),
OpDivIntCases<u32>(),
OpDivFloatCases<AFloat>(),
OpDivFloatCases<f32>(),
OpDivFloatCases<f16>()))));
template <typename T, bool equals>
std::vector<Case> OpEqualCases() {
return {
C(Val(T{0}), Val(T{0}), Val(true == equals)),
C(Val(T{0}), Val(T{1}), Val(false == equals)),
C(Val(T{1}), Val(T{0}), Val(false == equals)),
C(Val(T{1}), Val(T{1}), Val(true == equals)),
C(Vec(T{0}, T{0}), Vec(T{0}, T{0}), Vec(true == equals, true == equals)),
C(Vec(T{1}, T{0}), Vec(T{0}, T{1}), Vec(false == equals, false == equals)),
C(Vec(T{1}, T{1}), Vec(T{0}, T{1}), Vec(false == equals, true == equals)),
};
}
INSTANTIATE_TEST_SUITE_P(Equal,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kEqual),
testing::ValuesIn(Concat( //
OpEqualCases<AInt, true>(),
OpEqualCases<i32, true>(),
OpEqualCases<u32, true>(),
OpEqualCases<AFloat, true>(),
OpEqualCases<f32, true>(),
OpEqualCases<f16, true>(),
OpEqualCases<bool, true>()))));
INSTANTIATE_TEST_SUITE_P(NotEqual,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kNotEqual),
testing::ValuesIn(Concat( //
OpEqualCases<AInt, false>(),
OpEqualCases<i32, false>(),
OpEqualCases<u32, false>(),
OpEqualCases<AFloat, false>(),
OpEqualCases<f32, false>(),
OpEqualCases<f16, false>(),
OpEqualCases<bool, false>()))));
template <typename T, bool less_than>
std::vector<Case> OpLessThanCases() {
return {
C(Val(T{0}), Val(T{0}), Val(false == less_than)),
C(Val(T{0}), Val(T{1}), Val(true == less_than)),
C(Val(T{1}), Val(T{0}), Val(false == less_than)),
C(Val(T{1}), Val(T{1}), Val(false == less_than)),
C(Vec(T{0}, T{0}), Vec(T{0}, T{0}), Vec(false == less_than, false == less_than)),
C(Vec(T{0}, T{0}), Vec(T{1}, T{1}), Vec(true == less_than, true == less_than)),
C(Vec(T{1}, T{1}), Vec(T{0}, T{0}), Vec(false == less_than, false == less_than)),
C(Vec(T{1}, T{0}), Vec(T{0}, T{1}), Vec(false == less_than, true == less_than)),
};
}
INSTANTIATE_TEST_SUITE_P(LessThan,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kLessThan),
testing::ValuesIn(Concat( //
OpLessThanCases<AInt, true>(),
OpLessThanCases<i32, true>(),
OpLessThanCases<u32, true>(),
OpLessThanCases<AFloat, true>(),
OpLessThanCases<f32, true>(),
OpLessThanCases<f16, true>()))));
INSTANTIATE_TEST_SUITE_P(GreaterThanEqual,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kGreaterThanEqual),
testing::ValuesIn(Concat( //
OpLessThanCases<AInt, false>(),
OpLessThanCases<i32, false>(),
OpLessThanCases<u32, false>(),
OpLessThanCases<AFloat, false>(),
OpLessThanCases<f32, false>(),
OpLessThanCases<f16, false>()))));
template <typename T, bool greater_than>
std::vector<Case> OpGreaterThanCases() {
return {
C(Val(T{0}), Val(T{0}), Val(false == greater_than)),
C(Val(T{0}), Val(T{1}), Val(false == greater_than)),
C(Val(T{1}), Val(T{0}), Val(true == greater_than)),
C(Val(T{1}), Val(T{1}), Val(false == greater_than)),
C(Vec(T{0}, T{0}), Vec(T{0}, T{0}), Vec(false == greater_than, false == greater_than)),
C(Vec(T{1}, T{1}), Vec(T{0}, T{0}), Vec(true == greater_than, true == greater_than)),
C(Vec(T{0}, T{0}), Vec(T{1}, T{1}), Vec(false == greater_than, false == greater_than)),
C(Vec(T{1}, T{0}), Vec(T{0}, T{1}), Vec(true == greater_than, false == greater_than)),
};
}
INSTANTIATE_TEST_SUITE_P(GreaterThan,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kGreaterThan),
testing::ValuesIn(Concat( //
OpGreaterThanCases<AInt, true>(),
OpGreaterThanCases<i32, true>(),
OpGreaterThanCases<u32, true>(),
OpGreaterThanCases<AFloat, true>(),
OpGreaterThanCases<f32, true>(),
OpGreaterThanCases<f16, true>()))));
INSTANTIATE_TEST_SUITE_P(LessThanEqual,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kLessThanEqual),
testing::ValuesIn(Concat( //
OpGreaterThanCases<AInt, false>(),
OpGreaterThanCases<i32, false>(),
OpGreaterThanCases<u32, false>(),
OpGreaterThanCases<AFloat, false>(),
OpGreaterThanCases<f32, false>(),
OpGreaterThanCases<f16, false>()))));
static std::vector<Case> OpAndBoolCases() {
return {
C(true, true, true),
C(true, false, false),
C(false, true, false),
C(false, false, false),
C(Vec(true, true), Vec(true, false), Vec(true, false)),
C(Vec(true, true), Vec(false, true), Vec(false, true)),
C(Vec(true, false), Vec(true, false), Vec(true, false)),
C(Vec(false, true), Vec(true, false), Vec(false, false)),
C(Vec(false, false), Vec(true, false), Vec(false, false)),
};
}
template <typename T>
std::vector<Case> OpAndIntCases() {
using B = BitValues<T>;
return {
C(T{0b1010}, T{0b1111}, T{0b1010}),
C(T{0b1010}, T{0b0000}, T{0b0000}),
C(T{0b1010}, T{0b0011}, T{0b0010}),
C(T{0b1010}, T{0b1100}, T{0b1000}),
C(T{0b1010}, T{0b0101}, T{0b0000}),
C(B::All, B::All, B::All),
C(B::LeftMost, B::LeftMost, B::LeftMost),
C(B::RightMost, B::RightMost, B::RightMost),
C(B::All, T{0}, T{0}),
C(T{0}, B::All, T{0}),
C(B::LeftMost, B::AllButLeftMost, T{0}),
C(B::AllButLeftMost, B::LeftMost, T{0}),
C(B::RightMost, B::AllButRightMost, T{0}),
C(B::AllButRightMost, B::RightMost, T{0}),
C(Vec(B::All, B::LeftMost, B::RightMost), //
Vec(B::All, B::All, B::All), //
Vec(B::All, B::LeftMost, B::RightMost)), //
C(Vec(B::All, B::LeftMost, B::RightMost), //
Vec(T{0}, T{0}, T{0}), //
Vec(T{0}, T{0}, T{0})), //
C(Vec(B::LeftMost, B::RightMost), //
Vec(B::AllButLeftMost, B::AllButRightMost), //
Vec(T{0}, T{0})),
};
}
INSTANTIATE_TEST_SUITE_P(And,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kAnd),
testing::ValuesIn( //
Concat(OpAndBoolCases(), //
OpAndIntCases<AInt>(),
OpAndIntCases<i32>(),
OpAndIntCases<u32>()))));
static std::vector<Case> OpOrBoolCases() {
return {
C(true, true, true),
C(true, false, true),
C(false, true, true),
C(false, false, false),
C(Vec(true, true), Vec(true, false), Vec(true, true)),
C(Vec(true, true), Vec(false, true), Vec(true, true)),
C(Vec(true, false), Vec(true, false), Vec(true, false)),
C(Vec(false, true), Vec(true, false), Vec(true, true)),
C(Vec(false, false), Vec(true, false), Vec(true, false)),
};
}
template <typename T>
std::vector<Case> OpOrIntCases() {
using B = BitValues<T>;
return {
C(T{0b1010}, T{0b1111}, T{0b1111}),
C(T{0b1010}, T{0b0000}, T{0b1010}),
C(T{0b1010}, T{0b0011}, T{0b1011}),
C(T{0b1010}, T{0b1100}, T{0b1110}),
C(T{0b1010}, T{0b0101}, T{0b1111}),
C(B::All, B::All, B::All),
C(B::LeftMost, B::LeftMost, B::LeftMost),
C(B::RightMost, B::RightMost, B::RightMost),
C(B::All, T{0}, B::All),
C(T{0}, B::All, B::All),
C(B::LeftMost, B::AllButLeftMost, B::All),
C(B::AllButLeftMost, B::LeftMost, B::All),
C(B::RightMost, B::AllButRightMost, B::All),
C(B::AllButRightMost, B::RightMost, B::All),
C(Vec(B::All, B::LeftMost, B::RightMost), //
Vec(B::All, B::All, B::All), //
Vec(B::All, B::All, B::All)), //
C(Vec(B::All, B::LeftMost, B::RightMost), //
Vec(T{0}, T{0}, T{0}), //
Vec(B::All, B::LeftMost, B::RightMost)), //
C(Vec(B::LeftMost, B::RightMost), //
Vec(B::AllButLeftMost, B::AllButRightMost), //
Vec(B::All, B::All)),
};
}
INSTANTIATE_TEST_SUITE_P(Or,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kOr),
testing::ValuesIn(Concat(OpOrBoolCases(),
OpOrIntCases<AInt>(),
OpOrIntCases<i32>(),
OpOrIntCases<u32>()))));
TEST_F(ResolverConstEvalTest, NotAndOrOfVecs) {
// const C = !((vec2(true, true) & vec2(true, false)) | vec2(false, true));
auto v1 = Vec(true, true).Expr(*this);
auto v2 = Vec(true, false).Expr(*this);
auto v3 = Vec(false, true).Expr(*this);
auto expr = Not(Or(And(v1, v2), v3));
GlobalConst("C", expr);
auto expected_expr = Vec(false, false).Expr(*this);
GlobalConst("E", expected_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* expected_sem = Sem().Get(expected_expr);
const sem::Constant* expected_value = expected_sem->ConstantValue();
ASSERT_NE(expected_value, nullptr);
EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
ForEachElemPair(value, expected_value, [&](const sem::Constant* a, const sem::Constant* b) {
EXPECT_EQ(a->As<bool>(), b->As<bool>());
return HasFailure() ? Action::kStop : Action::kContinue;
});
}
template <typename T>
std::vector<Case> XorCases() {
using B = BitValues<T>;
return {
C(T{0b1010}, T{0b1111}, T{0b0101}),
C(T{0b1010}, T{0b0000}, T{0b1010}),
C(T{0b1010}, T{0b0011}, T{0b1001}),
C(T{0b1010}, T{0b1100}, T{0b0110}),
C(T{0b1010}, T{0b0101}, T{0b1111}),
C(B::All, B::All, T{0}),
C(B::LeftMost, B::LeftMost, T{0}),
C(B::RightMost, B::RightMost, T{0}),
C(B::All, T{0}, B::All),
C(T{0}, B::All, B::All),
C(B::LeftMost, B::AllButLeftMost, B::All),
C(B::AllButLeftMost, B::LeftMost, B::All),
C(B::RightMost, B::AllButRightMost, B::All),
C(B::AllButRightMost, B::RightMost, B::All),
C(Vec(B::All, B::LeftMost, B::RightMost), //
Vec(B::All, B::All, B::All), //
Vec(T{0}, B::AllButLeftMost, B::AllButRightMost)), //
C(Vec(B::All, B::LeftMost, B::RightMost), //
Vec(T{0}, T{0}, T{0}), //
Vec(B::All, B::LeftMost, B::RightMost)), //
C(Vec(B::LeftMost, B::RightMost), //
Vec(B::AllButLeftMost, B::AllButRightMost), //
Vec(B::All, B::All)),
};
}
INSTANTIATE_TEST_SUITE_P(Xor,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kXor),
testing::ValuesIn(Concat(XorCases<AInt>(), //
XorCases<i32>(), //
XorCases<u32>()))));
template <typename T>
std::vector<Case> ShiftLeftCases() {
// Shift type is u32 for non-abstract
using ST = std::conditional_t<IsAbstract<T>, T, u32>;
using B = BitValues<T>;
return {
C(T{0b1010}, ST{0}, T{0b0000'0000'1010}), //
C(T{0b1010}, ST{1}, T{0b0000'0001'0100}), //
C(T{0b1010}, ST{2}, T{0b0000'0010'1000}), //
C(T{0b1010}, ST{3}, T{0b0000'0101'0000}), //
C(T{0b1010}, ST{4}, T{0b0000'1010'0000}), //
C(T{0b1010}, ST{5}, T{0b0001'0100'0000}), //
C(T{0b1010}, ST{6}, T{0b0010'1000'0000}), //
C(T{0b1010}, ST{7}, T{0b0101'0000'0000}), //
C(T{0b1010}, ST{8}, T{0b1010'0000'0000}), //
C(B::LeftMost, ST{0}, B::LeftMost), //
C(B::TwoLeftMost, ST{1}, B::LeftMost), // No overflow
C(B::All, ST{1}, B::AllButRightMost), // No overflow
C(B::All, ST{B::NumBits - 1}, B::LeftMost), // No overflow
C(Vec(T{0b1010}, T{0b1010}), //
Vec(ST{0}, ST{1}), //
Vec(T{0b0000'0000'1010}, T{0b0000'0001'0100})), //
C(Vec(T{0b1010}, T{0b1010}), //
Vec(ST{2}, ST{3}), //
Vec(T{0b0000'0010'1000}, T{0b0000'0101'0000})), //
C(Vec(T{0b1010}, T{0b1010}), //
Vec(ST{4}, ST{5}), //
Vec(T{0b0000'1010'0000}, T{0b0001'0100'0000})), //
C(Vec(T{0b1010}, T{0b1010}, T{0b1010}), //
Vec(ST{6}, ST{7}, ST{8}), //
Vec(T{0b0010'1000'0000}, T{0b0101'0000'0000}, T{0b1010'0000'0000})), //
};
}
INSTANTIATE_TEST_SUITE_P(ShiftLeft,
ResolverConstEvalBinaryOpTest,
testing::Combine( //
testing::Values(ast::BinaryOp::kShiftLeft),
testing::ValuesIn(Concat(ShiftLeftCases<AInt>(), //
ShiftLeftCases<i32>(), //
ShiftLeftCases<u32>()))));
// Tests for errors on overflow/underflow of binary operations with abstract numbers
struct OverflowCase {
ast::BinaryOp op;
Types lhs;
Types rhs;
};
static std::ostream& operator<<(std::ostream& o, const OverflowCase& c) {
o << ast::FriendlyName(c.op) << ", lhs: " << c.lhs << ", rhs: " << c.rhs;
return o;
}
using ResolverConstEvalBinaryOpTest_Overflow = ResolverTestWithParam<OverflowCase>;
TEST_P(ResolverConstEvalBinaryOpTest_Overflow, Test) {
Enable(ast::Extension::kF16);
auto& c = GetParam();
auto* lhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.lhs);
auto* rhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.rhs);
auto* expr = create<ast::BinaryExpression>(Source{{1, 1}}, c.op, lhs_expr, rhs_expr);
GlobalConst("C", expr);
ASSERT_FALSE(r()->Resolve());
std::string type_name = std::visit(
[&](auto&& value) {
using ValueType = std::decay_t<decltype(value)>;
return builder::FriendlyName<ValueType>();
},
c.lhs);
EXPECT_THAT(r()->error(), HasSubstr("1:1 error: '"));
EXPECT_THAT(r()->error(), HasSubstr("' cannot be represented as '" + type_name + "'"));
}
INSTANTIATE_TEST_SUITE_P(
Test,
ResolverConstEvalBinaryOpTest_Overflow,
testing::Values(
// scalar-scalar add
OverflowCase{ast::BinaryOp::kAdd, Val(AInt::Highest()), Val(1_a)},
OverflowCase{ast::BinaryOp::kAdd, Val(AInt::Lowest()), Val(-1_a)},
OverflowCase{ast::BinaryOp::kAdd, Val(AFloat::Highest()), Val(AFloat::Highest())},
OverflowCase{ast::BinaryOp::kAdd, Val(AFloat::Lowest()), Val(AFloat::Lowest())},
// scalar-scalar subtract
OverflowCase{ast::BinaryOp::kSubtract, Val(AInt::Lowest()), Val(1_a)},
OverflowCase{ast::BinaryOp::kSubtract, Val(AInt::Highest()), Val(-1_a)},
OverflowCase{ast::BinaryOp::kSubtract, Val(AFloat::Highest()), Val(AFloat::Lowest())},
OverflowCase{ast::BinaryOp::kSubtract, Val(AFloat::Lowest()), Val(AFloat::Highest())},
// scalar-scalar multiply
OverflowCase{ast::BinaryOp::kMultiply, Val(AInt::Highest()), Val(2_a)},
OverflowCase{ast::BinaryOp::kMultiply, Val(AInt::Lowest()), Val(-2_a)},
// scalar-vector multiply
OverflowCase{ast::BinaryOp::kMultiply, Val(AInt::Highest()), Vec(2_a, 1_a)},
OverflowCase{ast::BinaryOp::kMultiply, Val(AInt::Lowest()), Vec(-2_a, 1_a)},
// vector-matrix multiply
// Overflow from first multiplication of dot product of vector and matrix column 0
// i.e. (v[0] * m[0][0] + v[1] * m[0][1])
// ^
OverflowCase{ast::BinaryOp::kMultiply, //
Vec(AFloat::Highest(), 1.0_a), //
Mat({2.0_a, 1.0_a}, //
{1.0_a, 1.0_a})},
// Overflow from second multiplication of dot product of vector and matrix column 0
// i.e. (v[0] * m[0][0] + v[1] * m[0][1])
// ^
OverflowCase{ast::BinaryOp::kMultiply, //
Vec(1.0_a, AFloat::Highest()), //
Mat({1.0_a, 2.0_a}, //
{1.0_a, 1.0_a})},
// Overflow from addition of dot product of vector and matrix column 0
// i.e. (v[0] * m[0][0] + v[1] * m[0][1])
// ^
OverflowCase{ast::BinaryOp::kMultiply, //
Vec(AFloat::Highest(), AFloat::Highest()), //
Mat({1.0_a, 1.0_a}, //
{1.0_a, 1.0_a})},
// matrix-matrix multiply
// Overflow from first multiplication of dot product of lhs row 0 and rhs column 0
// i.e. m1[0][0] * m2[0][0] + m1[0][1] * m[1][0]
// ^
OverflowCase{ast::BinaryOp::kMultiply, //
Mat({AFloat::Highest(), 1.0_a}, //
{1.0_a, 1.0_a}), //
Mat({2.0_a, 1.0_a}, //
{1.0_a, 1.0_a})},
// Overflow from second multiplication of dot product of lhs row 0 and rhs column 0
// i.e. m1[0][0] * m2[0][0] + m1[0][1] * m[1][0]
// ^
OverflowCase{ast::BinaryOp::kMultiply, //
Mat({1.0_a, AFloat::Highest()}, //
{1.0_a, 1.0_a}), //
Mat({1.0_a, 1.0_a}, //
{2.0_a, 1.0_a})},
// Overflow from addition of dot product of lhs row 0 and rhs column 0
// i.e. m1[0][0] * m2[0][0] + m1[0][1] * m[1][0]
// ^
OverflowCase{ast::BinaryOp::kMultiply, //
Mat({AFloat::Highest(), 1.0_a}, //
{AFloat::Highest(), 1.0_a}), //
Mat({1.0_a, 1.0_a}, //
{1.0_a, 1.0_a})},
// Divide by zero
OverflowCase{ast::BinaryOp::kDivide, Val(123_a), Val(0_a)},
OverflowCase{ast::BinaryOp::kDivide, Val(-123_a), Val(-0_a)},
OverflowCase{ast::BinaryOp::kDivide, Val(-123_a), Val(0_a)},
OverflowCase{ast::BinaryOp::kDivide, Val(123_a), Val(-0_a)},
// Most negative value divided by -1
OverflowCase{ast::BinaryOp::kDivide, Val(AInt::Lowest()), Val(-1_a)},
// ShiftLeft of AInts that result in values not representable as AInts.
// Note that for i32/u32, these would error because shift value is larger than 32.
OverflowCase{ast::BinaryOp::kShiftLeft, //
Val(AInt{BitValues<AInt>::All}), //
Val(AInt{BitValues<AInt>::NumBits})}, //
OverflowCase{ast::BinaryOp::kShiftLeft, //
Val(AInt{BitValues<AInt>::RightMost}), //
Val(AInt{BitValues<AInt>::NumBits})}, //
OverflowCase{ast::BinaryOp::kShiftLeft, //
Val(AInt{BitValues<AInt>::AllButLeftMost}), //
Val(AInt{BitValues<AInt>::NumBits})}, //
OverflowCase{ast::BinaryOp::kShiftLeft, //
Val(AInt{BitValues<AInt>::AllButLeftMost}), //
Val(AInt{BitValues<AInt>::NumBits + 1})}, //
OverflowCase{ast::BinaryOp::kShiftLeft, //
Val(AInt{BitValues<AInt>::AllButLeftMost}), //
Val(AInt{BitValues<AInt>::NumBits + 1000})}
));
TEST_F(ResolverConstEvalTest, BinaryAbstractAddOverflow_AInt) {
GlobalConst("c", Add(Source{{1, 1}}, Expr(AInt::Highest()), 1_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"1:1 error: '9223372036854775807 + 1' cannot be represented as 'abstract-int'");
}
TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AInt) {
GlobalConst("c", Add(Source{{1, 1}}, Expr(AInt::Lowest()), -1_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"1:1 error: '-9223372036854775808 + -1' cannot be represented as 'abstract-int'");
}
TEST_F(ResolverConstEvalTest, BinaryAbstractAddOverflow_AFloat) {
GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Highest()), AFloat::Highest()));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"1:1 error: '1.79769e+308 + 1.79769e+308' cannot be represented as 'abstract-float'");
}
TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AFloat) {
GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Lowest()), AFloat::Lowest()));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"1:1 error: '-1.79769e+308 + -1.79769e+308' cannot be represented as 'abstract-float'");
}
// Mixed AInt and AFloat args to test implicit conversion to AFloat
INSTANTIATE_TEST_SUITE_P(
AbstractMixed,
ResolverConstEvalBinaryOpTest,
testing::Combine(
testing::Values(ast::BinaryOp::kAdd),
testing::Values(C(Val(1_a), Val(2.3_a), Val(3.3_a)),
C(Val(2.3_a), Val(1_a), Val(3.3_a)),
C(Val(1_a), Vec(2.3_a, 2.3_a, 2.3_a), Vec(3.3_a, 3.3_a, 3.3_a)),
C(Vec(2.3_a, 2.3_a, 2.3_a), Val(1_a), Vec(3.3_a, 3.3_a, 3.3_a)),
C(Vec(2.3_a, 2.3_a, 2.3_a), Val(1_a), Vec(3.3_a, 3.3_a, 3.3_a)),
C(Val(1_a), Vec(2.3_a, 2.3_a, 2.3_a), Vec(3.3_a, 3.3_a, 3.3_a)),
C(Mat({1_a, 2_a}, //
{1_a, 2_a}, //
{1_a, 2_a}), //
Mat({1.2_a, 2.3_a}, //
{1.2_a, 2.3_a}, //
{1.2_a, 2.3_a}), //
Mat({2.2_a, 4.3_a}, //
{2.2_a, 4.3_a}, //
{2.2_a, 4.3_a})), //
C(Mat({1.2_a, 2.3_a}, //
{1.2_a, 2.3_a}, //
{1.2_a, 2.3_a}), //
Mat({1_a, 2_a}, //
{1_a, 2_a}, //
{1_a, 2_a}), //
Mat({2.2_a, 4.3_a}, //
{2.2_a, 4.3_a}, //
{2.2_a, 4.3_a})) //
)));
// AInt left shift negative value -> error
TEST_F(ResolverConstEvalTest, BinaryAbstractShiftLeftByNegativeValue_Error) {
GlobalConst("c", Shl(Source{{1, 1}}, Expr(1_a), Expr(-1_a)));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "1:1 error: cannot shift left by a negative value");
}
// i32/u32 left shift by >= 32 -> error
using ResolverConstEvalShiftLeftConcreteGeqBitWidthError =
ResolverTestWithParam<std::tuple<Types, Types>>;
TEST_P(ResolverConstEvalShiftLeftConcreteGeqBitWidthError, Test) {
auto* lhs_expr =
std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<0>(GetParam()));
auto* rhs_expr =
std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<1>(GetParam()));
GlobalConst("c", Shl(Source{{1, 1}}, lhs_expr, rhs_expr));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"1:1 error: shift left value must be less than the bit width of the lhs, which is 32");
}
INSTANTIATE_TEST_SUITE_P(Test,
ResolverConstEvalShiftLeftConcreteGeqBitWidthError,
testing::Values( //
std::make_tuple(Val(1_i), Val(32_u)), //
std::make_tuple(Val(1_i), Val(33_u)), //
std::make_tuple(Val(1_i), Val(34_u)), //
std::make_tuple(Val(1_i), Val(99999999_u)), //
std::make_tuple(Val(1_u), Val(32_u)), //
std::make_tuple(Val(1_u), Val(33_u)), //
std::make_tuple(Val(1_u), Val(34_u)), //
std::make_tuple(Val(1_u), Val(99999999_u)) //
));
// AInt left shift results in sign change error
using ResolverConstEvalShiftLeftSignChangeError = ResolverTestWithParam<std::tuple<Types, Types>>;
TEST_P(ResolverConstEvalShiftLeftSignChangeError, Test) {
auto* lhs_expr =
std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<0>(GetParam()));
auto* rhs_expr =
std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<1>(GetParam()));
GlobalConst("c", Shl(Source{{1, 1}}, lhs_expr, rhs_expr));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "1:1 error: shift left operation results in sign change");
}
template <typename T>
std::vector<std::tuple<Types, Types>> ShiftLeftSignChangeErrorCases() {
// Shift type is u32 for non-abstract
using ST = std::conditional_t<IsAbstract<T>, T, u32>;
using B = BitValues<T>;
return {
{Val(T{0b0001}), Val(ST{B::NumBits - 1})},
{Val(T{0b0010}), Val(ST{B::NumBits - 2})},
{Val(T{0b0100}), Val(ST{B::NumBits - 3})},
{Val(T{0b1000}), Val(ST{B::NumBits - 4})},
{Val(T{0b0011}), Val(ST{B::NumBits - 2})},
{Val(T{0b0110}), Val(ST{B::NumBits - 3})},
{Val(T{0b1100}), Val(ST{B::NumBits - 4})},
{Val(B::AllButLeftMost), Val(ST{1})},
{Val(B::AllButLeftMost), Val(ST{B::NumBits - 1})},
{Val(B::LeftMost), Val(ST{1})},
{Val(B::LeftMost), Val(ST{B::NumBits - 1})},
};
}
INSTANTIATE_TEST_SUITE_P(Test,
ResolverConstEvalShiftLeftSignChangeError,
testing::ValuesIn(Concat( //
ShiftLeftSignChangeErrorCases<AInt>(),
ShiftLeftSignChangeErrorCases<i32>(),
ShiftLeftSignChangeErrorCases<u32>())));
} // namespace
} // namespace tint::resolver

View File

@ -0,0 +1,302 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/resolver/const_eval_test.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
// Bring in std::ostream& operator<<(std::ostream& o, const Types& types)
using resolver::operator<<;
struct Case {
Case(utils::VectorRef<Types> in_args, Types in_expected)
: args(std::move(in_args)), expected(std::move(in_expected)) {}
/// Expected value may be positive or negative
Case& PosOrNeg() {
expected_pos_or_neg = true;
return *this;
}
/// Expected value should be compared using FLOAT_EQ instead of EQ
Case& FloatComp() {
float_compare = true;
return *this;
}
utils::Vector<Types, 8> args;
Types expected;
bool expected_pos_or_neg = false;
bool float_compare = false;
};
static std::ostream& operator<<(std::ostream& o, const Case& c) {
o << "args: ";
for (auto& a : c.args) {
o << a << ", ";
}
o << "expected: " << c.expected << ", expected_pos_or_neg: " << c.expected_pos_or_neg;
return o;
}
/// Creates a Case with Values for args and result
static Case C(std::initializer_list<Types> args, Types result) {
return Case{utils::Vector<Types, 8>{args}, std::move(result)};
}
/// Convenience overload that creates a Case with just scalars
using ScalarTypes = std::variant<AInt, AFloat, u32, i32, f32, f16>;
static Case C(std::initializer_list<ScalarTypes> sargs, ScalarTypes sresult) {
utils::Vector<Types, 8> args;
for (auto& sa : sargs) {
std::visit([&](auto&& v) { return args.Push(Val(v)); }, sa);
}
Types result = Val(0_a);
std::visit([&](auto&& v) { result = Val(v); }, sresult);
return Case{std::move(args), std::move(result)};
}
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());
utils::Vector<const ast::Expression*, 8> args;
for (auto& a : c.args) {
std::visit([&](auto&& v) { args.Push(v.Expr(*this)); }, a);
}
std::visit(
[&](auto&& expected) {
using T = typename std::decay_t<decltype(expected)>::ElementType;
auto* expr = Call(sem::str(builtin), std::move(args));
GlobalConst("C", expr);
auto* expected_expr = expected.Expr(*this);
GlobalConst("E", expected_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* expected_sem = Sem().Get(expected_expr);
const sem::Constant* expected_value = expected_sem->ConstantValue();
ASSERT_NE(expected_value, nullptr);
EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
ForEachElemPair(value, expected_value,
[&](const sem::Constant* a, const sem::Constant* b) {
auto v = a->As<T>();
auto e = b->As<T>();
if constexpr (std::is_same_v<bool, T>) {
EXPECT_EQ(v, e);
} else if constexpr (IsFloatingPoint<T>) {
if (std::isnan(e)) {
EXPECT_TRUE(std::isnan(v));
} else {
auto vf = (c.expected_pos_or_neg ? Abs(v) : v);
if (c.float_compare) {
EXPECT_FLOAT_EQ(vf, e);
} else {
EXPECT_EQ(vf, e);
}
}
} else {
EXPECT_EQ((c.expected_pos_or_neg ? Abs(v) : v), e);
// 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(a->As<AInt>(), b->As<AInt>());
}
return HasFailure() ? Action::kStop : Action::kContinue;
});
},
c.expected);
}
INSTANTIATE_TEST_SUITE_P( //
MixedAbstractArgs,
ResolverConstEvalBuiltinTest,
testing::Combine(testing::Values(sem::BuiltinType::kAtan2),
testing::ValuesIn(std::vector{
C({0_a, -0.0_a}, kPi<AFloat>),
C({1.0_a, 0_a}, kPiOver2<AFloat>),
})));
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>).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(),
// If x is +/-0 and y is negative, -PI/2 is returned
C({-T(1.0), T(0.0)}, -kPiOver2<T>).FloatComp(), //
C({-T(1.0), -T(0.0)}, -kPiOver2<T>).FloatComp(),
// If x is +/-0 and y is positive, +PI/2 is returned
C({T(1.0), T(0.0)}, kPiOver2<T>).FloatComp(), //
C({T(1.0), -T(0.0)}, kPiOver2<T>).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(),
};
if constexpr (!finite_only) {
std::vector<Case> non_finite_cases = {
// If y is +/-INF and x is finite, +/-PI/2 is returned
C({T::Inf(), T(0.0)}, kPiOver2<T>).PosOrNeg().FloatComp(),
C({-T::Inf(), T(0.0)}, kPiOver2<T>).PosOrNeg().FloatComp(),
// If y is +/-INF and x is -INF, +/-3PI/4 is returned
C({T::Inf(), -T::Inf()}, k3PiOver4<T>).PosOrNeg().FloatComp(),
C({-T::Inf(), -T::Inf()}, k3PiOver4<T>).PosOrNeg().FloatComp(),
// If y is +/-INF and x is +INF, +/-PI/4 is returned
C({T::Inf(), T::Inf()}, kPiOver4<T>).PosOrNeg().FloatComp(),
C({-T::Inf(), T::Inf()}, kPiOver4<T>).PosOrNeg().FloatComp(),
// If x is -INF and y is finite and positive, +PI is returned
C({T(0.0), -T::Inf()}, kPi<T>).FloatComp(),
// If x is -INF and y is finite and negative, -PI is returned
C({-T(0.0), -T::Inf()}, -kPi<T>).FloatComp(),
// If x is +INF and y is finite and positive, +0 is returned
C({T(0.0), T::Inf()}, T(0.0)),
// If x is +INF and y is finite and negative, -0 is returned
C({-T(0.0), T::Inf()}, -T(0.0)),
// If either x is NaN or y is NaN, NaN is returned
C({T::NaN(), T(0.0)}, T::NaN()),
C({T(0.0), T::NaN()}, T::NaN()),
C({T::NaN(), T::NaN()}, T::NaN()),
// Vector tests
C({Vec(T::Inf(), -T::Inf(), T::Inf(), -T::Inf()), //
Vec(T(0.0), T(0.0), -T::Inf(), -T::Inf())}, //
Vec(kPiOver2<T>, kPiOver2<T>, k3PiOver4<T>, k3PiOver4<T>))
.PosOrNeg()
.FloatComp(),
};
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>(),
Atan2Cases<f16, false>()))));
template <typename T>
std::vector<Case> ClampCases() {
return {
C({T(0), T(0), T(0)}, T(0)),
C({T(0), T(42), T::Highest()}, T(42)),
C({T::Lowest(), T(0), T(42)}, T(0)),
C({T(0), T::Lowest(), T::Highest()}, T(0)),
C({T(0), T::Highest(), T::Lowest()}, T::Lowest()),
C({T::Highest(), T::Highest(), T::Highest()}, T::Highest()),
C({T::Lowest(), T::Lowest(), T::Lowest()}, T::Lowest()),
C({T::Highest(), T::Lowest(), T::Highest()}, T::Highest()),
C({T::Lowest(), T::Lowest(), T::Highest()}, T::Lowest()),
// Vector tests
C({Vec(T(0), T(0)), //
Vec(T(0), T(42)), //
Vec(T(0), T::Highest())}, //
Vec(T(0), T(42))), //
C({Vec(T::Lowest(), T(0), T(0)), //
Vec(T(0), T::Lowest(), T::Highest()), //
Vec(T(42), T::Highest(), T::Lowest())}, //
Vec(T(0), T(0), T::Lowest())),
};
}
INSTANTIATE_TEST_SUITE_P( //
Clamp,
ResolverConstEvalBuiltinTest,
testing::Combine(testing::Values(sem::BuiltinType::kClamp),
testing::ValuesIn(Concat(ClampCases<AInt>(), //
ClampCases<i32>(),
ClampCases<u32>(),
ClampCases<AFloat>(),
ClampCases<f32>(),
ClampCases<f16>()))));
template <typename T>
std::vector<Case> SelectCases() {
return {
C({Val(T{1}), Val(T{2}), Val(false)}, Val(T{1})),
C({Val(T{1}), Val(T{2}), Val(true)}, Val(T{2})),
C({Val(T{2}), Val(T{1}), Val(false)}, Val(T{2})),
C({Val(T{2}), Val(T{1}), Val(true)}, Val(T{1})),
C({Vec(T{1}, T{2}), Vec(T{3}, T{4}), Vec(false, false)}, Vec(T{1}, T{2})),
C({Vec(T{1}, T{2}), Vec(T{3}, T{4}), Vec(false, true)}, Vec(T{1}, T{4})),
C({Vec(T{1}, T{2}), Vec(T{3}, T{4}), Vec(true, false)}, Vec(T{3}, T{2})),
C({Vec(T{1}, T{2}), Vec(T{3}, T{4}), Vec(true, true)}, Vec(T{3}, T{4})),
C({Vec(T{1}, T{1}, T{2}, T{2}), //
Vec(T{2}, T{2}, T{1}, T{1}), //
Vec(false, true, false, true)}, //
Vec(T{1}, T{2}, T{2}, T{1})), //
};
}
static std::vector<Case> SelectBoolCases() {
return {
C({Val(true), Val(false), Val(false)}, Val(true)),
C({Val(true), Val(false), Val(true)}, Val(false)),
C({Val(false), Val(true), Val(true)}, Val(true)),
C({Val(false), Val(true), Val(false)}, Val(false)),
C({Vec(true, true, false, false), //
Vec(false, false, true, true), //
Vec(false, true, true, false)}, //
Vec(true, false, true, false)), //
};
}
INSTANTIATE_TEST_SUITE_P( //
Select,
ResolverConstEvalBuiltinTest,
testing::Combine(testing::Values(sem::BuiltinType::kSelect),
testing::ValuesIn(Concat(SelectCases<AInt>(), //
SelectCases<i32>(),
SelectCases<u32>(),
SelectCases<AFloat>(),
SelectCases<f32>(),
SelectCases<f16>(),
SelectBoolCases()))));
} // namespace
} // namespace tint::resolver

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,529 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/resolver/const_eval_test.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
using Scalar = std::variant< //
builder::Value<AInt>,
builder::Value<AFloat>,
builder::Value<u32>,
builder::Value<i32>,
builder::Value<f32>,
builder::Value<f16>,
builder::Value<bool>>;
static std::ostream& operator<<(std::ostream& o, const Scalar& scalar) {
std::visit(
[&](auto&& v) {
using ValueType = std::decay_t<decltype(v)>;
o << ValueType::DataType::Name() << "(";
for (auto& a : v.args.values) {
o << std::get<typename ValueType::ElementType>(a);
if (&a != &v.args.values.Back()) {
o << ", ";
}
}
o << ")";
},
scalar);
return o;
}
enum class Kind {
kScalar,
kVector,
};
static std::ostream& operator<<(std::ostream& o, const Kind& k) {
switch (k) {
case Kind::kScalar:
return o << "scalar";
case Kind::kVector:
return o << "vector";
}
return o << "<unknown>";
}
struct Case {
Scalar input;
Scalar expected;
builder::CreatePtrs type;
bool unrepresentable = false;
};
static std::ostream& operator<<(std::ostream& o, const Case& c) {
if (c.unrepresentable) {
o << "[unrepresentable] input: " << c.input;
} else {
o << "input: " << c.input << ", expected: " << c.expected;
}
return o << ", type: " << c.type;
}
template <typename TO, typename FROM>
Case Success(FROM input, TO expected) {
return {builder::Val(input), builder::Val(expected), builder::CreatePtrsFor<TO>()};
}
template <typename TO, typename FROM>
Case Unrepresentable(FROM input) {
return {builder::Val(input), builder::Val(0_i), builder::CreatePtrsFor<TO>(),
/* unrepresentable */ true};
}
using ResolverConstEvalConvTest = ResolverTestWithParam<std::tuple<Kind, Case>>;
TEST_P(ResolverConstEvalConvTest, Test) {
const auto& kind = std::get<0>(GetParam());
const auto& input = std::get<1>(GetParam()).input;
const auto& expected = std::get<1>(GetParam()).expected;
const auto& type = std::get<1>(GetParam()).type;
const auto unrepresentable = std::get<1>(GetParam()).unrepresentable;
auto* input_val = std::visit([&](auto val) { return val.Expr(*this); }, input);
auto* expr = Construct(type.ast(*this), input_val);
if (kind == Kind::kVector) {
expr = Construct(ty.vec(nullptr, 3), expr);
}
WrapInFunction(expr);
auto* target_sem_ty = type.sem(*this);
if (kind == Kind::kVector) {
target_sem_ty = create<sem::Vector>(target_sem_ty, 3u);
}
if (unrepresentable) {
ASSERT_FALSE(r()->Resolve());
EXPECT_THAT(r()->error(), testing::HasSubstr("cannot be represented as"));
} else {
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TYPE(sem->Type(), target_sem_ty);
ASSERT_NE(sem->ConstantValue(), nullptr);
EXPECT_TYPE(sem->ConstantValue()->Type(), target_sem_ty);
auto expected_values = std::visit([&](auto&& val) { return val.args; }, expected);
if (kind == Kind::kVector) {
expected_values.values.Push(expected_values.values[0]);
expected_values.values.Push(expected_values.values[0]);
}
auto got_values = ScalarArgsFrom(sem->ConstantValue());
EXPECT_EQ(expected_values, got_values);
}
}
INSTANTIATE_TEST_SUITE_P(ScalarAndVector,
ResolverConstEvalConvTest,
testing::Combine(testing::Values(Kind::kScalar, Kind::kVector),
testing::ValuesIn({
// TODO(crbug.com/tint/1502): Add f16 tests
// i32 -> u32
Success(0_i, 0_u),
Success(1_i, 1_u),
Success(-1_i, 0xffffffff_u),
Success(2_i, 2_u),
Success(-2_i, 0xfffffffe_u),
// i32 -> f32
Success(0_i, 0_f),
Success(1_i, 1_f),
Success(-1_i, -1_f),
Success(2_i, 2_f),
Success(-2_i, -2_f),
// i32 -> bool
Success(0_i, false),
Success(1_i, true),
Success(-1_i, true),
Success(2_i, true),
Success(-2_i, true),
// u32 -> i32
Success(0_u, 0_i),
Success(1_u, 1_i),
Success(0xffffffff_u, -1_i),
Success(2_u, 2_i),
Success(0xfffffffe_u, -2_i),
// u32 -> f32
Success(0_u, 0_f),
Success(1_u, 1_f),
Success(2_u, 2_f),
Success(0xffffffff_u, 0xffffffff_f),
// u32 -> bool
Success(0_u, false),
Success(1_u, true),
Success(2_u, true),
Success(0xffffffff_u, true),
// f32 -> i32
Success(0_f, 0_i),
Success(1_f, 1_i),
Success(2_f, 2_i),
Success(1e20_f, i32::Highest()),
Success(-1e20_f, i32::Lowest()),
// f32 -> u32
Success(0_f, 0_i),
Success(1_f, 1_i),
Success(-1_f, u32::Lowest()),
Success(2_f, 2_i),
Success(1e20_f, u32::Highest()),
Success(-1e20_f, u32::Lowest()),
// f32 -> bool
Success(0_f, false),
Success(1_f, true),
Success(-1_f, true),
Success(2_f, true),
Success(1e20_f, true),
Success(-1e20_f, true),
// abstract-int -> i32
Success(0_a, 0_i),
Success(1_a, 1_i),
Success(-1_a, -1_i),
Success(0x7fffffff_a, i32::Highest()),
Success(-0x80000000_a, i32::Lowest()),
Unrepresentable<i32>(0x80000000_a),
// abstract-int -> u32
Success(0_a, 0_u),
Success(1_a, 1_u),
Success(0xffffffff_a, 0xffffffff_u),
Unrepresentable<u32>(0x100000000_a),
Unrepresentable<u32>(-1_a),
// abstract-int -> f32
Success(0_a, 0_f),
Success(1_a, 1_f),
Success(0xffffffff_a, 0xffffffff_f),
Success(0x100000000_a, 0x100000000_f),
Success(-0x100000000_a, -0x100000000_f),
Success(0x7fffffffffffffff_a, 0x7fffffffffffffff_f),
Success(-0x7fffffffffffffff_a, -0x7fffffffffffffff_f),
// abstract-int -> bool
Success(0_a, false),
Success(1_a, true),
Success(0xffffffff_a, true),
Success(0x100000000_a, true),
Success(-0x100000000_a, true),
Success(0x7fffffffffffffff_a, true),
Success(-0x7fffffffffffffff_a, true),
// abstract-float -> i32
Success(0.0_a, 0_i),
Success(1.0_a, 1_i),
Success(-1.0_a, -1_i),
Success(AFloat(0x7fffffff), i32::Highest()),
Success(-AFloat(0x80000000), i32::Lowest()),
Unrepresentable<i32>(0x80000000_a),
// abstract-float -> u32
Success(0.0_a, 0_u),
Success(1.0_a, 1_u),
Success(AFloat(0xffffffff), 0xffffffff_u),
Unrepresentable<u32>(AFloat(0x100000000)),
Unrepresentable<u32>(AFloat(-1)),
// abstract-float -> f32
Success(0.0_a, 0_f),
Success(1.0_a, 1_f),
Success(AFloat(0xffffffff), 0xffffffff_f),
Success(AFloat(0x100000000), 0x100000000_f),
Success(-AFloat(0x100000000), -0x100000000_f),
Unrepresentable<f32>(1e40_a),
Unrepresentable<f32>(-1e40_a),
// abstract-float -> bool
Success(0.0_a, false),
Success(1.0_a, true),
Success(AFloat(0xffffffff), true),
Success(AFloat(0x100000000), true),
Success(-AFloat(0x100000000), true),
Success(1e40_a, true),
Success(-1e40_a, true),
})));
TEST_F(ResolverConstEvalTest, Vec3_Convert_f32_to_i32) {
auto* expr = vec3<i32>(vec3<f32>(1.1_f, 2.2_f, 3.3_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::I32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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<AInt>(), 1);
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<AInt>(), 2);
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<AInt>(), 3);
}
TEST_F(ResolverConstEvalTest, Vec3_Convert_u32_to_f32) {
auto* expr = vec3<f32>(vec3<u32>(10_u, 20_u, 30_u));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::F32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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>(), 10.f);
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>(), 20.f);
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>(), 30.f);
}
TEST_F(ResolverConstEvalTest, Vec3_Convert_f16_to_i32) {
Enable(ast::Extension::kF16);
auto* expr = vec3<i32>(vec3<f16>(1.1_h, 2.2_h, 3.3_h));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::I32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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<AInt>(), 1_i);
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<AInt>(), 2_i);
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<AInt>(), 3_i);
}
TEST_F(ResolverConstEvalTest, Vec3_Convert_u32_to_f16) {
Enable(ast::Extension::kF16);
auto* expr = vec3<f16>(vec3<u32>(10_u, 20_u, 30_u));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::F16>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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>(), 10.f);
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>(), 20.f);
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>(), 30.f);
}
TEST_F(ResolverConstEvalTest, 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);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::I32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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<AInt>(), i32::Highest());
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<AInt>(), i32::Lowest());
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<AInt>(), i32::Highest());
}
TEST_F(ResolverConstEvalTest, 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);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::U32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_TRUE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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<AInt>(), u32::Highest());
EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllEqual());
EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(1)->As<AInt>(), u32::Lowest());
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<AInt>(), u32::Highest());
}
TEST_F(ResolverConstEvalTest, Vec3_Convert_Large_f32_to_f16) {
Enable(ast::Extension::kF16);
auto* expr = vec3<f16>(vec3<f32>(1e10_f, -1e20_f, 1e30_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
constexpr auto kInfinity = std::numeric_limits<double>::infinity();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::F16>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
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>(), 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>(), -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>(), kInfinity);
}
TEST_F(ResolverConstEvalTest, Vec3_Convert_Small_f32_to_f16) {
Enable(ast::Extension::kF16);
auto* expr = vec3<f16>(vec3<f32>(1e-20_f, -2e-30_f, 3e-40_f));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::F16>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_FALSE(sem->ConstantValue()->AllEqual());
EXPECT_TRUE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllEqual());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(0)->As<AFloat>(), 0.0);
EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(0)->As<AFloat>().value));
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>(), -0.0);
EXPECT_TRUE(std::signbit(sem->ConstantValue()->Index(1)->As<AFloat>().value));
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllEqual());
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
EXPECT_EQ(sem->ConstantValue()->Index(2)->As<AFloat>(), 0.0);
EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(2)->As<AFloat>().value));
}
} // namespace
} // namespace tint::resolver

View File

@ -0,0 +1,314 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/resolver/const_eval_test.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
TEST_F(ResolverConstEvalTest, Vec3_Index) {
auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), 2_i);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::I32>());
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
EXPECT_EQ(sem->ConstantValue()->As<i32>(), 3_i);
}
TEST_F(ResolverConstEvalTest, Vec3_Index_OOB_High) {
auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), Expr(Source{{12, 34}}, 3_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index 3 out of bounds [0..2]");
}
TEST_F(ResolverConstEvalTest, Vec3_Index_OOB_Low) {
auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), Expr(Source{{12, 34}}, -3_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index -3 out of bounds [0..2]");
}
TEST_F(ResolverConstEvalTest, Vec3_Swizzle_Scalar) {
auto* expr = MemberAccessor(vec3<i32>(1_i, 2_i, 3_i), "y");
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::I32>());
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
EXPECT_EQ(sem->ConstantValue()->As<i32>(), 2_i);
}
TEST_F(ResolverConstEvalTest, Vec3_Swizzle_Vector) {
auto* expr = MemberAccessor(vec3<i32>(1_i, 2_i, 3_i), "zx");
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_EQ(vec->Width(), 2u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
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<f32>(), 3._a);
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<f32>(), 1._a);
}
TEST_F(ResolverConstEvalTest, Vec3_Swizzle_Chain) {
auto* expr = // (1, 2, 3) -> (2, 3, 1) -> (3, 2) -> 2
MemberAccessor(MemberAccessor(MemberAccessor(vec3<i32>(1_i, 2_i, 3_i), "gbr"), "yx"), "y");
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::I32>());
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue()->AllEqual());
EXPECT_FALSE(sem->ConstantValue()->AnyZero());
EXPECT_FALSE(sem->ConstantValue()->AllZero());
EXPECT_EQ(sem->ConstantValue()->As<i32>(), 2_i);
}
TEST_F(ResolverConstEvalTest, Mat3x2_Index) {
auto* expr = IndexAccessor(
mat3x2<f32>(vec2<f32>(1._a, 2._a), vec2<f32>(3._a, 4._a), vec2<f32>(5._a, 6._a)), 2_i);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_EQ(vec->Width(), 2u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
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<f32>(), 5._a);
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<f32>(), 6._a);
}
TEST_F(ResolverConstEvalTest, Mat3x2_Index_OOB_High) {
auto* expr = IndexAccessor(
mat3x2<f32>(vec2<f32>(1._a, 2._a), vec2<f32>(3._a, 4._a), vec2<f32>(5._a, 6._a)),
Expr(Source{{12, 34}}, 3_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index 3 out of bounds [0..2]");
}
TEST_F(ResolverConstEvalTest, Mat3x2_Index_OOB_Low) {
auto* expr = IndexAccessor(
mat3x2<f32>(vec2<f32>(1._a, 2._a), vec2<f32>(3._a, 4._a), vec2<f32>(5._a, 6._a)),
Expr(Source{{12, 34}}, -3_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index -3 out of bounds [0..2]");
}
TEST_F(ResolverConstEvalTest, Array_vec3_f32_Index) {
auto* expr = IndexAccessor(Construct(ty.array(ty.vec3<f32>(), 2_u), //
vec3<f32>(1_f, 2_f, 3_f), vec3<f32>(4_f, 5_f, 6_f)),
1_i);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_TRUE(vec->type()->Is<sem::F32>());
EXPECT_EQ(vec->Width(), 3u);
EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
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<f32>(), 4_f);
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<f32>(), 5_f);
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<f32>(), 6_f);
}
TEST_F(ResolverConstEvalTest, Array_vec3_f32_Index_OOB_High) {
auto* expr = IndexAccessor(Construct(ty.array(ty.vec3<f32>(), 2_u), //
vec3<f32>(1_f, 2_f, 3_f), vec3<f32>(4_f, 5_f, 6_f)),
Expr(Source{{12, 34}}, 2_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index 2 out of bounds [0..1]");
}
TEST_F(ResolverConstEvalTest, Array_vec3_f32_Index_OOB_Low) {
auto* expr = IndexAccessor(Construct(ty.array(ty.vec3<f32>(), 2_u), //
vec3<f32>(1_f, 2_f, 3_f), vec3<f32>(4_f, 5_f, 6_f)),
Expr(Source{{12, 34}}, -2_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index -2 out of bounds [0..1]");
}
TEST_F(ResolverConstEvalTest, RuntimeArray_vec3_f32_Index_OOB_Low) {
auto* sb = GlobalVar("sb", ty.array(ty.vec3<f32>()), Group(0_a), Binding(0_a),
ast::AddressSpace::kStorage);
auto* expr = IndexAccessor(sb, Expr(Source{{12, 34}}, -2_i));
WrapInFunction(expr);
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 error: index -2 out of bounds");
}
TEST_F(ResolverConstEvalTest, ChainedIndex) {
auto* arr_expr = Construct(ty.array(ty.mat2x3<f32>(), 2_u), // array<mat2x3<f32>, 2u>
mat2x3<f32>(vec3<f32>(1_f, 2_f, 3_f), //
vec3<f32>(4_f, 5_f, 6_f)), //
mat2x3<f32>(vec3<f32>(7_f, 0_f, 9_f), //
vec3<f32>(10_f, 11_f, 12_f)));
auto* mat_expr = IndexAccessor(arr_expr, 1_i); // arr[1]
auto* vec_expr = IndexAccessor(mat_expr, 0_i); // arr[1][0]
auto* f32_expr = IndexAccessor(vec_expr, 2_i); // arr[1][0][2]
WrapInFunction(f32_expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
{
auto* mat = Sem().Get(mat_expr);
EXPECT_NE(mat, nullptr);
auto* ty = mat->Type()->As<sem::Matrix>();
ASSERT_NE(mat->Type(), nullptr);
EXPECT_TRUE(ty->ColumnType()->Is<sem::Vector>());
EXPECT_EQ(ty->columns(), 2u);
EXPECT_EQ(ty->rows(), 3u);
EXPECT_EQ(mat->ConstantValue()->Type(), mat->Type());
EXPECT_FALSE(mat->ConstantValue()->AllEqual());
EXPECT_TRUE(mat->ConstantValue()->AnyZero());
EXPECT_FALSE(mat->ConstantValue()->AllZero());
EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(0)->AllEqual());
EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(0)->AnyZero());
EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(0)->AllZero());
EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(0)->As<f32>(), 7_f);
EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AllEqual());
EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AnyZero());
EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(1)->AllZero());
EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(1)->As<f32>(), 0_f);
EXPECT_TRUE(mat->ConstantValue()->Index(0)->Index(2)->AllEqual());
EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(2)->AnyZero());
EXPECT_FALSE(mat->ConstantValue()->Index(0)->Index(2)->AllZero());
EXPECT_EQ(mat->ConstantValue()->Index(0)->Index(2)->As<f32>(), 9_f);
EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(0)->AllEqual());
EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(0)->AnyZero());
EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(0)->AllZero());
EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(0)->As<f32>(), 10_f);
EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(1)->AllEqual());
EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(1)->AnyZero());
EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(1)->AllZero());
EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(1)->As<f32>(), 11_f);
EXPECT_TRUE(mat->ConstantValue()->Index(1)->Index(2)->AllEqual());
EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(2)->AnyZero());
EXPECT_FALSE(mat->ConstantValue()->Index(1)->Index(2)->AllZero());
EXPECT_EQ(mat->ConstantValue()->Index(1)->Index(2)->As<f32>(), 12_f);
}
{
auto* vec = Sem().Get(vec_expr);
EXPECT_NE(vec, nullptr);
auto* ty = vec->Type()->As<sem::Vector>();
ASSERT_NE(vec->Type(), nullptr);
EXPECT_TRUE(ty->type()->Is<sem::F32>());
EXPECT_EQ(ty->Width(), 3u);
EXPECT_EQ(vec->ConstantValue()->Type(), vec->Type());
EXPECT_FALSE(vec->ConstantValue()->AllEqual());
EXPECT_TRUE(vec->ConstantValue()->AnyZero());
EXPECT_FALSE(vec->ConstantValue()->AllZero());
EXPECT_TRUE(vec->ConstantValue()->Index(0)->AllEqual());
EXPECT_FALSE(vec->ConstantValue()->Index(0)->AnyZero());
EXPECT_FALSE(vec->ConstantValue()->Index(0)->AllZero());
EXPECT_EQ(vec->ConstantValue()->Index(0)->As<f32>(), 7_f);
EXPECT_TRUE(vec->ConstantValue()->Index(1)->AllEqual());
EXPECT_TRUE(vec->ConstantValue()->Index(1)->AnyZero());
EXPECT_TRUE(vec->ConstantValue()->Index(1)->AllZero());
EXPECT_EQ(vec->ConstantValue()->Index(1)->As<f32>(), 0_f);
EXPECT_TRUE(vec->ConstantValue()->Index(2)->AllEqual());
EXPECT_FALSE(vec->ConstantValue()->Index(2)->AnyZero());
EXPECT_FALSE(vec->ConstantValue()->Index(2)->AllZero());
EXPECT_EQ(vec->ConstantValue()->Index(2)->As<f32>(), 9_f);
}
{
auto* f = Sem().Get(f32_expr);
EXPECT_NE(f, nullptr);
EXPECT_TRUE(f->Type()->Is<sem::F32>());
EXPECT_EQ(f->ConstantValue()->Type(), f->Type());
EXPECT_TRUE(f->ConstantValue()->AllEqual());
EXPECT_FALSE(f->ConstantValue()->AnyZero());
EXPECT_FALSE(f->ConstantValue()->AllZero());
EXPECT_EQ(f->ConstantValue()->As<f32>(), 9_f);
}
}
} // namespace
} // namespace tint::resolver

View File

@ -0,0 +1,98 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/resolver/const_eval_test.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
TEST_F(ResolverConstEvalTest, MemberAccess) {
Structure("Inner", utils::Vector{
Member("i1", ty.i32()),
Member("i2", ty.u32()),
Member("i3", ty.f32()),
});
Structure("Outer", utils::Vector{
Member("o1", ty.type_name("Inner")),
Member("o2", ty.type_name("Inner")),
});
auto* outer_expr = Construct(ty.type_name("Outer"), //
Construct(ty.type_name("Inner"), 1_i, 2_u, 3_f),
Construct(ty.type_name("Inner")));
auto* o1_expr = MemberAccessor(outer_expr, "o1");
auto* i2_expr = MemberAccessor(o1_expr, "i2");
WrapInFunction(i2_expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* outer = Sem().Get(outer_expr);
ASSERT_NE(outer, nullptr);
auto* str = outer->Type()->As<sem::Struct>();
ASSERT_NE(str, nullptr);
EXPECT_EQ(str->Members().size(), 2u);
ASSERT_NE(outer->ConstantValue(), nullptr);
EXPECT_TYPE(outer->ConstantValue()->Type(), outer->Type());
EXPECT_FALSE(outer->ConstantValue()->AllEqual());
EXPECT_TRUE(outer->ConstantValue()->AnyZero());
EXPECT_FALSE(outer->ConstantValue()->AllZero());
auto* o1 = Sem().Get(o1_expr);
ASSERT_NE(o1->ConstantValue(), nullptr);
EXPECT_FALSE(o1->ConstantValue()->AllEqual());
EXPECT_FALSE(o1->ConstantValue()->AnyZero());
EXPECT_FALSE(o1->ConstantValue()->AllZero());
EXPECT_TRUE(o1->ConstantValue()->Type()->Is<sem::Struct>());
EXPECT_EQ(o1->ConstantValue()->Index(0)->As<i32>(), 1_i);
EXPECT_EQ(o1->ConstantValue()->Index(1)->As<u32>(), 2_u);
EXPECT_EQ(o1->ConstantValue()->Index(2)->As<f32>(), 3_f);
auto* i2 = Sem().Get(i2_expr);
ASSERT_NE(i2->ConstantValue(), nullptr);
EXPECT_TRUE(i2->ConstantValue()->AllEqual());
EXPECT_FALSE(i2->ConstantValue()->AnyZero());
EXPECT_FALSE(i2->ConstantValue()->AllZero());
EXPECT_TRUE(i2->ConstantValue()->Type()->Is<sem::U32>());
EXPECT_EQ(i2->ConstantValue()->As<u32>(), 2_u);
}
TEST_F(ResolverConstEvalTest, Matrix_AFloat_Construct_From_AInt_Vectors) {
auto* c = Const("a", Construct(ty.mat(nullptr, 2, 2), //
Construct(ty.vec(nullptr, 2), Expr(1_a), Expr(2_a)),
Construct(ty.vec(nullptr, 2), Expr(3_a), Expr(4_a))));
WrapInFunction(c);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(c);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Type()->Is<sem::Matrix>());
auto* cv = sem->ConstantValue();
EXPECT_TYPE(cv->Type(), sem->Type());
EXPECT_TRUE(cv->Index(0)->Type()->Is<sem::Vector>());
EXPECT_TRUE(cv->Index(0)->Index(0)->Type()->Is<sem::AbstractFloat>());
EXPECT_FALSE(cv->AllEqual());
EXPECT_FALSE(cv->AnyZero());
EXPECT_FALSE(cv->AllZero());
auto* c0 = cv->Index(0);
auto* c1 = cv->Index(1);
EXPECT_EQ(std::get<AFloat>(c0->Index(0)->Value()), 1.0);
EXPECT_EQ(std::get<AFloat>(c0->Index(1)->Value()), 2.0);
EXPECT_EQ(std::get<AFloat>(c1->Index(0)->Value()), 3.0);
EXPECT_EQ(std::get<AFloat>(c1->Index(1)->Value()), 4.0);
}
} // namespace
} // namespace tint::resolver

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,292 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_TINT_RESOLVER_CONST_EVAL_TEST_H_
#define SRC_TINT_RESOLVER_CONST_EVAL_TEST_H_
#include <limits>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/tint/resolver/resolver_test_helper.h"
#include "src/tint/sem/test_helper.h"
namespace tint::resolver {
template <typename T>
inline const auto kPi = T(UnwrapNumber<T>(3.14159265358979323846));
template <typename T>
inline const auto kPiOver2 = T(UnwrapNumber<T>(1.57079632679489661923));
template <typename T>
inline const auto kPiOver4 = T(UnwrapNumber<T>(0.785398163397448309616));
template <typename T>
inline const auto k3PiOver4 = T(UnwrapNumber<T>(2.356194490192344928846));
/// Walks the sem::Constant @p c, accumulating all the inner-most scalar values into @p args
inline void CollectScalarArgs(const sem::Constant* c, builder::ScalarArgs& args) {
Switch(
c->Type(), //
[&](const sem::Bool*) { args.values.Push(c->As<bool>()); },
[&](const sem::I32*) { args.values.Push(c->As<i32>()); },
[&](const sem::U32*) { args.values.Push(c->As<u32>()); },
[&](const sem::F32*) { args.values.Push(c->As<f32>()); },
[&](const sem::F16*) { args.values.Push(c->As<f16>()); },
[&](Default) {
size_t i = 0;
while (auto* child = c->Index(i++)) {
CollectScalarArgs(child, args);
}
});
}
/// Walks the sem::Constant @p c, returning all the inner-most scalar values.
inline builder::ScalarArgs ScalarArgsFrom(const sem::Constant* c) {
builder::ScalarArgs out;
CollectScalarArgs(c, out);
return out;
}
template <typename T>
inline constexpr auto Negate(const Number<T>& v) {
if constexpr (std::is_integral_v<T>) {
if constexpr (std::is_signed_v<T>) {
// 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 (v == std::numeric_limits<T>::min()) {
return v;
}
return -v;
} else {
// Allow negating unsigned values
using ST = std::make_signed_t<T>;
auto as_signed = Number<ST>{static_cast<ST>(v)};
return Number<T>{static_cast<T>(Negate(as_signed))};
}
} else {
// float case
return -v;
}
}
template <typename T>
inline 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));
}
}
TINT_BEGIN_DISABLE_WARNING(CONSTANT_OVERFLOW);
template <typename T>
inline constexpr Number<T> Mul(Number<T> v1, Number<T> v2) {
if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
// For signed integrals, avoid C++ UB by multiplying as unsigned
using UT = std::make_unsigned_t<T>;
return static_cast<Number<T>>(static_cast<UT>(v1) * static_cast<UT>(v2));
} else {
return static_cast<Number<T>>(v1 * v2);
}
}
TINT_END_DISABLE_WARNING(CONSTANT_OVERFLOW);
// Concats any number of std::vectors
template <typename Vec, typename... Vecs>
[[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);
}
// Concats vectors `vs` into `v1`
template <typename Vec, typename... Vecs>
inline void ConcatInto(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)), ...);
}
// Concats vectors `vs` into `v1` iff `condition` is true
template <bool condition, typename Vec, typename... Vecs>
inline void ConcatIntoIf([[maybe_unused]] Vec& v1, [[maybe_unused]] Vecs&&... vs) {
if constexpr (condition) {
ConcatInto(v1, std::forward<Vecs>(vs)...);
}
}
using builder::IsValue;
using builder::Mat;
using builder::Val;
using builder::Value;
using builder::Vec;
using Types = std::variant< //
Value<AInt>,
Value<AFloat>,
Value<u32>,
Value<i32>,
Value<f32>,
Value<f16>,
Value<bool>,
Value<builder::vec2<AInt>>,
Value<builder::vec2<AFloat>>,
Value<builder::vec2<u32>>,
Value<builder::vec2<i32>>,
Value<builder::vec2<f32>>,
Value<builder::vec2<f16>>,
Value<builder::vec2<bool>>,
Value<builder::vec3<AInt>>,
Value<builder::vec3<AFloat>>,
Value<builder::vec3<u32>>,
Value<builder::vec3<i32>>,
Value<builder::vec3<f32>>,
Value<builder::vec3<f16>>,
Value<builder::vec3<bool>>,
Value<builder::vec4<AInt>>,
Value<builder::vec4<AFloat>>,
Value<builder::vec4<u32>>,
Value<builder::vec4<i32>>,
Value<builder::vec4<f32>>,
Value<builder::vec4<f16>>,
Value<builder::vec4<bool>>,
Value<builder::mat2x2<AInt>>,
Value<builder::mat2x2<AFloat>>,
Value<builder::mat2x2<f32>>,
Value<builder::mat2x2<f16>>,
Value<builder::mat2x3<AInt>>,
Value<builder::mat2x3<AFloat>>,
Value<builder::mat2x3<f32>>,
Value<builder::mat2x3<f16>>,
Value<builder::mat3x2<AInt>>,
Value<builder::mat3x2<AFloat>>,
Value<builder::mat3x2<f32>>,
Value<builder::mat3x2<f16>>
//
>;
inline std::ostream& operator<<(std::ostream& o, const Types& types) {
std::visit(
[&](auto&& v) {
using ValueType = std::decay_t<decltype(v)>;
o << ValueType::DataType::Name() << "(";
for (auto& a : v.args.values) {
o << std::get<typename ValueType::ElementType>(a);
if (&a != &v.args.values.Back()) {
o << ", ";
}
}
o << ")";
},
types);
return o;
}
// Calls `f` on deepest elements of both `a` and `b`. If function returns Action::kStop, it stops
// traversing, and return Action::kStop; if the function returns Action::kContinue, it continues and
// returns Action::kContinue when done.
// TODO(amaiorano): Move to Constant.h?
enum class Action { kStop, kContinue };
template <typename Func>
inline Action ForEachElemPair(const sem::Constant* a, const sem::Constant* b, Func&& f) {
EXPECT_EQ(a->Type(), b->Type());
size_t i = 0;
while (true) {
auto* a_elem = a->Index(i);
if (!a_elem) {
break;
}
auto* b_elem = b->Index(i);
if (ForEachElemPair(a_elem, b_elem, f) == Action::kStop) {
return Action::kStop;
}
i++;
}
if (i == 0) {
return f(a, b);
}
return Action::kContinue;
}
/// Defines common bit value patterns for the input `NumberT` type used for testing.
template <typename NumberT>
struct BitValues {
/// The unwrapped number type
using T = UnwrapNumber<NumberT>;
/// Details
struct detail {
/// Unsigned type of `T`
using UT = std::make_unsigned_t<T>;
/// Size in bits of type T
static constexpr size_t NumBits = sizeof(T) * 8;
/// All bits set 1
static constexpr T All = T{~T{0}};
/// Only left-most bits set to 1, rest set to 0
static constexpr T LeftMost = static_cast<T>(UT{1} << (NumBits - 1u));
/// Only left-most bits set to 0, rest set to 1
static constexpr T AllButLeftMost = T{~LeftMost};
/// Only two left-most bits set to 1, rest set to 0
static constexpr T TwoLeftMost = static_cast<T>(UT{0b11} << (NumBits - 2u));
/// Only two left-most bits set to 0, rest set to 1
static constexpr T AllButTwoLeftMost = T{~TwoLeftMost};
/// Only right-most bit set to 1, rest set to 0
static constexpr T RightMost = T{1};
/// Only right-most bit set to 0, rest set to 1
static constexpr T AllButRightMost = T{~RightMost};
};
/// Size in bits of type NumberT
static inline const size_t NumBits = detail::NumBits;
/// All bits set 1
static inline const NumberT All = NumberT{detail::All};
/// Only left-most bits set to 1, rest set to 0
static inline const NumberT LeftMost = NumberT{detail::LeftMost};
/// Only left-most bits set to 0, rest set to 1
static inline const NumberT AllButLeftMost = NumberT{detail::AllButLeftMost};
/// Only two left-most bits set to 1, rest set to 0
static inline const NumberT TwoLeftMost = NumberT{detail::TwoLeftMost};
/// Only two left-most bits set to 0, rest set to 1
static inline const NumberT AllButTwoLeftMost = NumberT{detail::AllButTwoLeftMost};
/// Only right-most bit set to 1, rest set to 0
static inline const NumberT RightMost = NumberT{detail::RightMost};
/// Only right-most bit set to 0, rest set to 1
static inline const NumberT AllButRightMost = NumberT{detail::AllButRightMost};
/// Performs a left-shift of `val` by `shiftBy`, both of varying type cast to `T`.
/// @param val value to shift left
/// @param shiftBy number of bits to shift left by
/// @returns the shifted value
template <typename U, typename V>
static constexpr NumberT Lsh(U val, V shiftBy) {
return NumberT{T{val} << T{shiftBy}};
}
};
using ResolverConstEvalTest = ResolverTest;
} // namespace tint::resolver
#endif // SRC_TINT_RESOLVER_CONST_EVAL_TEST_H_

View File

@ -0,0 +1,189 @@
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/resolver/const_eval_test.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
// Bring in std::ostream& operator<<(std::ostream& o, const Types& types)
using resolver::operator<<;
struct Case {
Types input;
Types expected;
};
static std::ostream& operator<<(std::ostream& o, const Case& c) {
o << "input: " << c.input << ", expected: " << c.expected;
return o;
}
/// Creates a Case with Values of any type
template <typename T, typename U>
Case C(Value<T> input, Value<U> expected) {
return Case{std::move(input), std::move(expected)};
}
/// Convenience overload to creates a Case with just scalars
template <typename T, typename U, typename = std::enable_if_t<!IsValue<T>>>
Case C(T input, U expected) {
return Case{Val(input), Val(expected)};
}
using ResolverConstEvalUnaryOpTest = ResolverTestWithParam<std::tuple<ast::UnaryOp, Case>>;
TEST_P(ResolverConstEvalUnaryOpTest, Test) {
Enable(ast::Extension::kF16);
auto op = std::get<0>(GetParam());
auto& c = std::get<1>(GetParam());
std::visit(
[&](auto&& expected) {
using T = typename std::decay_t<decltype(expected)>::ElementType;
auto* input_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.input);
auto* expr = create<ast::UnaryOpExpression>(op, input_expr);
GlobalConst("C", expr);
auto* expected_expr = expected.Expr(*this);
GlobalConst("E", expected_expr);
ASSERT_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* expected_sem = Sem().Get(expected_expr);
const sem::Constant* expected_value = expected_sem->ConstantValue();
ASSERT_NE(expected_value, nullptr);
EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
ForEachElemPair(value, expected_value,
[&](const sem::Constant* a, const sem::Constant* b) {
EXPECT_EQ(a->As<T>(), b->As<T>());
if constexpr (IsIntegral<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(a->As<AInt>(), b->As<AInt>());
}
return HasFailure() ? Action::kStop : Action::kContinue;
});
},
c.expected);
}
INSTANTIATE_TEST_SUITE_P(Complement,
ResolverConstEvalUnaryOpTest,
testing::Combine(testing::Values(ast::UnaryOp::kComplement),
testing::ValuesIn({
// AInt
C(0_a, 0xffffffffffffffff_a),
C(0xffffffffffffffff_a, 0_a),
C(0xf0f0f0f0f0f0f0f0_a, 0x0f0f0f0f0f0f0f0f_a),
C(0xaaaaaaaaaaaaaaaa_a, 0x5555555555555555_a),
C(0x5555555555555555_a, 0xaaaaaaaaaaaaaaaa_a),
// u32
C(0_u, 0xffffffff_u),
C(0xffffffff_u, 0_u),
C(0xf0f0f0f0_u, 0x0f0f0f0f_u),
C(0xaaaaaaaa_u, 0x55555555_u),
C(0x55555555_u, 0xaaaaaaaa_u),
// i32
C(0_i, -1_i),
C(-1_i, 0_i),
C(1_i, -2_i),
C(-2_i, 1_i),
C(2_i, -3_i),
C(-3_i, 2_i),
})));
INSTANTIATE_TEST_SUITE_P(Negation,
ResolverConstEvalUnaryOpTest,
testing::Combine(testing::Values(ast::UnaryOp::kNegation),
testing::ValuesIn({
// AInt
C(0_a, -0_a),
C(-0_a, 0_a),
C(1_a, -1_a),
C(-1_a, 1_a),
C(AInt::Highest(), -AInt::Highest()),
C(-AInt::Highest(), AInt::Highest()),
C(AInt::Lowest(), Negate(AInt::Lowest())),
C(Negate(AInt::Lowest()), AInt::Lowest()),
// i32
C(0_i, -0_i),
C(-0_i, 0_i),
C(1_i, -1_i),
C(-1_i, 1_i),
C(i32::Highest(), -i32::Highest()),
C(-i32::Highest(), i32::Highest()),
C(i32::Lowest(), Negate(i32::Lowest())),
C(Negate(i32::Lowest()), i32::Lowest()),
// 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(AFloat::Highest(), -AFloat::Highest()),
C(-AFloat::Highest(), AFloat::Highest()),
C(AFloat::Lowest(), Negate(AFloat::Lowest())),
C(Negate(AFloat::Lowest()), AFloat::Lowest()),
// 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(f32::Highest(), -f32::Highest()),
C(-f32::Highest(), f32::Highest()),
C(f32::Lowest(), Negate(f32::Lowest())),
C(Negate(f32::Lowest()), f32::Lowest()),
// f16
C(0.0_h, -0.0_h),
C(-0.0_h, 0.0_h),
C(1.0_h, -1.0_h),
C(-1.0_h, 1.0_h),
C(f16::Highest(), -f16::Highest()),
C(-f16::Highest(), f16::Highest()),
C(f16::Lowest(), Negate(f16::Lowest())),
C(Negate(f16::Lowest()), f16::Lowest()),
})));
// Make sure UBSan doesn't trip on C++'s undefined behaviour of negating the smallest negative
// number.
TEST_F(ResolverConstEvalTest, UnaryNegateLowestAbstract) {
// const break_me = -(-9223372036854775808);
auto* c = GlobalConst("break_me", Negation(Negation(Expr(9223372036854775808_a))));
(void)c;
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(c);
EXPECT_EQ(sem->ConstantValue()->As<AInt>(), 9223372036854775808_a);
}
INSTANTIATE_TEST_SUITE_P(Not,
ResolverConstEvalUnaryOpTest,
testing::Combine(testing::Values(ast::UnaryOp::kNot),
testing::ValuesIn({
C(true, false),
C(false, true),
C(Vec(true, true), Vec(false, false)),
C(Vec(true, false), Vec(false, true)),
C(Vec(false, true), Vec(true, false)),
C(Vec(false, false), Vec(true, true)),
})));
} // namespace
} // namespace tint::resolver