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 <rharrison@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Alastair Donaldson <afdx@google.com> Auto-Submit: Alastair Donaldson <afdx@google.com>
This commit is contained in:
parent
1006b06c7d
commit
444e051faa
|
@ -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",
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <cassert>
|
||||
|
||||
#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> Mutation::FromMessage(
|
|||
case protobufs::Mutation::kReplaceIdentifier:
|
||||
return std::make_unique<MutationReplaceIdentifier>(
|
||||
message.replace_identifier());
|
||||
case protobufs::Mutation::kChangeBinaryOperator:
|
||||
return std::make_unique<MutationChangeBinaryOperator>(
|
||||
message.change_binary_operator());
|
||||
case protobufs::Mutation::MUTATION_NOT_SET:
|
||||
assert(false && "Mutation is not set");
|
||||
break;
|
||||
|
|
|
@ -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 <memory>
|
||||
#include <vector>
|
||||
|
||||
#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<ast::BinaryOp> 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<ast::BinaryExpression>(node);
|
||||
if (!binary_expr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get vector of all operators this could be replaced with.
|
||||
std::vector<ast::BinaryOp> 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<MutationChangeBinaryOperator>(
|
||||
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
|
|
@ -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_
|
|
@ -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 <utility>
|
||||
|
||||
#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<sem::Bool>();
|
||||
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<sem::Bool>();
|
||||
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<uint32_t>(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<sem::Reference>()
|
||||
? lhs_type->As<sem::Reference>()->StoreType()
|
||||
: lhs_type;
|
||||
const sem::Type* rhs_basic_type =
|
||||
rhs_type->Is<sem::Reference>()
|
||||
? rhs_type->As<sem::Reference>()->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<ast::BinaryExpression>(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<ast::BinaryOp>(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<ast::BinaryExpression>(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<ast::BinaryOp>(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
|
|
@ -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_
|
|
@ -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 <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#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<ast::VariableDeclStatement>()->variable;
|
||||
ASSERT_NE(a_var, nullptr);
|
||||
|
||||
auto a_var_id = node_id_map.GetId(a_var);
|
||||
|
||||
const auto* sum_expr = a_var->constructor->As<ast::BinaryExpression>();
|
||||
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<ast::VariableDeclStatement>()->variable;
|
||||
ASSERT_NE(a_var, nullptr);
|
||||
|
||||
const auto* sum_expr = a_var->constructor->As<ast::BinaryExpression>();
|
||||
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<ast::BinaryOp>& 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<ast::BinaryOp> 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<ast::VariableDeclStatement>()->variable;
|
||||
ASSERT_NE(r_var, nullptr);
|
||||
|
||||
const auto* binary_expr = r_var->constructor->As<ast::BinaryExpression>();
|
||||
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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
CheckMutations(type, type, type, op,
|
||||
{other_op, ast::BinaryOp::kMultiply,
|
||||
ast::BinaryOp::kDivide, ast::BinaryOp::kModulo});
|
||||
}
|
||||
for (std::string vector_type : {"vec2<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
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<f32>", "mat3x3<f32>", "mat4x4<f32>"}) {
|
||||
CheckMutations(square_matrix_type, square_matrix_type, square_matrix_type,
|
||||
op, {other_op, ast::BinaryOp::kMultiply});
|
||||
}
|
||||
for (std::string non_square_matrix_type :
|
||||
{"mat2x3<f32>", "mat2x4<f32>", "mat3x2<f32>", "mat3x4<f32>",
|
||||
"mat4x2<f32>", "mat4x3<f32>"}) {
|
||||
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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
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<f32>", "mat3x3<f32>", "mat4x4<f32>"}) {
|
||||
CheckMutations(square_matrix_type, square_matrix_type, square_matrix_type,
|
||||
ast::BinaryOp::kMultiply,
|
||||
{ast::BinaryOp::kAdd, ast::BinaryOp::kSubtract});
|
||||
}
|
||||
|
||||
CheckMutations("vec2<f32>", "mat2x2<f32>", "vec2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("vec2<f32>", "mat3x2<f32>", "vec3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("vec2<f32>", "mat4x2<f32>", "vec4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat2x2<f32>", "vec2<f32>", "vec2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x2<f32>", "mat3x2<f32>", "mat3x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x2<f32>", "mat4x2<f32>", "mat4x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat2x3<f32>", "vec2<f32>", "vec3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x3<f32>", "mat2x2<f32>", "mat2x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x3<f32>", "mat3x2<f32>", "mat3x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x3<f32>", "mat4x2<f32>", "mat4x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat2x4<f32>", "vec2<f32>", "vec4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x4<f32>", "mat2x2<f32>", "mat2x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x4<f32>", "mat3x2<f32>", "mat3x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat2x4<f32>", "mat4x2<f32>", "mat4x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("vec3<f32>", "mat2x3<f32>", "vec2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("vec3<f32>", "mat3x3<f32>", "vec3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("vec3<f32>", "mat4x3<f32>", "vec4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat3x2<f32>", "vec3<f32>", "vec2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x2<f32>", "mat2x3<f32>", "mat2x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x2<f32>", "mat3x3<f32>", "mat3x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x2<f32>", "mat4x3<f32>", "mat4x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat3x3<f32>", "vec3<f32>", "vec3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x3<f32>", "mat2x3<f32>", "mat2x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x3<f32>", "mat4x3<f32>", "mat4x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat3x4<f32>", "vec3<f32>", "vec4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x4<f32>", "mat2x3<f32>", "mat2x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x4<f32>", "mat3x3<f32>", "mat3x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat3x4<f32>", "mat4x3<f32>", "mat4x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("vec4<f32>", "mat2x4<f32>", "vec2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("vec4<f32>", "mat3x4<f32>", "vec3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("vec4<f32>", "mat4x4<f32>", "vec4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat4x2<f32>", "vec4<f32>", "vec2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x2<f32>", "mat2x4<f32>", "mat2x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x2<f32>", "mat3x4<f32>", "mat3x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x2<f32>", "mat4x4<f32>", "mat4x2<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat4x3<f32>", "vec4<f32>", "vec3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x3<f32>", "mat2x4<f32>", "mat2x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x3<f32>", "mat3x4<f32>", "mat3x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x3<f32>", "mat4x4<f32>", "mat4x3<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
|
||||
CheckMutations("mat4x4<f32>", "vec4<f32>", "vec4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x4<f32>", "mat2x4<f32>", "mat2x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
CheckMutations("mat4x4<f32>", "mat3x4<f32>", "mat3x4<f32>",
|
||||
ast::BinaryOp::kMultiply, {});
|
||||
}
|
||||
|
||||
TEST(ChangeBinaryOperatorTest, Divide) {
|
||||
for (std::string type : {"i32", "vec2<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<f32>", "vec3<f32>", "vec4<f32>"}) {
|
||||
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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
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<ast::BinaryOp> 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<i32>", "vec3<i32>", "vec4<i32>"}) {
|
||||
CheckMutations(type, type, type, op,
|
||||
allowed_replacement_operators_signed);
|
||||
}
|
||||
std::unordered_set<ast::BinaryOp> 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<u32>", "vec3<u32>", "vec4<u32>"}) {
|
||||
CheckMutations(type, type, type, op,
|
||||
allowed_replacement_operators_unsigned);
|
||||
}
|
||||
if (op != ast::BinaryOp::kXor) {
|
||||
for (std::string type :
|
||||
{"bool", "vec2<bool>", "vec3<bool>", "vec4<bool>"}) {
|
||||
std::unordered_set<ast::BinaryOp> 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 << "<bool>";
|
||||
}
|
||||
std::unordered_set<ast::BinaryOp> 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<ast::BinaryOp> 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 << "<bool>";
|
||||
std::unordered_set<ast::BinaryOp> 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 << "<bool>";
|
||||
}
|
||||
std::unordered_set<ast::BinaryOp> 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<ast::BinaryOp> 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 << "<u32>";
|
||||
}
|
||||
std::unordered_set<ast::BinaryOp> 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
|
|
@ -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 <string>
|
||||
|
||||
#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"
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<MutationFinderChangeBinaryOperators>(
|
||||
enable_all_mutations, probability_context, &result);
|
||||
MaybeAddFinder<MutationFinderReplaceIdentifiers>(
|
||||
enable_all_mutations, probability_context, &result);
|
||||
} while (result.empty());
|
||||
|
|
|
@ -21,12 +21,15 @@ namespace fuzzers {
|
|||
namespace ast_fuzzer {
|
||||
namespace {
|
||||
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90};
|
||||
const std::pair<uint32_t, uint32_t> 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");
|
||||
|
|
|
@ -55,6 +55,11 @@ class ProbabilityContext {
|
|||
return static_cast<size_t>(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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 <typename LHS, typename RHS>
|
||||
const ast::Expression* Div(LHS&& lhs, RHS&& rhs) {
|
||||
const ast::BinaryExpression* Div(LHS&& lhs, RHS&& rhs) {
|
||||
return create<ast::BinaryExpression>(ast::BinaryOp::kDivide,
|
||||
Expr(std::forward<LHS>(lhs)),
|
||||
Expr(std::forward<RHS>(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 <typename LHS, typename RHS>
|
||||
const ast::Expression* Mod(LHS&& lhs, RHS&& rhs) {
|
||||
const ast::BinaryExpression* Mod(LHS&& lhs, RHS&& rhs) {
|
||||
return create<ast::BinaryExpression>(ast::BinaryOp::kModulo,
|
||||
Expr(std::forward<LHS>(lhs)),
|
||||
Expr(std::forward<RHS>(rhs)));
|
||||
|
@ -1769,6 +1769,26 @@ class ProgramBuilder {
|
|||
Expr(std::forward<RHS>(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 <typename LHS, typename RHS>
|
||||
const ast::BinaryExpression* LogicalAnd(LHS&& lhs, RHS&& rhs) {
|
||||
return create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
|
||||
Expr(std::forward<LHS>(lhs)),
|
||||
Expr(std::forward<RHS>(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 <typename LHS, typename RHS>
|
||||
const ast::BinaryExpression* LogicalOr(LHS&& lhs, RHS&& rhs) {
|
||||
return create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
|
||||
Expr(std::forward<LHS>(lhs)),
|
||||
Expr(std::forward<RHS>(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>(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 <typename LHS, typename RHS>
|
||||
const ast::BinaryExpression* NotEqual(LHS&& lhs, RHS&& rhs) {
|
||||
return create<ast::BinaryExpression>(ast::BinaryOp::kNotEqual,
|
||||
Expr(std::forward<LHS>(lhs)),
|
||||
Expr(std::forward<RHS>(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
|
||||
|
|
|
@ -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(); });
|
||||
}
|
||||
|
|
|
@ -77,6 +77,8 @@ class Type : public Castable<Type, Node> {
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue