From 444e051faa2e29bda2828cd0bc4b7d3f3f20684b Mon Sep 17 00:00:00 2001 From: Alastair Donaldson Date: Fri, 25 Mar 2022 12:31:35 +0000 Subject: [PATCH] AST fuzzer: change binary operator A mutation and mutation finder that changes the operator in a binary expression to something type-compatible. Fixes: tint:1085 Change-Id: I2e35d3cdfdbcc52d4dc5981b187da217fc48e462 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/84640 Reviewed-by: Ryan Harrison Kokoro: Kokoro Commit-Queue: Alastair Donaldson Auto-Submit: Alastair Donaldson --- src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn | 4 + .../fuzzers/tint_ast_fuzzer/CMakeLists.txt | 5 + src/tint/fuzzers/tint_ast_fuzzer/mutation.cc | 4 + .../change_binary_operators.cc | 92 +++ .../change_binary_operators.h | 42 + .../mutations/change_binary_operator.cc | 528 ++++++++++++ .../mutations/change_binary_operator.h | 85 ++ .../mutations/change_binary_operator_test.cc | 749 ++++++++++++++++++ .../mutations/replace_identifier_test.cc | 10 +- src/tint/fuzzers/tint_ast_fuzzer/mutator.cc | 3 + .../tint_ast_fuzzer/probability_context.cc | 3 + .../tint_ast_fuzzer/probability_context.h | 6 + .../protobufs/tint_ast_fuzzer.proto | 15 +- src/tint/program_builder.h | 35 +- src/tint/sem/type.cc | 6 + src/tint/sem/type.h | 2 + 16 files changed, 1580 insertions(+), 9 deletions(-) create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn index 036ec8a58e..70941acb6f 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn +++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn @@ -46,8 +46,12 @@ if (build_with_chromium) { "mutation.h", "mutation_finder.cc", "mutation_finder.h", + "mutation_finders/change_binary_operators.cc", + "mutation_finders/change_binary_operators.h", "mutation_finders/replace_identifiers.cc", "mutation_finders/replace_identifiers.h", + "mutations/change_binary_operator.cc", + "mutations/change_binary_operator.h", "mutations/replace_identifier.cc", "mutations/replace_identifier.h", "mutator.cc", diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt index 757cd5320a..a31d4118e4 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt +++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt @@ -40,7 +40,9 @@ set(LIBTINT_AST_FUZZER_SOURCES ../random_generator_engine.h mutation.h mutation_finder.h + mutation_finders/change_binary_operators.h mutation_finders/replace_identifiers.h + mutations/change_binary_operator.h mutations/replace_identifier.h mutator.h node_id_map.h @@ -55,7 +57,9 @@ set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES} ../random_generator_engine.cc mutation.cc mutation_finder.cc + mutation_finders/change_binary_operators.cc mutation_finders/replace_identifiers.cc + mutations/change_binary_operator.cc mutations/replace_identifier.cc mutator.cc node_id_map.cc @@ -92,6 +96,7 @@ add_tint_ast_fuzzer(tint_ast_wgsl_writer_fuzzer) # Add tests. if (${TINT_BUILD_TESTS}) set(TEST_SOURCES + mutations/change_binary_operator_test.cc mutations/replace_identifier_test.cc) add_executable(tint_ast_fuzzer_unittests ${TEST_SOURCES}) diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc index 63e00b34d3..1c4fb2e83e 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc @@ -16,6 +16,7 @@ #include +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" namespace tint { @@ -30,6 +31,9 @@ std::unique_ptr Mutation::FromMessage( case protobufs::Mutation::kReplaceIdentifier: return std::make_unique( message.replace_identifier()); + case protobufs::Mutation::kChangeBinaryOperator: + return std::make_unique( + message.change_binary_operator()); case protobufs::Mutation::MUTATION_NOT_SET: assert(false && "Mutation is not set"); break; diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc new file mode 100644 index 0000000000..dbfc36e0a1 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.cc @@ -0,0 +1,92 @@ +// 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/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h" + +#include +#include + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +MutationList MutationFinderChangeBinaryOperators::FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const { + MutationList result; + + // Go through each binary expression in the AST and add a mutation that + // replaces its operator with some other type-compatible operator. + + const std::vector all_binary_operators = { + ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, + ast::BinaryOp::kXor, + ast::BinaryOp::kLogicalAnd, + ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual, + ast::BinaryOp::kLessThan, + ast::BinaryOp::kGreaterThan, + ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight, + ast::BinaryOp::kAdd, + ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo}; + + for (const auto* node : program.ASTNodes().Objects()) { + const auto* binary_expr = As(node); + if (!binary_expr) { + continue; + } + + // Get vector of all operators this could be replaced with. + std::vector allowed_replacements; + for (auto candidate_op : all_binary_operators) { + if (MutationChangeBinaryOperator::CanReplaceBinaryOperator( + program, *binary_expr, candidate_op)) { + allowed_replacements.push_back(candidate_op); + } + } + + if (!allowed_replacements.empty()) { + // Choose an available replacement operator at random. + const ast::BinaryOp replacement = + allowed_replacements[probability_context->GetRandomIndex( + allowed_replacements)]; + // Add a mutation according to the chosen replacement. + result.push_back(std::make_unique( + node_id_map->GetId(binary_expr), replacement)); + } + } + + return result; +} + +uint32_t MutationFinderChangeBinaryOperators::GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const { + return probability_context->GetChanceOfChangingBinaryOperators(); +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h new file mode 100644 index 0000000000..460196ad8f --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h @@ -0,0 +1,42 @@ +// 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_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_BINARY_OPERATORS_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_BINARY_OPERATORS_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// Looks for opportunities to apply `MutationChangeBinaryOperator`. +/// +/// Concretely, for each binary expression in the module, tries to replace it +/// with a different, type-compatible operator. +class MutationFinderChangeBinaryOperators : public MutationFinder { + public: + MutationList FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* probability_context) const override; + uint32_t GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const override; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_BINARY_OPERATORS_H_ diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc new file mode 100644 index 0000000000..174424acb5 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc @@ -0,0 +1,528 @@ +// 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/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" + +#include + +#include "src/tint/sem/reference_type.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +namespace { + +bool IsSuitableForShift(const sem::Type* lhs_type, const sem::Type* rhs_type) { + // `a << b` requires b to be an unsigned scalar or vector, and `a` to be an + // integer scalar or vector with the same width as `b`. Similar for `a >> b`. + + if (rhs_type->is_unsigned_integer_scalar()) { + return lhs_type->is_integer_scalar(); + } + if (rhs_type->is_unsigned_integer_vector()) { + return lhs_type->is_unsigned_integer_vector(); + } + return false; +} + +bool CanReplaceAddSubtractWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + // The program is assumed to be well-typed, so this method determines when + // 'new_operator' can be used as a type-preserving replacement in an '+' or + // '-' expression. + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + // '+' and '-' are fully type compatible. + return true; + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // These operators do not have a mixed vector-scalar form, and only work + // on integer types. + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kMultiply: + // '+' and '*' are largely type-compatible, but for matrices they are only + // type-compatible if the matrices are square. + return !lhs_type->is_float_matrix() || lhs_type->is_square_float_matrix(); + case ast::BinaryOp::kDivide: + // '/' is not defined for matrices. + return lhs_type->is_numeric_scalar_or_vector() && + rhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kModulo: + // TODO(https://crbug.com/tint/1370): once fixed, the rules should be the + // same as for divide. + if (lhs_type->is_float_vector() || rhs_type->is_float_vector()) { + return lhs_type == rhs_type; + } + return !lhs_type->is_float_matrix() && !rhs_type->is_float_matrix(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceMultiplyWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + // The program is assumed to be well-typed, so this method determines when + // 'new_operator' can be used as a type-preserving replacement in a '*' + // expression. + switch (new_operator) { + case ast::BinaryOp::kMultiply: + return true; + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + // '*' is type-compatible with '+' and '-' for square matrices, and for + // numeric scalars/vectors. + if (lhs_type->is_square_float_matrix() && + rhs_type->is_square_float_matrix()) { + return true; + } + return lhs_type->is_numeric_scalar_or_vector() && + rhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // These operators require homogeneous integer types. + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kDivide: + // '/' is not defined for matrices. + return lhs_type->is_numeric_scalar_or_vector() && + rhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kModulo: + // TODO(https://crbug.com/tint/1370): once fixed, this should be the same + // as for divide + if (lhs_type->is_float_vector() || rhs_type->is_float_vector()) { + return lhs_type == rhs_type; + } + return !lhs_type->is_float_matrix() && !rhs_type->is_float_matrix(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceDivideWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + // The program is assumed to be well-typed, so this method determines when + // 'new_operator' can be used as a type-preserving replacement in a '/' + // expression. + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + // These operators work in all contexts where '/' works. + return true; + case ast::BinaryOp::kModulo: + // TODO(https://crbug.com/tint/1370): this special case should not be + // required; modulo and divide should work in the same contexts. + return lhs_type->is_integer_scalar_or_vector() || lhs_type == rhs_type; + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // These operators require homogeneous integer types. + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +// TODO(https://crbug.com/tint/1370): once fixed, this method will be removed +// and the same method will be used to check Divide and Modulo. +bool CanReplaceModuloWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + return true; + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + return lhs_type == rhs_type && lhs_type->is_integer_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceLogicalAndLogicalOrWith(ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + // These operators all work whenever '&&' and '||' work. + return true; + default: + return false; + } +} + +bool CanReplaceAndOrWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + // '&' and '|' work in all the same contexts. + return true; + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + case ast::BinaryOp::kXor: + // '&' and '|' can be applied to booleans. In all other contexts, + // integer numeric operators work. + return !lhs_type->is_bool_scalar_or_vector(); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + // '&' and '|' can be applied to booleans, and for boolean scalar + // scalar contexts, their logical counterparts work. + return lhs_type->Is(); + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + // '&' and '|' can be applied to booleans, and in these contexts equality + // comparison operators also work. + return lhs_type->is_bool_scalar_or_vector(); + default: + return false; + } +} + +bool CanReplaceXorWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // '^' only works on integer types, and in any such context, all other + // integer operators also work. + return true; + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return IsSuitableForShift(lhs_type, rhs_type); + default: + return false; + } +} + +bool CanReplaceShiftLeftShiftRightWith(const sem::Type* lhs_type, + const sem::Type* rhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + // These operators are type-compatible. + return true; + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + case ast::BinaryOp::kMultiply: + case ast::BinaryOp::kDivide: + case ast::BinaryOp::kModulo: + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + case ast::BinaryOp::kXor: + // Shift operators allow mixing of signed and unsigned arguments, but in + // the case where the arguments are homogeneous, they are type-compatible + // with other numeric operators. + return lhs_type == rhs_type; + default: + return false; + } +} + +bool CanReplaceEqualNotEqualWith(const sem::Type* lhs_type, + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + // These operators are type-compatible. + return true; + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + // An equality comparison between numeric types can be changed to an + // ordered comparison. + return lhs_type->is_numeric_scalar_or_vector(); + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + // An equality comparison between boolean scalars can be turned into a + // logical operation. + return lhs_type->Is(); + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + // An equality comparison between boolean scalars or vectors can be turned + // into a component-wise non-short-circuit logical operation. + return lhs_type->is_bool_scalar_or_vector(); + default: + return false; + } +} + +bool CanReplaceLessThanLessThanEqualGreaterThanGreaterThanEqualWith( + ast::BinaryOp new_operator) { + switch (new_operator) { + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + // Ordered comparison operators can be interchanged, and equality + // operators can be used in their place. + return true; + default: + return false; + } +} +} // namespace + +MutationChangeBinaryOperator::MutationChangeBinaryOperator( + protobufs::MutationChangeBinaryOperator message) + : message_(std::move(message)) {} + +MutationChangeBinaryOperator::MutationChangeBinaryOperator( + uint32_t binary_expr_id, + ast::BinaryOp new_operator) { + message_.set_binary_expr_id(binary_expr_id); + message_.set_new_operator(static_cast(new_operator)); +} + +bool MutationChangeBinaryOperator::CanReplaceBinaryOperator( + const Program& program, + const ast::BinaryExpression& binary_expr, + ast::BinaryOp new_operator) { + if (new_operator == binary_expr.op) { + // An operator should not be replaced with itself, as this would be a no-op. + return false; + } + + // Get the types of the operators. + const auto* lhs_type = program.Sem().Get(binary_expr.lhs)->Type(); + const auto* rhs_type = program.Sem().Get(binary_expr.rhs)->Type(); + + // If these are reference types, unwrap them to get the pointee type. + const sem::Type* lhs_basic_type = + lhs_type->Is() + ? lhs_type->As()->StoreType() + : lhs_type; + const sem::Type* rhs_basic_type = + rhs_type->Is() + ? rhs_type->As()->StoreType() + : rhs_type; + + switch (binary_expr.op) { + case ast::BinaryOp::kAdd: + case ast::BinaryOp::kSubtract: + return CanReplaceAddSubtractWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kMultiply: + return CanReplaceMultiplyWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kDivide: + return CanReplaceDivideWith(lhs_basic_type, rhs_basic_type, new_operator); + case ast::BinaryOp::kModulo: + return CanReplaceModuloWith(lhs_basic_type, rhs_basic_type, new_operator); + case ast::BinaryOp::kAnd: + case ast::BinaryOp::kOr: + return CanReplaceAndOrWith(lhs_basic_type, rhs_basic_type, new_operator); + case ast::BinaryOp::kXor: + return CanReplaceXorWith(lhs_basic_type, rhs_basic_type, new_operator); + case ast::BinaryOp::kShiftLeft: + case ast::BinaryOp::kShiftRight: + return CanReplaceShiftLeftShiftRightWith(lhs_basic_type, rhs_basic_type, + new_operator); + case ast::BinaryOp::kLogicalAnd: + case ast::BinaryOp::kLogicalOr: + return CanReplaceLogicalAndLogicalOrWith(new_operator); + case ast::BinaryOp::kEqual: + case ast::BinaryOp::kNotEqual: + return CanReplaceEqualNotEqualWith(lhs_basic_type, new_operator); + case ast::BinaryOp::kLessThan: + case ast::BinaryOp::kLessThanEqual: + case ast::BinaryOp::kGreaterThan: + case ast::BinaryOp::kGreaterThanEqual: + case ast::BinaryOp::kNone: + return CanReplaceLessThanLessThanEqualGreaterThanGreaterThanEqualWith( + new_operator); + assert(false && "Unreachable"); + return false; + } +} + +bool MutationChangeBinaryOperator::IsApplicable( + const Program& program, + const NodeIdMap& node_id_map) const { + const auto* binary_expr_node = + As(node_id_map.GetNode(message_.binary_expr_id())); + if (binary_expr_node == nullptr) { + // Either the id does not exist, or does not correspond to a binary + // expression. + return false; + } + // Check whether the replacement is acceptable. + const auto new_operator = static_cast(message_.new_operator()); + return CanReplaceBinaryOperator(program, *binary_expr_node, new_operator); +} + +void MutationChangeBinaryOperator::Apply(const NodeIdMap& node_id_map, + CloneContext* clone_context, + NodeIdMap* new_node_id_map) const { + // Get the node whose operator is to be replaced. + const auto* binary_expr_node = + As(node_id_map.GetNode(message_.binary_expr_id())); + + // Clone the binary expression, with the appropriate new operator. + const ast::BinaryExpression* cloned_replacement; + switch (static_cast(message_.new_operator())) { + case ast::BinaryOp::kAnd: + cloned_replacement = + clone_context->dst->And(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kOr: + cloned_replacement = + clone_context->dst->Or(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kXor: + cloned_replacement = + clone_context->dst->Xor(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLogicalAnd: + cloned_replacement = clone_context->dst->LogicalAnd( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLogicalOr: + cloned_replacement = clone_context->dst->LogicalOr( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kEqual: + cloned_replacement = clone_context->dst->Equal( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kNotEqual: + cloned_replacement = clone_context->dst->NotEqual( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLessThan: + cloned_replacement = clone_context->dst->LessThan( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kGreaterThan: + cloned_replacement = clone_context->dst->GreaterThan( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kLessThanEqual: + cloned_replacement = clone_context->dst->LessThanEqual( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kGreaterThanEqual: + cloned_replacement = clone_context->dst->GreaterThanEqual( + clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kShiftLeft: + cloned_replacement = + clone_context->dst->Shl(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kShiftRight: + cloned_replacement = + clone_context->dst->Shr(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kAdd: + cloned_replacement = + clone_context->dst->Add(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kSubtract: + cloned_replacement = + clone_context->dst->Sub(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kMultiply: + cloned_replacement = + clone_context->dst->Mul(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kDivide: + cloned_replacement = + clone_context->dst->Div(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kModulo: + cloned_replacement = + clone_context->dst->Mod(clone_context->Clone(binary_expr_node->lhs), + clone_context->Clone(binary_expr_node->rhs)); + break; + case ast::BinaryOp::kNone: + cloned_replacement = nullptr; + assert(false && "Unreachable"); + } + // Set things up so that the original binary expression will be replaced with + // its clone, and update the id mapping. + clone_context->Replace(binary_expr_node, cloned_replacement); + new_node_id_map->Add(cloned_replacement, message_.binary_expr_id()); +} + +protobufs::Mutation MutationChangeBinaryOperator::ToMessage() const { + protobufs::Mutation mutation; + *mutation.mutable_change_binary_operator() = message_; + return mutation; +} + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h new file mode 100644 index 0000000000..73ac22b950 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h @@ -0,0 +1,85 @@ +// 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_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_BINARY_OPERATOR_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_BINARY_OPERATOR_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" + +#include "src/tint/ast/binary_expression.h" +#include "src/tint/program.h" +#include "src/tint/sem/variable.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { + +/// @see MutationChangeBinaryOperator::Apply +class MutationChangeBinaryOperator : public Mutation { + public: + /// @brief Constructs an instance of this mutation from a protobuf message. + /// @param message - protobuf message + explicit MutationChangeBinaryOperator( + protobufs::MutationChangeBinaryOperator message); + + /// @brief Constructor. + /// @param binary_expr_id - the id of a binary expression. + /// @param new_operator - a new binary operator to replace the one used in the + /// expression. + MutationChangeBinaryOperator(uint32_t binary_expr_id, + ast::BinaryOp new_operator); + + /// @copybrief Mutation::IsApplicable + /// + /// The mutation is applicable iff: + /// - `binary_expr_id` is a valid id of an `ast::BinaryExpression`. + /// - `new_operator` is type-compatible with the arguments of the binary + /// expression. + /// + /// @copydetails Mutation::IsApplicable + bool IsApplicable(const tint::Program& program, + const NodeIdMap& node_id_map) const override; + + /// @copybrief Mutation::Apply + /// + /// Replaces binary operator in the binary expression corresponding to + /// `binary_expr_id` with `new_operator`. + /// + /// @copydetails Mutation::Apply + void Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const override; + + protobufs::Mutation ToMessage() const override; + + /// @brief Determines whether replacing the operator of a binary expression + /// with another operator would preserve well-typedness. + /// @param program - the program that owns the binary expression. + /// @param binary_expr - the binary expression being considered for mutation. + /// @param new_operator - a new binary operator to be checked as a candidate + /// replacement for the binary expression's operator. + /// @return `true` if and only if the replacement would be well-typed. + static bool CanReplaceBinaryOperator(const Program& program, + const ast::BinaryExpression& binary_expr, + ast::BinaryOp new_operator); + + private: + protobufs::MutationChangeBinaryOperator message_; +}; + +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_BINARY_OPERATOR_H_ diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc new file mode 100644 index 0000000000..1120d6f1c9 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator_test.cc @@ -0,0 +1,749 @@ +// 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/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" +#include "src/tint/program_builder.h" +#include "src/tint/reader/wgsl/parser.h" +#include "src/tint/writer/wgsl/generator.h" + +namespace tint { +namespace fuzzers { +namespace ast_fuzzer { +namespace { + +std::string OpToString(ast::BinaryOp op) { + switch (op) { + case ast::BinaryOp::kNone: + assert(false && "Unreachable"); + return ""; + case ast::BinaryOp::kAnd: + return "&"; + case ast::BinaryOp::kOr: + return "|"; + case ast::BinaryOp::kXor: + return "^"; + case ast::BinaryOp::kLogicalAnd: + return "&&"; + case ast::BinaryOp::kLogicalOr: + return "||"; + case ast::BinaryOp::kEqual: + return "=="; + case ast::BinaryOp::kNotEqual: + return "!="; + case ast::BinaryOp::kLessThan: + return "<"; + case ast::BinaryOp::kGreaterThan: + return ">"; + case ast::BinaryOp::kLessThanEqual: + return "<="; + case ast::BinaryOp::kGreaterThanEqual: + return ">="; + case ast::BinaryOp::kShiftLeft: + return "<<"; + case ast::BinaryOp::kShiftRight: + return ">>"; + case ast::BinaryOp::kAdd: + return "+"; + case ast::BinaryOp::kSubtract: + return "-"; + case ast::BinaryOp::kMultiply: + return "*"; + case ast::BinaryOp::kDivide: + return "/"; + case ast::BinaryOp::kModulo: + return "%"; + } +} + +TEST(ChangeBinaryOperatorTest, NotApplicable_Simple) { + std::string content = R"( + fn main() { + let a : i32 = 1 + 2; + } + )"; + Source::File file("test.wgsl", content); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements; + + const auto* a_var = + main_fn_stmts[0]->As()->variable; + ASSERT_NE(a_var, nullptr); + + auto a_var_id = node_id_map.GetId(a_var); + + const auto* sum_expr = a_var->constructor->As(); + ASSERT_NE(sum_expr, nullptr); + + auto sum_expr_id = node_id_map.GetId(sum_expr); + ASSERT_NE(sum_expr_id, 0); + + // binary_expr_id is invalid. + EXPECT_FALSE(MutationChangeBinaryOperator(0, ast::BinaryOp::kSubtract) + .IsApplicable(program, node_id_map)); + + // binary_expr_id is not a binary expression. + EXPECT_FALSE(MutationChangeBinaryOperator(a_var_id, ast::BinaryOp::kSubtract) + .IsApplicable(program, node_id_map)); + + // new_operator is applicable to the argument types. + EXPECT_FALSE(MutationChangeBinaryOperator(0, ast::BinaryOp::kLogicalAnd) + .IsApplicable(program, node_id_map)); + + // new_operator does not have the right result type. + EXPECT_FALSE(MutationChangeBinaryOperator(0, ast::BinaryOp::kLessThan) + .IsApplicable(program, node_id_map)); +} + +TEST(ChangeBinaryOperatorTest, Applicable_Simple) { + std::string shader = R"(fn main() { + let a : i32 = (1 + 2); +} +)"; + Source::File file("test.wgsl", shader); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& main_fn_stmts = program.AST().Functions()[0]->body->statements; + + const auto* a_var = + main_fn_stmts[0]->As()->variable; + ASSERT_NE(a_var, nullptr); + + const auto* sum_expr = a_var->constructor->As(); + ASSERT_NE(sum_expr, nullptr); + + auto sum_expr_id = node_id_map.GetId(sum_expr); + ASSERT_NE(sum_expr_id, 0); + + ASSERT_TRUE(MaybeApplyMutation( + program, + MutationChangeBinaryOperator(sum_expr_id, ast::BinaryOp::kSubtract), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + std::string expected_shader = R"(fn main() { + let a : i32 = (1 - 2); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +void CheckMutations( + const std::string& lhs_type, + const std::string& rhs_type, + const std::string& result_type, + ast::BinaryOp original_operator, + const std::unordered_set& allowed_replacement_operators) { + std::stringstream shader; + shader << "fn foo(a : " << lhs_type << ", b : " << rhs_type + ") {\n" + << " let r : " << result_type + << " = (a " + OpToString(original_operator) << " b);\n}\n"; + + const std::vector all_operators = { + ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, + ast::BinaryOp::kXor, + ast::BinaryOp::kLogicalAnd, + ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual, + ast::BinaryOp::kLessThan, + ast::BinaryOp::kGreaterThan, + ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight, + ast::BinaryOp::kAdd, + ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo}; + + for (auto new_operator : all_operators) { + Source::File file("test.wgsl", shader.str()); + auto program = reader::wgsl::Parse(&file); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + NodeIdMap node_id_map(program); + + const auto& stmts = program.AST().Functions()[0]->body->statements; + + const auto* r_var = stmts[0]->As()->variable; + ASSERT_NE(r_var, nullptr); + + const auto* binary_expr = r_var->constructor->As(); + ASSERT_NE(binary_expr, nullptr); + + auto binary_expr_id = node_id_map.GetId(binary_expr); + ASSERT_NE(binary_expr_id, 0); + + MutationChangeBinaryOperator mutation(binary_expr_id, new_operator); + + std::stringstream expected_shader; + expected_shader << "fn foo(a : " << lhs_type << ", b : " << rhs_type + << ") {\n" + << " let r : " << result_type << " = (a " + << OpToString(new_operator) << " b);\n}\n"; + + if (allowed_replacement_operators.count(new_operator) == 0) { + ASSERT_FALSE(mutation.IsApplicable(program, node_id_map)); + if (new_operator != binary_expr->op) { + Source::File invalid_file("test.wgsl", expected_shader.str()); + auto invalid_program = reader::wgsl::Parse(&invalid_file); + ASSERT_FALSE(invalid_program.IsValid()) << program.Diagnostics().str(); + } + } else { + ASSERT_TRUE(MaybeApplyMutation(program, mutation, node_id_map, &program, + &node_id_map, nullptr)); + ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str(); + + writer::wgsl::Options options; + auto result = writer::wgsl::Generate(&program, options); + ASSERT_TRUE(result.success) << result.error; + + ASSERT_EQ(expected_shader.str(), result.wgsl); + } + } +} + +TEST(ChangeBinaryOperatorTest, AddSubtract) { + for (auto op : {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract}) { + const ast::BinaryOp other_op = op == ast::BinaryOp::kAdd + ? ast::BinaryOp::kSubtract + : ast::BinaryOp::kAdd; + for (std::string type : {"i32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, op, + {other_op, ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, op, + {other_op, ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2", "vec3", "vec4"}) { + CheckMutations(type, type, type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, op, + {other_op, ast::BinaryOp::kMultiply, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "f32"; + CheckMutations( + vector_type, scalar_type, vector_type, op, + { + other_op, ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide + // TODO(https://crbug.com/tint/1370): once fixed, add kModulo + }); + CheckMutations( + scalar_type, vector_type, vector_type, op, + { + other_op, ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide + // TODO(https://crbug.com/tint/1370): once fixed, add kModulo + }); + } + for (std::string square_matrix_type : + {"mat2x2", "mat3x3", "mat4x4"}) { + CheckMutations(square_matrix_type, square_matrix_type, square_matrix_type, + op, {other_op, ast::BinaryOp::kMultiply}); + } + for (std::string non_square_matrix_type : + {"mat2x3", "mat2x4", "mat3x2", "mat3x4", + "mat4x2", "mat4x3"}) { + CheckMutations(non_square_matrix_type, non_square_matrix_type, + non_square_matrix_type, op, {other_op}); + } + } +} + +TEST(ChangeBinaryOperatorTest, Mul) { + for (std::string type : {"i32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2", "vec3", "vec4"}) { + CheckMutations(type, type, type, ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "f32"; + CheckMutations( + vector_type, scalar_type, vector_type, ast::BinaryOp::kMultiply, + { + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide + // TODO(https://crbug.com/tint/1370): once fixed, add kModulo + }); + CheckMutations( + scalar_type, vector_type, vector_type, ast::BinaryOp::kMultiply, + { + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kDivide + // TODO(https://crbug.com/tint/1370): once fixed, add kModulo + }); + } + for (std::string square_matrix_type : + {"mat2x2", "mat3x3", "mat4x4"}) { + CheckMutations(square_matrix_type, square_matrix_type, square_matrix_type, + ast::BinaryOp::kMultiply, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract}); + } + + CheckMutations("vec2", "mat2x2", "vec2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec2", "mat3x2", "vec3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec2", "mat4x2", "vec4", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat2x2", "vec2", "vec2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x2", "mat3x2", "mat3x2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x2", "mat4x2", "mat4x2", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat2x3", "vec2", "vec3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x3", "mat2x2", "mat2x3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x3", "mat3x2", "mat3x3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x3", "mat4x2", "mat4x3", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat2x4", "vec2", "vec4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x4", "mat2x2", "mat2x4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x4", "mat3x2", "mat3x4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat2x4", "mat4x2", "mat4x4", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("vec3", "mat2x3", "vec2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec3", "mat3x3", "vec3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec3", "mat4x3", "vec4", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat3x2", "vec3", "vec2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x2", "mat2x3", "mat2x2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x2", "mat3x3", "mat3x2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x2", "mat4x3", "mat4x2", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat3x3", "vec3", "vec3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x3", "mat2x3", "mat2x3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x3", "mat4x3", "mat4x3", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat3x4", "vec3", "vec4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x4", "mat2x3", "mat2x4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x4", "mat3x3", "mat3x4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat3x4", "mat4x3", "mat4x4", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("vec4", "mat2x4", "vec2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec4", "mat3x4", "vec3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("vec4", "mat4x4", "vec4", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat4x2", "vec4", "vec2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x2", "mat2x4", "mat2x2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x2", "mat3x4", "mat3x2", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x2", "mat4x4", "mat4x2", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat4x3", "vec4", "vec3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x3", "mat2x4", "mat2x3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x3", "mat3x4", "mat3x3", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x3", "mat4x4", "mat4x3", + ast::BinaryOp::kMultiply, {}); + + CheckMutations("mat4x4", "vec4", "vec4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x4", "mat2x4", "mat2x4", + ast::BinaryOp::kMultiply, {}); + CheckMutations("mat4x4", "mat3x4", "mat3x4", + ast::BinaryOp::kMultiply, {}); +} + +TEST(ChangeBinaryOperatorTest, Divide) { + for (std::string type : {"i32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2", "vec3", "vec4"}) { + CheckMutations(type, type, type, ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kDivide, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kModulo}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "f32"; + CheckMutations( + vector_type, scalar_type, vector_type, ast::BinaryOp::kDivide, + { + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply + // TODO(https://crbug.com/tint/1370): once fixed, add kModulo + }); + CheckMutations( + scalar_type, vector_type, vector_type, ast::BinaryOp::kDivide, + { + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply + // TODO(https://crbug.com/tint/1370): once fixed, add kModulo + }); + } +} + +// TODO(https://crbug.com/tint/1370): once fixed, combine this with the Divide +// test +TEST(ChangeBinaryOperatorTest, Modulo) { + for (std::string type : {"i32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}); + } + for (std::string type : {"u32", "vec2", "vec3", "vec4"}) { + CheckMutations( + type, type, type, ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight}); + } + for (std::string type : {"f32", "vec2", "vec3", "vec4"}) { + CheckMutations(type, type, type, ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "i32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + } + for (std::string vector_type : {"vec2", "vec3", "vec4"}) { + std::string scalar_type = "u32"; + CheckMutations(vector_type, scalar_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + CheckMutations(scalar_type, vector_type, vector_type, + ast::BinaryOp::kModulo, + {ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide}); + } + // TODO(https://crbug.com/tint/1370): mixed float scalars/vectors will be + // added when this test is combined with the Divide test +} + +TEST(ChangeBinaryOperatorTest, AndOrXor) { + for (auto op : + {ast::BinaryOp::kAnd, ast::BinaryOp::kOr, ast::BinaryOp::kXor}) { + std::unordered_set allowed_replacement_operators_signed{ + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}; + allowed_replacement_operators_signed.erase(op); + for (std::string type : {"i32", "vec2", "vec3", "vec4"}) { + CheckMutations(type, type, type, op, + allowed_replacement_operators_signed); + } + std::unordered_set allowed_replacement_operators_unsigned{ + ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract, + ast::BinaryOp::kMultiply, ast::BinaryOp::kDivide, + ast::BinaryOp::kModulo, ast::BinaryOp::kShiftLeft, + ast::BinaryOp::kShiftRight, ast::BinaryOp::kAnd, + ast::BinaryOp::kOr, ast::BinaryOp::kXor}; + allowed_replacement_operators_unsigned.erase(op); + for (std::string type : {"u32", "vec2", "vec3", "vec4"}) { + CheckMutations(type, type, type, op, + allowed_replacement_operators_unsigned); + } + if (op != ast::BinaryOp::kXor) { + for (std::string type : + {"bool", "vec2", "vec3", "vec4"}) { + std::unordered_set allowed_replacement_operators_bool{ + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual}; + allowed_replacement_operators_bool.erase(op); + if (type == "bool") { + allowed_replacement_operators_bool.insert(ast::BinaryOp::kLogicalAnd); + allowed_replacement_operators_bool.insert(ast::BinaryOp::kLogicalOr); + } + CheckMutations(type, type, type, op, + allowed_replacement_operators_bool); + } + } + } +} + +TEST(ChangeBinaryOperatorTest, EqualNotEqual) { + for (auto op : {ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}) { + for (std::string element_type : {"i32", "u32", "f32"}) { + for (size_t element_count = 1; element_count <= 4; element_count++) { + std::stringstream argument_type; + std::stringstream result_type; + if (element_count == 1) { + argument_type << element_type; + result_type << "bool"; + } else { + argument_type << "vec" << element_count << "<" << element_type << ">"; + result_type << "vec" << element_count << ""; + } + std::unordered_set allowed_replacement_operators{ + ast::BinaryOp::kLessThan, ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThan, ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations(argument_type.str(), argument_type.str(), + result_type.str(), op, allowed_replacement_operators); + } + } + { + std::unordered_set allowed_replacement_operators{ + ast::BinaryOp::kLogicalAnd, ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations("bool", "bool", "bool", op, allowed_replacement_operators); + } + for (size_t element_count = 2; element_count <= 4; element_count++) { + std::stringstream argument_and_result_type; + argument_and_result_type << "vec" << element_count << ""; + std::unordered_set allowed_replacement_operators{ + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, ast::BinaryOp::kEqual, + ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations( + argument_and_result_type.str(), argument_and_result_type.str(), + argument_and_result_type.str(), op, allowed_replacement_operators); + } + } +} + +TEST(ChangeBinaryOperatorTest, + LessThanLessThanEqualGreaterThanGreaterThanEqual) { + for (auto op : + {ast::BinaryOp::kLessThan, ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThan, ast::BinaryOp::kGreaterThanEqual}) { + for (std::string element_type : {"i32", "u32", "f32"}) { + for (size_t element_count = 1; element_count <= 4; element_count++) { + std::stringstream argument_type; + std::stringstream result_type; + if (element_count == 1) { + argument_type << element_type; + result_type << "bool"; + } else { + argument_type << "vec" << element_count << "<" << element_type << ">"; + result_type << "vec" << element_count << ""; + } + std::unordered_set allowed_replacement_operators{ + ast::BinaryOp::kLessThan, ast::BinaryOp::kLessThanEqual, + ast::BinaryOp::kGreaterThan, ast::BinaryOp::kGreaterThanEqual, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations(argument_type.str(), argument_type.str(), + result_type.str(), op, allowed_replacement_operators); + } + } + } +} + +TEST(ChangeBinaryOperatorTest, LogicalAndLogicalOr) { + for (auto op : {ast::BinaryOp::kLogicalAnd, ast::BinaryOp::kLogicalOr}) { + std::unordered_set allowed_replacement_operators{ + ast::BinaryOp::kLogicalAnd, ast::BinaryOp::kLogicalOr, + ast::BinaryOp::kAnd, ast::BinaryOp::kOr, + ast::BinaryOp::kEqual, ast::BinaryOp::kNotEqual}; + allowed_replacement_operators.erase(op); + CheckMutations("bool", "bool", "bool", op, allowed_replacement_operators); + } +} + +TEST(ChangeBinaryOperatorTest, ShiftLeftShiftRight) { + for (auto op : {ast::BinaryOp::kShiftLeft, ast::BinaryOp::kShiftRight}) { + for (std::string lhs_element_type : {"i32", "u32"}) { + for (size_t element_count = 1; element_count <= 4; element_count++) { + std::stringstream lhs_and_result_type; + std::stringstream rhs_type; + if (element_count == 1) { + lhs_and_result_type << lhs_element_type; + rhs_type << "u32"; + } else { + lhs_and_result_type << "vec" << element_count << "<" + << lhs_element_type << ">"; + rhs_type << "vec" << element_count << ""; + } + std::unordered_set allowed_replacement_operators{ + ast::BinaryOp::kShiftLeft, ast::BinaryOp::kShiftRight}; + allowed_replacement_operators.erase(op); + if (lhs_element_type == "u32") { + allowed_replacement_operators.insert(ast::BinaryOp::kAdd); + allowed_replacement_operators.insert(ast::BinaryOp::kSubtract); + allowed_replacement_operators.insert(ast::BinaryOp::kMultiply); + allowed_replacement_operators.insert(ast::BinaryOp::kDivide); + allowed_replacement_operators.insert(ast::BinaryOp::kModulo); + allowed_replacement_operators.insert(ast::BinaryOp::kAnd); + allowed_replacement_operators.insert(ast::BinaryOp::kOr); + allowed_replacement_operators.insert(ast::BinaryOp::kXor); + } + CheckMutations(lhs_and_result_type.str(), rhs_type.str(), + lhs_and_result_type.str(), op, + allowed_replacement_operators); + } + } + } +} + +} // namespace +} // namespace ast_fuzzer +} // namespace fuzzers +} // namespace tint diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc index 6ffc40ac58..bdb730c858 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier_test.cc @@ -12,17 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" + #include #include "gtest/gtest.h" -#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" -#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" -#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h" - -#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" - #include "src/tint/ast/call_statement.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" #include "src/tint/program_builder.h" #include "src/tint/reader/wgsl/parser.h" #include "src/tint/writer/wgsl/generator.h" diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc index 26ce653114..63d62e2466 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc @@ -20,6 +20,7 @@ #include #include +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h" #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h" #include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h" @@ -45,6 +46,8 @@ MutationFinderList CreateMutationFinders( bool enable_all_mutations) { MutationFinderList result; do { + MaybeAddFinder( + enable_all_mutations, probability_context, &result); MaybeAddFinder( enable_all_mutations, probability_context, &result); } while (result.empty()); diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc index 515078cb7e..f41f5b9db6 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc @@ -21,12 +21,15 @@ namespace fuzzers { namespace ast_fuzzer { namespace { +const std::pair kChanceOfChangingBinaryOperators = {30, 90}; const std::pair kChanceOfReplacingIdentifiers = {30, 70}; } // namespace ProbabilityContext::ProbabilityContext(RandomGenerator* generator) : generator_(generator), + chance_of_changing_binary_operators_( + RandomFromRange(kChanceOfChangingBinaryOperators)), chance_of_replacing_identifiers_( RandomFromRange(kChanceOfReplacingIdentifiers)) { assert(generator != nullptr && "generator must not be nullptr"); diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h index 0d6393aa60..b890abafce 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h @@ -55,6 +55,11 @@ class ProbabilityContext { return static_cast(generator_->GetUInt64(arr.size())); } + /// @return the probability of replacing some binary operator with another. + uint32_t GetChanceOfChangingBinaryOperators() const { + return chance_of_changing_binary_operators_; + } + /// @return the probability of replacing some identifier with some other one. uint32_t GetChanceOfReplacingIdentifiers() const { return chance_of_replacing_identifiers_; @@ -67,6 +72,7 @@ class ProbabilityContext { RandomGenerator* generator_; + uint32_t chance_of_changing_binary_operators_; uint32_t chance_of_replacing_identifiers_; }; diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto index 3682a0cac2..e60e35de4d 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto +++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto @@ -17,7 +17,10 @@ syntax = "proto3"; package tint.fuzzers.ast_fuzzer.protobufs; message Mutation { - oneof mutation { MutationReplaceIdentifier replace_identifier = 1; }; + oneof mutation { + MutationReplaceIdentifier replace_identifier = 1; + MutationChangeBinaryOperator change_binary_operator = 2; + }; } message MutationSequence { @@ -46,3 +49,13 @@ message MutationReplaceIdentifier { // The id of a definition of a variable to replace the use with. uint32 replacement_id = 2; } + +message MutationChangeBinaryOperator { + // This transformation replaces one binary operator with another. + + // The id of a binary expression in the AST. + uint32 binary_expr_id = 1; + + // A BinaryOp representing the new binary operator. + uint32 new_operator = 2; +} diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h index c6a5403e6e..1d40cb171e 100644 --- a/src/tint/program_builder.h +++ b/src/tint/program_builder.h @@ -1723,7 +1723,7 @@ class ProgramBuilder { /// @param rhs the right hand argument to the division operation /// @returns a `ast::BinaryExpression` dividing `lhs` by `rhs` template - const ast::Expression* Div(LHS&& lhs, RHS&& rhs) { + const ast::BinaryExpression* Div(LHS&& lhs, RHS&& rhs) { return create(ast::BinaryOp::kDivide, Expr(std::forward(lhs)), Expr(std::forward(rhs))); @@ -1733,7 +1733,7 @@ class ProgramBuilder { /// @param rhs the right hand argument to the modulo operation /// @returns a `ast::BinaryExpression` applying modulo of `lhs` by `rhs` template - const ast::Expression* Mod(LHS&& lhs, RHS&& rhs) { + const ast::BinaryExpression* Mod(LHS&& lhs, RHS&& rhs) { return create(ast::BinaryOp::kModulo, Expr(std::forward(lhs)), Expr(std::forward(rhs))); @@ -1769,6 +1769,26 @@ class ProgramBuilder { Expr(std::forward(rhs))); } + /// @param lhs the left hand argument to the logical and operation + /// @param rhs the right hand argument to the logical and operation + /// @returns a `ast::BinaryExpression` of `lhs` && `rhs` + template + const ast::BinaryExpression* LogicalAnd(LHS&& lhs, RHS&& rhs) { + return create(ast::BinaryOp::kLogicalAnd, + Expr(std::forward(lhs)), + Expr(std::forward(rhs))); + } + + /// @param lhs the left hand argument to the logical or operation + /// @param rhs the right hand argument to the logical or operation + /// @returns a `ast::BinaryExpression` of `lhs` || `rhs` + template + const ast::BinaryExpression* LogicalOr(LHS&& lhs, RHS&& rhs) { + return create(ast::BinaryOp::kLogicalOr, + Expr(std::forward(lhs)), + Expr(std::forward(rhs))); + } + /// @param lhs the left hand argument to the greater than operation /// @param rhs the right hand argument to the greater than operation /// @returns a `ast::BinaryExpression` of `lhs` > `rhs` @@ -1819,6 +1839,17 @@ class ProgramBuilder { Expr(std::forward(rhs))); } + /// @param lhs the left hand argument to the not-equal expression + /// @param rhs the right hand argument to the not-equal expression + /// @returns a `ast::BinaryExpression` comparing `lhs` equal to `rhs` for + /// disequality + template + const ast::BinaryExpression* NotEqual(LHS&& lhs, RHS&& rhs) { + return create(ast::BinaryOp::kNotEqual, + Expr(std::forward(lhs)), + Expr(std::forward(rhs))); + } + /// @param source the source information /// @param obj the object for the index accessor expression /// @param idx the index argument for the index accessor expression diff --git a/src/tint/sem/type.cc b/src/tint/sem/type.cc index 88dbf14d52..c14ec69079 100644 --- a/src/tint/sem/type.cc +++ b/src/tint/sem/type.cc @@ -80,6 +80,12 @@ bool Type::is_float_matrix() const { return Is([](const Matrix* m) { return m->type()->is_float_scalar(); }); } +bool Type::is_square_float_matrix() const { + return Is([](const Matrix* m) { + return m->type()->is_float_scalar() && m->rows() == m->columns(); + }); +} + bool Type::is_float_vector() const { return Is([](const Vector* v) { return v->type()->is_float_scalar(); }); } diff --git a/src/tint/sem/type.h b/src/tint/sem/type.h index ae30e08753..c0abce3d76 100644 --- a/src/tint/sem/type.h +++ b/src/tint/sem/type.h @@ -77,6 +77,8 @@ class Type : public Castable { bool is_float_scalar() const; /// @returns true if this type is a float matrix bool is_float_matrix() const; + /// @returns true if this type is a square float matrix + bool is_square_float_matrix() const; /// @returns true if this type is a float vector bool is_float_vector() const; /// @returns true if this type is a float scalar or vector