tint/resolver: Support error cases with const-eval builtin tests

Also:

Print unrepresentable numbers with higher precision - otherwise values can round, and diagnostics can be very confusing.
Improve diagnostic distinction between `( )` `[ ]` interval ranges.

Change-Id: I9269fbf1738f0bce5f2ddb5a387687543fd5d0bb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/108700
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
Ben Clayton 2022-11-04 19:18:55 +00:00 committed by Dawn LUCI CQ
parent a39384133f
commit b6903295a8
3 changed files with 159 additions and 161 deletions

View File

@ -15,6 +15,7 @@
#include "src/tint/resolver/const_eval.h" #include "src/tint/resolver/const_eval.h"
#include <algorithm> #include <algorithm>
#include <iomanip>
#include <limits> #include <limits>
#include <optional> #include <optional>
#include <string> #include <string>
@ -187,14 +188,24 @@ inline bool IsPositiveZero(T value) {
template <typename NumberT> template <typename NumberT>
std::string OverflowErrorMessage(NumberT lhs, const char* op, NumberT rhs) { std::string OverflowErrorMessage(NumberT lhs, const char* op, NumberT rhs) {
std::stringstream ss; std::stringstream ss;
ss << std::setprecision(20);
ss << "'" << lhs.value << " " << op << " " << rhs.value << "' cannot be represented as '" ss << "'" << lhs.value << " " << op << " " << rhs.value << "' cannot be represented as '"
<< FriendlyName<NumberT>() << "'"; << FriendlyName<NumberT>() << "'";
return ss.str(); return ss.str();
} }
template <typename VALUE_TY>
std::string OverflowErrorMessage(VALUE_TY value, std::string_view target_ty) {
std::stringstream ss;
ss << std::setprecision(20);
ss << "value " << value << " cannot be represented as "
<< "'" << target_ty << "'";
return ss.str();
}
/// @returns the number of consecutive leading bits in `@p e` set to `@p bit_value_to_count`. /// @returns the number of consecutive leading bits in `@p e` set to `@p bit_value_to_count`.
template <typename T> template <typename T>
auto CountLeadingBits(T e, T bit_value_to_count) -> std::make_unsigned_t<T> { std::make_unsigned_t<T> CountLeadingBits(T e, T bit_value_to_count) {
using UT = std::make_unsigned_t<T>; using UT = std::make_unsigned_t<T>;
constexpr UT kNumBits = sizeof(UT) * 8; constexpr UT kNumBits = sizeof(UT) * 8;
constexpr UT kLeftMost = UT{1} << (kNumBits - 1); constexpr UT kLeftMost = UT{1} << (kNumBits - 1);
@ -211,7 +222,7 @@ auto CountLeadingBits(T e, T bit_value_to_count) -> std::make_unsigned_t<T> {
/// @returns the number of consecutive trailing bits set to `@p bit_value_to_count` in `@p e` /// @returns the number of consecutive trailing bits set to `@p bit_value_to_count` in `@p e`
template <typename T> template <typename T>
auto CountTrailingBits(T e, T bit_value_to_count) -> std::make_unsigned_t<T> { std::make_unsigned_t<T> CountTrailingBits(T e, T bit_value_to_count) {
using UT = std::make_unsigned_t<T>; using UT = std::make_unsigned_t<T>;
constexpr UT kNumBits = sizeof(UT) * 8; constexpr UT kNumBits = sizeof(UT) * 8;
constexpr UT kRightMost = UT{1}; constexpr UT kRightMost = UT{1};
@ -292,10 +303,9 @@ struct Element : ImplConstant {
// --- Below this point are the failure cases --- // --- Below this point are the failure cases ---
} else if constexpr (IsAbstract<FROM>) { } else if constexpr (IsAbstract<FROM>) {
// [abstract-numeric -> x] - materialization failure // [abstract-numeric -> x] - materialization failure
std::stringstream ss; builder.Diagnostics().add_error(
ss << "value " << value << " cannot be represented as "; tint::diag::System::Resolver,
ss << "'" << builder.FriendlyName(target_ty) << "'"; OverflowErrorMessage(value, builder.FriendlyName(target_ty)), source);
builder.Diagnostics().add_error(tint::diag::System::Resolver, ss.str(), source);
return utils::Failure; return utils::Failure;
} else if constexpr (IsFloatingPoint<TO>) { } else if constexpr (IsFloatingPoint<TO>) {
// [x -> floating-point] - number not exactly representable // [x -> floating-point] - number not exactly representable
@ -1602,7 +1612,7 @@ ConstEval::Result ConstEval::acos(const sem::Type* ty,
auto create = [&](auto i) -> ImplResult { auto create = [&](auto i) -> ImplResult {
using NumberT = decltype(i); using NumberT = decltype(i);
if (i < NumberT(-1.0) || i > NumberT(1.0)) { if (i < NumberT(-1.0) || i > NumberT(1.0)) {
AddError("acos must be called with a value in the range [-1, 1]", source); AddError("acos must be called with a value in the range [-1 .. 1] (inclusive)", source);
return utils::Failure; return utils::Failure;
} }
return CreateElement(builder, c0->Type(), NumberT(std::acos(i.value))); return CreateElement(builder, c0->Type(), NumberT(std::acos(i.value)));
@ -1631,7 +1641,7 @@ ConstEval::Result ConstEval::asin(const sem::Type* ty,
auto create = [&](auto i) -> ImplResult { auto create = [&](auto i) -> ImplResult {
using NumberT = decltype(i); using NumberT = decltype(i);
if (i < NumberT(-1.0) || i > NumberT(1.0)) { if (i < NumberT(-1.0) || i > NumberT(1.0)) {
AddError("asin must be called with a value in the range [-1, 1]", source); AddError("asin must be called with a value in the range [-1 .. 1] (inclusive)", source);
return utils::Failure; return utils::Failure;
} }
return CreateElement(builder, c0->Type(), NumberT(std::asin(i.value))); return CreateElement(builder, c0->Type(), NumberT(std::asin(i.value)));
@ -1677,7 +1687,7 @@ ConstEval::Result ConstEval::atanh(const sem::Type* ty,
auto create = [&](auto i) -> ImplResult { auto create = [&](auto i) -> ImplResult {
using NumberT = decltype(i); using NumberT = decltype(i);
if (i <= NumberT(-1.0) || i >= NumberT(1.0)) { if (i <= NumberT(-1.0) || i >= NumberT(1.0)) {
AddError("atanh must be called with a value in the range (-1, 1)", source); AddError("atanh must be called with a value in the range (-1 .. 1) (exclusive)", source);
return utils::Failure; return utils::Failure;
} }
return CreateElement(builder, c0->Type(), NumberT(std::atanh(i.value))); return CreateElement(builder, c0->Type(), NumberT(std::atanh(i.value)));

View File

@ -853,7 +853,7 @@ TEST_F(ResolverConstEvalTest, BinaryAbstractAddOverflow_AFloat) {
GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Highest()), AFloat::Highest())); GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Highest()), AFloat::Highest()));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"1:1 error: '1.79769e+308 + 1.79769e+308' cannot be represented as 'abstract-float'"); "1:1 error: '1.7976931348623157081e+308 + 1.7976931348623157081e+308' cannot be represented as 'abstract-float'");
} }
TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AFloat) { TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AFloat) {
@ -861,7 +861,7 @@ TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AFloat) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
"1:1 error: '-1.79769e+308 + -1.79769e+308' cannot be represented as 'abstract-float'"); "1:1 error: '-1.7976931348623157081e+308 + -1.7976931348623157081e+308' cannot be represented as 'abstract-float'");
} }
// Mixed AInt and AFloat args to test implicit conversion to AFloat // Mixed AInt and AFloat args to test implicit conversion to AFloat

View File

@ -14,6 +14,8 @@
#include "src/tint/resolver/const_eval_test.h" #include "src/tint/resolver/const_eval_test.h"
#include "src/tint/utils/result.h"
using namespace tint::number_suffixes; // NOLINT using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver { namespace tint::resolver {
@ -23,25 +25,39 @@ namespace {
using resolver::operator<<; using resolver::operator<<;
struct Case { struct Case {
Case(utils::VectorRef<Types> in_args, Types in_expected) Case(utils::VectorRef<Types> in_args, Types expected_value)
: args(std::move(in_args)), expected(std::move(in_expected)) {} : args(std::move(in_args)), expected(Success{std::move(expected_value), false, false}) {}
Case(utils::VectorRef<Types> in_args, const char* expected_err)
: args(std::move(in_args)), expected(Failure{expected_err}) {}
/// Expected value may be positive or negative /// Expected value may be positive or negative
Case& PosOrNeg() { Case& PosOrNeg() {
expected_pos_or_neg = true; Success s = expected.Get();
s.pos_or_neg = true;
expected = s;
return *this; return *this;
} }
/// Expected value should be compared using FLOAT_EQ instead of EQ /// Expected value should be compared using FLOAT_EQ instead of EQ
Case& FloatComp() { Case& FloatComp() {
float_compare = true; Success s = expected.Get();
s.float_compare = true;
expected = s;
return *this; return *this;
} }
struct Success {
Types value;
bool pos_or_neg = false;
bool float_compare = false;
};
struct Failure {
const char* error = nullptr;
};
utils::Vector<Types, 8> args; utils::Vector<Types, 8> args;
Types expected; utils::Result<Success, Failure> expected;
bool expected_pos_or_neg = false;
bool float_compare = false;
}; };
static std::ostream& operator<<(std::ostream& o, const Case& c) { static std::ostream& operator<<(std::ostream& o, const Case& c) {
@ -49,17 +65,24 @@ static std::ostream& operator<<(std::ostream& o, const Case& c) {
for (auto& a : c.args) { for (auto& a : c.args) {
o << a << ", "; o << a << ", ";
} }
o << "expected: " << c.expected << ", expected_pos_or_neg: " << c.expected_pos_or_neg; o << "expected: ";
if (c.expected) {
auto s = c.expected.Get();
o << s.value << ", pos_or_neg: " << s.pos_or_neg;
} else {
o << "[ERROR: " << c.expected.Failure().error << "]";
}
return o; return o;
} }
using ScalarTypes = std::variant<AInt, AFloat, u32, i32, f32, f16>;
/// Creates a Case with Values for args and result /// Creates a Case with Values for args and result
static Case C(std::initializer_list<Types> args, Types result) { static Case C(std::initializer_list<Types> args, Types result) {
return Case{utils::Vector<Types, 8>{args}, std::move(result)}; return Case{utils::Vector<Types, 8>{args}, std::move(result)};
} }
/// Convenience overload that creates a Case with just scalars /// 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) { static Case C(std::initializer_list<ScalarTypes> sargs, ScalarTypes sresult) {
utils::Vector<Types, 8> args; utils::Vector<Types, 8> args;
for (auto& sa : sargs) { for (auto& sa : sargs) {
@ -70,6 +93,20 @@ static Case C(std::initializer_list<ScalarTypes> sargs, ScalarTypes sresult) {
return Case{std::move(args), std::move(result)}; return Case{std::move(args), std::move(result)};
} }
/// Creates a Case with Values for args and expected error
static Case E(std::initializer_list<Types> args, const char* err) {
return Case{utils::Vector<Types, 8>{args}, err};
}
/// Convenience overload that creates an expected-error Case with just scalars
static Case E(std::initializer_list<ScalarTypes> sargs, const char* err) {
utils::Vector<Types, 8> args;
for (auto& sa : sargs) {
std::visit([&](auto&& v) { return args.Push(Val(v)); }, sa);
}
return Case{std::move(args), err};
}
using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<sem::BuiltinType, Case>>; using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<sem::BuiltinType, Case>>;
TEST_P(ResolverConstEvalBuiltinTest, Test) { TEST_P(ResolverConstEvalBuiltinTest, Test) {
@ -83,58 +120,65 @@ TEST_P(ResolverConstEvalBuiltinTest, Test) {
std::visit([&](auto&& v) { args.Push(v.Expr(*this)); }, a); std::visit([&](auto&& v) { args.Push(v.Expr(*this)); }, a);
} }
auto* expected = ToValueBase(c.expected); auto* expr = Call(Source{{12, 34}}, sem::str(builtin), std::move(args));
auto* expr = Call(sem::str(builtin), std::move(args));
GlobalConst("C", expr); GlobalConst("C", expr);
auto* expected_expr = expected->Expr(*this);
GlobalConst("E", expected_expr);
ASSERT_TRUE(r()->Resolve()) << r()->error(); if (c.expected) {
auto expected = c.expected.Get();
auto* sem = Sem().Get(expr); auto* expected_expr = ToValueBase(expected.value)->Expr(*this);
ASSERT_NE(sem, nullptr); GlobalConst("E", expected_expr);
const sem::Constant* value = sem->ConstantValue();
ASSERT_NE(value, nullptr);
EXPECT_TYPE(value->Type(), sem->Type());
auto* expected_sem = Sem().Get(expected_expr); ASSERT_TRUE(r()->Resolve()) << r()->error();
const sem::Constant* expected_value = expected_sem->ConstantValue();
ASSERT_NE(expected_value, nullptr);
EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
// @TODO(amaiorano): Rewrite using ScalarArgsFrom() auto* sem = Sem().Get(expr);
ForEachElemPair(value, expected_value, [&](const sem::Constant* a, const sem::Constant* b) { ASSERT_NE(sem, nullptr);
std::visit( const sem::Constant* value = sem->ConstantValue();
[&](auto&& ct_expected) { ASSERT_NE(value, nullptr);
using T = typename std::decay_t<decltype(ct_expected)>::ElementType; EXPECT_TYPE(value->Type(), sem->Type());
auto v = a->As<T>(); auto* expected_sem = Sem().Get(expected_expr);
auto e = b->As<T>(); const sem::Constant* expected_value = expected_sem->ConstantValue();
if constexpr (std::is_same_v<bool, T>) { ASSERT_NE(expected_value, nullptr);
EXPECT_EQ(v, e); EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
} else if constexpr (IsFloatingPoint<T>) {
if (std::isnan(e)) { // @TODO(amaiorano): Rewrite using ScalarArgsFrom()
EXPECT_TRUE(std::isnan(v)); ForEachElemPair(value, expected_value, [&](const sem::Constant* a, const sem::Constant* b) {
} else { std::visit(
auto vf = (c.expected_pos_or_neg ? Abs(v) : v); [&](auto&& ct_expected) {
if (c.float_compare) { using T = typename std::decay_t<decltype(ct_expected)>::ElementType;
EXPECT_FLOAT_EQ(vf, e);
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 { } else {
EXPECT_EQ(vf, e); auto vf = (expected.pos_or_neg ? Abs(v) : v);
if (expected.float_compare) {
EXPECT_FLOAT_EQ(vf, e);
} else {
EXPECT_EQ(vf, e);
}
} }
} else {
EXPECT_EQ((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>());
} }
} else { },
EXPECT_EQ((c.expected_pos_or_neg ? Abs(v) : v), e); expected.value);
// 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>());
}
},
c.expected);
return HasFailure() ? Action::kStop : Action::kContinue; return HasFailure() ? Action::kStop : Action::kContinue;
}); });
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), c.expected.Failure().error);
}
} }
INSTANTIATE_TEST_SUITE_P( // INSTANTIATE_TEST_SUITE_P( //
@ -374,6 +418,19 @@ std::vector<Case> AtanhCases() {
C({Vec(T(0.0), T(0.9), -T(0.9))}, Vec(T(0.0), T(1.4722193), -T(1.4722193))).FloatComp(), C({Vec(T(0.0), T(0.9), -T(0.9))}, Vec(T(0.0), T(1.4722193), -T(1.4722193))).FloatComp(),
}; };
ConcatIntoIf<finite_only>( //
cases,
std::vector<Case>{
E({1.1_a},
"12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
E({-1.1_a},
"12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
E({T::Inf()},
"12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
E({-T::Inf()},
"12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
});
ConcatIntoIf<!finite_only>( // ConcatIntoIf<!finite_only>( //
cases, std::vector<Case>{ cases, std::vector<Case>{
// If i is NaN, NaN is returned // If i is NaN, NaN is returned
@ -393,38 +450,6 @@ INSTANTIATE_TEST_SUITE_P( //
AtanhCases<f32, false>(), AtanhCases<f32, false>(),
AtanhCases<f16, false>())))); AtanhCases<f16, false>()))));
TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Positive) {
auto* expr = Call(Source{{12, 24}}, "atanh", Expr(1.0_a));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
}
TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Negative) {
auto* expr = Call(Source{{12, 24}}, "atanh", Negation(1.0_a));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
}
TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Positive_INF) {
auto* expr = Call(Source{{12, 24}}, "atanh", Expr(f32::Inf()));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
}
TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Negative_INF) {
auto* expr = Call(Source{{12, 24}}, "atanh", Negation(f32::Inf()));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
}
template <typename T, bool finite_only> template <typename T, bool finite_only>
std::vector<Case> AcosCases() { std::vector<Case> AcosCases() {
std::vector<Case> cases = { std::vector<Case> cases = {
@ -438,6 +463,19 @@ std::vector<Case> AcosCases() {
C({Vec(T(1.0), -T(1.0))}, Vec(T(0), kPi<T>)).FloatComp(), C({Vec(T(1.0), -T(1.0))}, Vec(T(0), kPi<T>)).FloatComp(),
}; };
ConcatIntoIf<finite_only>( //
cases,
std::vector<Case>{
E({1.1_a},
"12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
E({-1.1_a},
"12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
E({T::Inf()},
"12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
E({-T::Inf()},
"12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
});
ConcatIntoIf<!finite_only>( // ConcatIntoIf<!finite_only>( //
cases, std::vector<Case>{ cases, std::vector<Case>{
// If i is NaN, NaN is returned // If i is NaN, NaN is returned
@ -457,38 +495,6 @@ INSTANTIATE_TEST_SUITE_P( //
AcosCases<f32, false>(), AcosCases<f32, false>(),
AcosCases<f16, false>())))); AcosCases<f16, false>()))));
TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Positive) {
auto* expr = Call(Source{{12, 24}}, "acos", Expr(1.1_a));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
}
TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Negative) {
auto* expr = Call(Source{{12, 24}}, "acos", Negation(1.1_a));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
}
TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Positive_INF) {
auto* expr = Call(Source{{12, 24}}, "acos", Expr(f32::Inf()));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
}
TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Negative_INF) {
auto* expr = Call(Source{{12, 24}}, "acos", Negation(f32::Inf()));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
}
template <typename T, bool finite_only> template <typename T, bool finite_only>
std::vector<Case> AsinCases() { std::vector<Case> AsinCases() {
std::vector<Case> cases = { std::vector<Case> cases = {
@ -503,6 +509,19 @@ std::vector<Case> AsinCases() {
C({Vec(T(0.0), T(1.0), -T(1.0))}, Vec(T(0.0), kPiOver2<T>, -kPiOver2<T>)).FloatComp(), C({Vec(T(0.0), T(1.0), -T(1.0))}, Vec(T(0.0), kPiOver2<T>, -kPiOver2<T>)).FloatComp(),
}; };
ConcatIntoIf<finite_only>( //
cases,
std::vector<Case>{
E({1.1_a},
"12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
E({-1.1_a},
"12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
E({T::Inf()},
"12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
E({-T::Inf()},
"12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
});
ConcatIntoIf<!finite_only>( // ConcatIntoIf<!finite_only>( //
cases, std::vector<Case>{ cases, std::vector<Case>{
// If i is NaN, NaN is returned // If i is NaN, NaN is returned
@ -522,38 +541,6 @@ INSTANTIATE_TEST_SUITE_P( //
AsinCases<f32, false>(), AsinCases<f32, false>(),
AsinCases<f16, false>())))); AsinCases<f16, false>()))));
TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Positive) {
auto* expr = Call(Source{{12, 24}}, "asin", Expr(1.1_a));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
}
TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Negative) {
auto* expr = Call(Source{{12, 24}}, "asin", Negation(1.1_a));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
}
TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Positive_INF) {
auto* expr = Call(Source{{12, 24}}, "asin", Expr(f32::Inf()));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
}
TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Negative_INF) {
auto* expr = Call(Source{{12, 24}}, "asin", Negation(f32::Inf()));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
}
template <typename T, bool finite_only> template <typename T, bool finite_only>
std::vector<Case> AsinhCases() { std::vector<Case> AsinhCases() {
std::vector<Case> cases = { std::vector<Case> cases = {
@ -980,12 +967,12 @@ using ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount =
ResolverTestWithParam<std::tuple<size_t, size_t>>; ResolverTestWithParam<std::tuple<size_t, size_t>>;
TEST_P(ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount, Test) { TEST_P(ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount, Test) {
auto& p = GetParam(); auto& p = GetParam();
auto* expr = Call(Source{{12, 24}}, sem::str(sem::BuiltinType::kInsertBits), Expr(1_u), auto* expr = Call(Source{{12, 34}}, sem::str(sem::BuiltinType::kInsertBits), Expr(1_u),
Expr(1_u), Expr(u32(std::get<0>(p))), Expr(u32(std::get<1>(p)))); Expr(1_u), Expr(u32(std::get<0>(p))), Expr(u32(std::get<1>(p))));
GlobalConst("C", expr); GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:24 error: 'offset + 'count' must be less than or equal to the bit width of 'e'"); "12:34 error: 'offset + 'count' must be less than or equal to the bit width of 'e'");
} }
INSTANTIATE_TEST_SUITE_P(InsertBits, INSTANTIATE_TEST_SUITE_P(InsertBits,
ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount, ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount,
@ -1083,12 +1070,12 @@ using ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount =
ResolverTestWithParam<std::tuple<size_t, size_t>>; ResolverTestWithParam<std::tuple<size_t, size_t>>;
TEST_P(ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount, Test) { TEST_P(ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount, Test) {
auto& p = GetParam(); auto& p = GetParam();
auto* expr = Call(Source{{12, 24}}, sem::str(sem::BuiltinType::kExtractBits), Expr(1_u), auto* expr = Call(Source{{12, 34}}, sem::str(sem::BuiltinType::kExtractBits), Expr(1_u),
Expr(u32(std::get<0>(p))), Expr(u32(std::get<1>(p)))); Expr(u32(std::get<0>(p))), Expr(u32(std::get<1>(p))));
GlobalConst("C", expr); GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:24 error: 'offset + 'count' must be less than or equal to the bit width of 'e'"); "12:34 error: 'offset + 'count' must be less than or equal to the bit width of 'e'");
} }
INSTANTIATE_TEST_SUITE_P(ExtractBits, INSTANTIATE_TEST_SUITE_P(ExtractBits,
ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount, ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount,
@ -1282,6 +1269,7 @@ INSTANTIATE_TEST_SUITE_P( //
StepCases<f16>())))); StepCases<f16>()))));
std::vector<Case> QuantizeToF16Cases() { std::vector<Case> QuantizeToF16Cases() {
(void)E({Vec(0_f, 0_f)}, ""); // Currently unused, but will be soon.
return { return {
C({0_f}, 0_f), // C({0_f}, 0_f), //
C({-0_f}, -0_f), // C({-0_f}, -0_f), //