From 3b8b9699d69f1eeb6fadddcbe56ffc68ae57715e Mon Sep 17 00:00:00 2001 From: Antonio Maiorano Date: Wed, 20 Jul 2022 18:00:35 +0000 Subject: [PATCH] tint: Implement const eval of unary minus Bug: tint:1581 Change-Id: I228e8d083229fabfe8a4c0876160d673502e10a3 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/96422 Kokoro: Kokoro Commit-Queue: Antonio Maiorano Reviewed-by: Ben Clayton Reviewed-by: dan sinclair Reviewed-by: Dan Sinclair --- .../mutations/wrap_unary_operator.cc | 7 +- src/tint/intrinsics.def | 5 +- src/tint/resolver/const_eval.cc | 75 +++++++--- src/tint/resolver/const_eval.h | 9 ++ src/tint/resolver/const_eval_test.cc | 82 ++++++++++- src/tint/resolver/intrinsic_table.inl | 132 ++++++++++++------ src/tint/resolver/intrinsic_table_test.cc | 4 +- src/tint/sem/type.cc | 24 ++++ src/tint/sem/type.h | 12 ++ .../msl/generator_impl_unary_op_test.cc | 7 +- 10 files changed, 282 insertions(+), 75 deletions(-) diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc index 8b52732a33..d6612f5486 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc @@ -18,6 +18,8 @@ #include #include "src/tint/program_builder.h" +#include "src/tint/sem/abstract_float.h" +#include "src/tint/sem/abstract_int.h" namespace tint::fuzzers::ast_fuzzer { @@ -98,7 +100,8 @@ std::vector MutationWrapUnaryOperator::GetValidUnaryWrapper( return {ast::UnaryOp::kNot}; } - if (expr_type->is_signed_scalar_or_vector()) { + if (expr_type->is_signed_scalar_or_vector() || + expr_type->is_abstract_integer_scalar_or_vector()) { return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement}; } @@ -106,7 +109,7 @@ std::vector MutationWrapUnaryOperator::GetValidUnaryWrapper( return {ast::UnaryOp::kComplement}; } - if (expr_type->is_float_scalar_or_vector()) { + if (expr_type->is_float_scalar_or_vector() || expr_type->is_abstract_float_scalar_or_vector()) { return {ast::UnaryOp::kNegation}; } diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def index ab77532c90..16f2ff4327 100644 --- a/src/tint/intrinsics.def +++ b/src/tint/intrinsics.def @@ -134,6 +134,7 @@ match fi32: f32 | i32 match fi32f16: f32 | f16 | i32 match iu32: i32 | u32 match aiu32: ai | i32 | u32 +match afi32f16: ai | af | f32 | i32 | f16 match scalar: f32 | f16 | i32 | u32 | bool match abstract_or_scalar: ai | af | f32 | f16 | i32 | u32 | bool match af_f32: af | f32 @@ -822,8 +823,8 @@ op ! (vec) -> vec @const op ~ (T) -> T @const op ~ (vec) -> vec -op - (T) -> T -op - (vec) -> vec +@const op - (T) -> T +@const op - (vec) -> vec //////////////////////////////////////////////////////////////////////////////// // Binary Operators // diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc index 4a8c097989..e2b16b5dde 100644 --- a/src/tint/resolver/const_eval.cc +++ b/src/tint/resolver/const_eval.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -45,14 +46,38 @@ namespace tint::resolver { namespace { -/// TypeDispatch is a helper for calling the function `f`, passing a single zero-value argument of -/// the C++ type that corresponds to the sem::Type `type`. For example, calling `TypeDispatch()` -/// with a type of `sem::I32*` will call the function f with a single argument of `i32(0)`. +/// Helper that calls 'f' passing in `c`'s value +template +auto aiu32Dispatch(const sem::Constant* c, F&& f) { + return Switch( + c->Type(), [&](const sem::AbstractInt*) { return f(c->As()); }, + [&](const sem::I32*) { return f(c->As()); }, + [&](const sem::U32*) { return f(c->As()); }); +} + +/// Helper that calls 'f' passing in `c`'s value +template +auto afi32f16Dispatch(const sem::Constant* c, F&& f) { + return Switch( + c->Type(), [&](const sem::AbstractInt*) { return f(c->As()); }, + [&](const sem::AbstractFloat*) { return f(c->As()); }, + [&](const sem::F32*) { return f(c->As()); }, + [&](const sem::I32*) { return f(c->As()); }, + [&](const sem::F16*) { + // TODO(crbug.com/tint/1502): Support const eval for f16 + return nullptr; + }); +} + +/// ZeroTypeDispatch is a helper for calling the function `f`, passing a single zero-value argument +/// of the C++ type that corresponds to the sem::Type `type`. For example, calling +/// `ZeroTypeDispatch()` with a type of `sem::I32*` will call the function f with a single argument +/// of `i32(0)`. /// @returns the value returned by calling `f`. /// @note `type` must be a scalar or abstract numeric type. Other types will not call `f`, and will /// return the zero-initialized value of the return type for `f`. template -auto TypeDispatch(const sem::Type* type, F&& f) { +auto ZeroTypeDispatch(const sem::Type* type, F&& f) { return Switch( type, // [&](const sem::AbstractInt*) { return f(AInt(0)); }, // @@ -64,20 +89,6 @@ auto TypeDispatch(const sem::Type* type, F&& f) { [&](const sem::Bool*) { return f(static_cast(0)); }); } -/// IntegerDispatch is a helper for calling the function `f`, passing the integer value of the -/// constant c. -/// @returns the value returned by calling `f`. -/// @note `c` must be of an integer type. Other types will not call `f`, and will return the -/// zero-initialized value of the return type for `f` -template -auto IntegerDispatch(const sem::Constant* c, F&& f) { - return Switch( - c->Type(), // - [&](const sem::AbstractInt*) { return f(c->As()); }, // - [&](const sem::I32*) { return f(c->As()); }, // - [&](const sem::U32*) { return f(c->As()); }); -} - /// @returns `value` if `T` is not a Number, otherwise ValueOf returns the inner value of the /// Number. template @@ -142,7 +153,7 @@ struct Element : Constant { return this; } bool failed = false; - auto* res = TypeDispatch(target_ty, [&](auto zero_to) -> const Constant* { + auto* res = ZeroTypeDispatch(target_ty, [&](auto zero_to) -> const Constant* { // `T` is the source type, `value` is the source value. // `TO` is the target type. using TO = std::decay_t; @@ -333,7 +344,7 @@ const Constant* ZeroValue(ProgramBuilder& builder, const sem::Type* type) { return CreateComposite(builder, s, std::move(zeros)); }, [&](Default) -> const Constant* { - return TypeDispatch(type, [&](auto zero) -> const Constant* { + return ZeroTypeDispatch(type, [&](auto zero) -> const Constant* { return CreateElement(builder, type, zero); }); }); @@ -643,12 +654,34 @@ const sem::Constant* ConstEval::OpComplement(const sem::Type*, sem::Expression const* const* args, size_t) { return TransformElements(builder, args[0]->ConstantValue(), [&](const sem::Constant* c) { - return IntegerDispatch(c, [&](auto i) { // + return aiu32Dispatch(c, [&](auto i) { // return CreateElement(builder, c->Type(), decltype(i)(~i.value)); }); }); } +const sem::Constant* ConstEval::OpMinus(const sem::Type*, + sem::Expression const* const* args, + size_t) { + return TransformElements(builder, args[0]->ConstantValue(), [&](const sem::Constant* c) { + return afi32f16Dispatch(c, [&](auto i) { // + // For signed integrals, avoid C++ UB by not negating the smallest negative number. In + // WGSL, this operation is well defined to return the same value, see: + // https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr. + using T = UnwrapNumber; + if constexpr (std::is_integral_v) { + auto v = i.value; + if (v != std::numeric_limits::min()) { + v = -v; + } + return CreateElement(builder, c->Type(), decltype(i)(v)); + } else { + return CreateElement(builder, c->Type(), decltype(i)(-i.value)); + } + }); + }); +} + utils::Result ConstEval::Convert(const sem::Type* target_ty, const sem::Constant* value, const Source& source) { diff --git a/src/tint/resolver/const_eval.h b/src/tint/resolver/const_eval.h index b1e7352a91..6a85c850f4 100644 --- a/src/tint/resolver/const_eval.h +++ b/src/tint/resolver/const_eval.h @@ -187,6 +187,15 @@ class ConstEval { sem::Expression const* const* args, size_t num_args); + /// Minus operator '-' + /// @param ty the expression type + /// @param args the input arguments + /// @param num_args the number of input arguments (must be 1) + /// @return the result value, or null if the value cannot be calculated + const sem::Constant* OpMinus(const sem::Type* ty, + sem::Expression const* const* args, + size_t num_args); + private: /// Adds the given error message to the diagnostics void AddError(const std::string& msg, const Source& source) const; diff --git a/src/tint/resolver/const_eval_test.cc b/src/tint/resolver/const_eval_test.cc index dcc1604a0b..7991f6f945 100644 --- a/src/tint/resolver/const_eval_test.cc +++ b/src/tint/resolver/const_eval_test.cc @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include "gtest/gtest.h" #include "src/tint/resolver/resolver_test_helper.h" @@ -2929,6 +2930,29 @@ TEST_F(ResolverConstEvalTest, MemberAccess) { //////////////////////////////////////////////////////////////////////////////////////////////////// namespace unary_op { +template +auto Highest() { + return T(T::kHighest); +} + +template +auto Lowest() { + return T(T::kLowest); +} + +template +constexpr auto Negate(const Number& v) { + // For signed integrals, avoid C++ UB by not negating the smallest negative number. In + // WGSL, this operation is well defined to return the same value, see: + // https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr. + if constexpr (std::is_integral_v && std::is_signed_v) { + if (v == std::numeric_limits::min()) { + return v; + } + } + return -v; +} + template struct Values { T input; @@ -2936,7 +2960,7 @@ struct Values { }; struct Case { - std::variant, Values, Values> values; + std::variant, Values, Values, Values, Values> values; }; static std::ostream& operator<<(std::ostream& o, const Case& c) { @@ -2952,6 +2976,8 @@ Case C(T input, T expect) { using ResolverConstEvalUnaryOpTest = ResolverTestWithParam>; TEST_P(ResolverConstEvalUnaryOpTest, Test) { + Enable(ast::Extension::kF16); + auto op = std::get<0>(GetParam()); auto c = std::get<1>(GetParam()); std::visit( @@ -3000,6 +3026,60 @@ INSTANTIATE_TEST_SUITE_P(Complement, 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(Highest(), -Highest()), + C(-Highest(), Highest()), + C(Lowest(), Negate(Lowest())), + C(Negate(Lowest()), Lowest()), + // i32 + C(0_i, -0_i), + C(-0_i, 0_i), + C(1_i, -1_i), + C(-1_i, 1_i), + C(Highest(), -Highest()), + C(-Highest(), Highest()), + C(Lowest(), Negate(Lowest())), + C(Negate(Lowest()), 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(Highest(), -Highest()), + C(-Highest(), Highest()), + C(Lowest(), Negate(Lowest())), + C(Negate(Lowest()), 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(Highest(), -Highest()), + C(-Highest(), Highest()), + C(Lowest(), Negate(Lowest())), + C(Negate(Lowest()), 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", nullptr, Negation(Negation(Expr(9223372036854775808_a)))); + (void)c; + EXPECT_TRUE(r()->Resolve()) << r()->error(); + auto* sem = Sem().Get(c); + EXPECT_EQ(sem->ConstantValue()->As(), 9223372036854775808_a); +} + } // namespace unary_op } // namespace diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl index e7b44a332c..811aa3495d 100644 --- a/src/tint/resolver/intrinsic_table.inl +++ b/src/tint/resolver/intrinsic_table.inl @@ -1832,8 +1832,52 @@ std::string Aiu32::String(MatchState*) const { return ss.str(); } -/// TypeMatcher for 'match scalar' +/// TypeMatcher for 'match afi32f16' /// @see src/tint/intrinsics.def:137:7 +class Afi32F16 : public TypeMatcher { + public: + /// Checks whether the given type matches the matcher rules, and returns the + /// expected, canonicalized type on success. + /// Match may define and refine the template types and numbers in state. + /// @param state the MatchState + /// @param type the type to match + /// @returns the canonicalized type on match, otherwise nullptr + const sem::Type* Match(MatchState& state, + const sem::Type* type) const override; + /// @param state the MatchState + /// @return a string representation of the matcher. + std::string String(MatchState* state) const override; +}; + +const sem::Type* Afi32F16::Match(MatchState& state, const sem::Type* ty) const { + if (match_af(ty)) { + return build_af(state); + } + if (match_ai(ty)) { + return build_ai(state); + } + if (match_i32(ty)) { + return build_i32(state); + } + if (match_f32(ty)) { + return build_f32(state); + } + if (match_f16(ty)) { + return build_f16(state); + } + return nullptr; +} + +std::string Afi32F16::String(MatchState*) const { + std::stringstream ss; + // Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support + // template arguments, nor can they match sub-types. As such, they have no use for the MatchState. + ss << Ai().String(nullptr) << ", " << Af().String(nullptr) << ", " << F32().String(nullptr) << ", " << I32().String(nullptr) << " or " << F16().String(nullptr); + return ss.str(); +} + +/// TypeMatcher for 'match scalar' +/// @see src/tint/intrinsics.def:138:7 class Scalar : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -1877,7 +1921,7 @@ std::string Scalar::String(MatchState*) const { } /// TypeMatcher for 'match abstract_or_scalar' -/// @see src/tint/intrinsics.def:138:7 +/// @see src/tint/intrinsics.def:139:7 class AbstractOrScalar : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -1927,7 +1971,7 @@ std::string AbstractOrScalar::String(MatchState*) const { } /// TypeMatcher for 'match af_f32' -/// @see src/tint/intrinsics.def:139:7 +/// @see src/tint/intrinsics.def:140:7 class AfF32 : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -1962,7 +2006,7 @@ std::string AfF32::String(MatchState*) const { } /// TypeMatcher for 'match af_f32f16' -/// @see src/tint/intrinsics.def:140:7 +/// @see src/tint/intrinsics.def:141:7 class AfF32F16 : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -2000,7 +2044,7 @@ std::string AfF32F16::String(MatchState*) const { } /// TypeMatcher for 'match scalar_no_f32' -/// @see src/tint/intrinsics.def:141:7 +/// @see src/tint/intrinsics.def:142:7 class ScalarNoF32 : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -2041,7 +2085,7 @@ std::string ScalarNoF32::String(MatchState*) const { } /// TypeMatcher for 'match scalar_no_f16' -/// @see src/tint/intrinsics.def:142:7 +/// @see src/tint/intrinsics.def:143:7 class ScalarNoF16 : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -2082,7 +2126,7 @@ std::string ScalarNoF16::String(MatchState*) const { } /// TypeMatcher for 'match scalar_no_i32' -/// @see src/tint/intrinsics.def:143:7 +/// @see src/tint/intrinsics.def:144:7 class ScalarNoI32 : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -2123,7 +2167,7 @@ std::string ScalarNoI32::String(MatchState*) const { } /// TypeMatcher for 'match scalar_no_u32' -/// @see src/tint/intrinsics.def:144:7 +/// @see src/tint/intrinsics.def:145:7 class ScalarNoU32 : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -2164,7 +2208,7 @@ std::string ScalarNoU32::String(MatchState*) const { } /// TypeMatcher for 'match scalar_no_bool' -/// @see src/tint/intrinsics.def:145:7 +/// @see src/tint/intrinsics.def:146:7 class ScalarNoBool : public TypeMatcher { public: /// Checks whether the given type matches the matcher rules, and returns the @@ -2205,7 +2249,7 @@ std::string ScalarNoBool::String(MatchState*) const { } /// EnumMatcher for 'match f32_texel_format' -/// @see src/tint/intrinsics.def:156:7 +/// @see src/tint/intrinsics.def:157:7 class F32TexelFormat : public NumberMatcher { public: /// Checks whether the given number matches the enum matcher rules. @@ -2238,7 +2282,7 @@ std::string F32TexelFormat::String(MatchState*) const { } /// EnumMatcher for 'match i32_texel_format' -/// @see src/tint/intrinsics.def:158:7 +/// @see src/tint/intrinsics.def:159:7 class I32TexelFormat : public NumberMatcher { public: /// Checks whether the given number matches the enum matcher rules. @@ -2270,7 +2314,7 @@ std::string I32TexelFormat::String(MatchState*) const { } /// EnumMatcher for 'match u32_texel_format' -/// @see src/tint/intrinsics.def:160:7 +/// @see src/tint/intrinsics.def:161:7 class U32TexelFormat : public NumberMatcher { public: /// Checks whether the given number matches the enum matcher rules. @@ -2302,7 +2346,7 @@ std::string U32TexelFormat::String(MatchState*) const { } /// EnumMatcher for 'match write_only' -/// @see src/tint/intrinsics.def:163:7 +/// @see src/tint/intrinsics.def:164:7 class WriteOnly : public NumberMatcher { public: /// Checks whether the given number matches the enum matcher rules. @@ -2328,7 +2372,7 @@ std::string WriteOnly::String(MatchState*) const { } /// EnumMatcher for 'match function_private_workgroup' -/// @see src/tint/intrinsics.def:165:7 +/// @see src/tint/intrinsics.def:166:7 class FunctionPrivateWorkgroup : public NumberMatcher { public: /// Checks whether the given number matches the enum matcher rules. @@ -2358,7 +2402,7 @@ std::string FunctionPrivateWorkgroup::String(MatchState*) const { } /// EnumMatcher for 'match workgroup_or_storage' -/// @see src/tint/intrinsics.def:166:7 +/// @see src/tint/intrinsics.def:167:7 class WorkgroupOrStorage : public NumberMatcher { public: /// Checks whether the given number matches the enum matcher rules. @@ -2524,6 +2568,7 @@ class Matchers { Fi32F16 Fi32F16_; Iu32 Iu32_; Aiu32 Aiu32_; + Afi32F16 Afi32F16_; Scalar Scalar_; AbstractOrScalar AbstractOrScalar_; AfF32 AfF32_; @@ -2550,7 +2595,7 @@ class Matchers { ~Matchers(); /// The template types, types, and type matchers - TypeMatcher const* const type[66] = { + TypeMatcher const* const type[67] = { /* [0] */ &template_type_0_, /* [1] */ &template_type_1_, /* [2] */ &Bool_, @@ -2608,15 +2653,16 @@ class Matchers { /* [54] */ &Fi32F16_, /* [55] */ &Iu32_, /* [56] */ &Aiu32_, - /* [57] */ &Scalar_, - /* [58] */ &AbstractOrScalar_, - /* [59] */ &AfF32_, - /* [60] */ &AfF32F16_, - /* [61] */ &ScalarNoF32_, - /* [62] */ &ScalarNoF16_, - /* [63] */ &ScalarNoI32_, - /* [64] */ &ScalarNoU32_, - /* [65] */ &ScalarNoBool_, + /* [57] */ &Afi32F16_, + /* [58] */ &Scalar_, + /* [59] */ &AbstractOrScalar_, + /* [60] */ &AfF32_, + /* [61] */ &AfF32F16_, + /* [62] */ &ScalarNoF32_, + /* [63] */ &ScalarNoF16_, + /* [64] */ &ScalarNoI32_, + /* [65] */ &ScalarNoU32_, + /* [66] */ &ScalarNoBool_, }; /// The template numbers, and number matchers @@ -7914,7 +7960,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [1] */ /* name */ "U", - /* matcher index */ 65, + /* matcher index */ 66, }, { /* [2] */ @@ -7924,7 +7970,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [3] */ /* name */ "U", - /* matcher index */ 61, + /* matcher index */ 62, }, { /* [4] */ @@ -7934,7 +7980,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [5] */ /* name */ "U", - /* matcher index */ 62, + /* matcher index */ 63, }, { /* [6] */ @@ -7944,7 +7990,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [7] */ /* name */ "U", - /* matcher index */ 63, + /* matcher index */ 64, }, { /* [8] */ @@ -7954,7 +8000,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [9] */ /* name */ "U", - /* matcher index */ 64, + /* matcher index */ 65, }, { /* [10] */ @@ -7964,7 +8010,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [11] */ /* name */ "T", - /* matcher index */ 60, + /* matcher index */ 61, }, { /* [12] */ @@ -7979,12 +8025,12 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [14] */ /* name */ "T", - /* matcher index */ 58, + /* matcher index */ 59, }, { /* [15] */ /* name */ "T", - /* matcher index */ 57, + /* matcher index */ 58, }, { /* [16] */ @@ -7994,27 +8040,27 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [17] */ /* name */ "T", - /* matcher index */ 65, + /* matcher index */ 66, }, { /* [18] */ /* name */ "T", - /* matcher index */ 62, + /* matcher index */ 63, }, { /* [19] */ /* name */ "T", - /* matcher index */ 61, + /* matcher index */ 62, }, { /* [20] */ /* name */ "T", - /* matcher index */ 64, + /* matcher index */ 65, }, { /* [21] */ /* name */ "T", - /* matcher index */ 63, + /* matcher index */ 64, }, { /* [22] */ @@ -8024,7 +8070,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = { { /* [23] */ /* name */ "T", - /* matcher index */ 54, + /* matcher index */ 57, }, { /* [24] */ @@ -13161,7 +13207,7 @@ constexpr OverloadInfo kOverloads[] = { /* parameters */ &kParameters[862], /* return matcher indices */ &kMatcherIndices[1], /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline), - /* const eval */ nullptr, + /* const eval */ &ConstEval::OpMinus, }, { /* [423] */ @@ -13173,7 +13219,7 @@ constexpr OverloadInfo kOverloads[] = { /* parameters */ &kParameters[863], /* return matcher indices */ &kMatcherIndices[39], /* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline), - /* const eval */ nullptr, + /* const eval */ &ConstEval::OpMinus, }, { /* [424] */ @@ -14521,8 +14567,8 @@ constexpr IntrinsicInfo kUnaryOperators[] = { }, { /* [2] */ - /* op -(T) -> T */ - /* op -(vec) -> vec */ + /* op -(T) -> T */ + /* op -(vec) -> vec */ /* num overloads */ 2, /* overloads */ &kOverloads[422], }, diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc index 30d7909ef2..affcc765f4 100644 --- a/src/tint/resolver/intrinsic_table_test.cc +++ b/src/tint/resolver/intrinsic_table_test.cc @@ -604,8 +604,8 @@ TEST_F(IntrinsicTableTest, MismatchUnaryOp) { EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching overload for operator - (bool) 2 candidate operators: - operator - (T) -> T where: T is f32, f16 or i32 - operator - (vecN) -> vecN where: T is f32, f16 or i32 + operator - (T) -> T where: T is abstract-int, abstract-float, f32, i32 or f16 + operator - (vecN) -> vecN where: T is abstract-int, abstract-float, f32, i32 or f16 )"); } diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc index 9d4c4699db..5b2baa03ee 100644 --- a/src/tint/sem/type.cc +++ b/src/tint/sem/type.cc @@ -136,6 +136,30 @@ bool Type::is_integer_scalar_or_vector() const { return is_unsigned_scalar_or_vector() || is_signed_scalar_or_vector(); } +bool Type::is_abstract_scalar_vector() const { + return Is([](const Vector* v) { return v->type()->Is(); }); +} + +bool Type::is_abstract_integer_vector() const { + return Is([](const Vector* v) { return v->type()->Is(); }); +} + +bool Type::is_abstract_float_vector() const { + return Is([](const Vector* v) { return v->type()->Is(); }); +} + +bool Type::is_abstract_scalar_or_vector() const { + return Is() || is_abstract_scalar_vector(); +} + +bool Type::is_abstract_integer_scalar_or_vector() const { + return Is() || is_abstract_integer_vector(); +} + +bool Type::is_abstract_float_scalar_or_vector() const { + return Is() || is_abstract_float_vector(); +} + bool Type::is_bool_vector() const { return Is([](const Vector* v) { return v->type()->Is(); }); } diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h index 25f3a438a7..3866e8dda8 100644 --- a/src/tint/sem/type.h +++ b/src/tint/sem/type.h @@ -103,6 +103,18 @@ class Type : public Castable { bool is_signed_scalar_or_vector() const; /// @returns true if this type is an integer scalar or vector bool is_integer_scalar_or_vector() const; + /// @returns true if this type is an abstract scalar vector + bool is_abstract_scalar_vector() const; + /// @returns true if this type is an abstract integer vector + bool is_abstract_integer_vector() const; + /// @returns true if this type is an abstract float vector + bool is_abstract_float_vector() const; + /// @returns true if this type is an abstract scalar or vector + bool is_abstract_scalar_or_vector() const; + /// @returns true if this type is an abstract integer scalar or vector + bool is_abstract_integer_scalar_or_vector() const; + /// @returns true if this type is an abstract float scalar or vector + bool is_abstract_float_scalar_or_vector() const; /// @returns true if this type is a boolean vector bool is_bool_vector() const; /// @returns true if this type is boolean scalar or vector diff --git a/src/tint/writer/msl/generator_impl_unary_op_test.cc b/src/tint/writer/msl/generator_impl_unary_op_test.cc index 843e2a4ac7..21d63575b6 100644 --- a/src/tint/writer/msl/generator_impl_unary_op_test.cc +++ b/src/tint/writer/msl/generator_impl_unary_op_test.cc @@ -81,16 +81,15 @@ TEST_F(MslUnaryOpTest, Negation) { EXPECT_EQ(out.str(), "tint_unary_minus(expr)"); } -TEST_F(MslUnaryOpTest, NegationOfIntMin) { - auto* op = create(ast::UnaryOp::kNegation, - Expr(i32(std::numeric_limits::min()))); +TEST_F(MslUnaryOpTest, IntMin) { + auto* op = Expr(i32(std::numeric_limits::min())); WrapInFunction(op); GeneratorImpl& gen = Build(); std::stringstream out; ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error(); - EXPECT_EQ(out.str(), "tint_unary_minus((-2147483647 - 1))"); + EXPECT_EQ(out.str(), "(-2147483647 - 1)"); } } // namespace