AST fuzzer: wrap unary operator
Add a mutation that wraps an expression in a unary operator.
Valid unary operators depend on the type of the expression.
Fixes: tint:1111
Change-Id: If5a63c5da7e3c212acbec4e838d6542303e59481
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/62000
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Alastair Donaldson <afdx@google.com>
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
index 70941ac..b6d205a 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
+++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
@@ -50,10 +50,14 @@
"mutation_finders/change_binary_operators.h",
"mutation_finders/replace_identifiers.cc",
"mutation_finders/replace_identifiers.h",
+ "mutation_finders/wrap_unary_operators.cc",
+ "mutation_finders/wrap_unary_operators.h",
"mutations/change_binary_operator.cc",
"mutations/change_binary_operator.h",
"mutations/replace_identifier.cc",
"mutations/replace_identifier.h",
+ "mutations/wrap_unary_operator.cc",
+ "mutations/wrap_unary_operator.h",
"mutator.cc",
"mutator.h",
"node_id_map.cc",
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
index a31d411..25dd3fc 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
+++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
@@ -42,8 +42,10 @@
mutation_finder.h
mutation_finders/change_binary_operators.h
mutation_finders/replace_identifiers.h
+ mutation_finders/wrap_unary_operators.h
mutations/change_binary_operator.h
mutations/replace_identifier.h
+ mutations/wrap_unary_operator.h
mutator.h
node_id_map.h
probability_context.h
@@ -59,8 +61,10 @@
mutation_finder.cc
mutation_finders/change_binary_operators.cc
mutation_finders/replace_identifiers.cc
+ mutation_finders/wrap_unary_operators.cc
mutations/change_binary_operator.cc
mutations/replace_identifier.cc
+ mutations/wrap_unary_operator.cc
mutator.cc
node_id_map.cc
probability_context.cc
@@ -97,7 +101,8 @@
if (${TINT_BUILD_TESTS})
set(TEST_SOURCES
mutations/change_binary_operator_test.cc
- mutations/replace_identifier_test.cc)
+ mutations/replace_identifier_test.cc
+ mutations/wrap_unary_operator_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 1c4fb2e..c3c89a6 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
@@ -18,6 +18,7 @@
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
namespace tint {
namespace fuzzers {
@@ -34,6 +35,9 @@
case protobufs::Mutation::kChangeBinaryOperator:
return std::make_unique<MutationChangeBinaryOperator>(
message.change_binary_operator());
+ case protobufs::Mutation::kWrapUnaryOperator:
+ return std::make_unique<MutationWrapUnaryOperator>(
+ message.wrap_unary_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/wrap_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
new file mode 100644
index 0000000..da026f1
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
@@ -0,0 +1,81 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h"
+
+#include <memory>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/statement.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationList MutationFinderWrapUnaryOperators::FindMutations(
+ const tint::Program& program,
+ NodeIdMap* node_id_map,
+ ProbabilityContext* probability_context) const {
+ MutationList result;
+
+ // Iterate through all ast nodes and for each expression node, try to wrap
+ // the inside a valid unary operator based on the type of the expression.
+ for (const auto* node : program.ASTNodes().Objects()) {
+ const auto* expr_ast_node = tint::As<ast::Expression>(node);
+
+ // Transformation applies only when the node represents a valid expression.
+ if (!expr_ast_node) {
+ continue;
+ }
+
+ const auto* expr_sem_node =
+ tint::As<sem::Expression>(program.Sem().Get(expr_ast_node));
+
+ // Transformation applies only when the semantic node for the given
+ // expression is present.
+ if (!expr_sem_node) {
+ continue;
+ }
+
+ std::vector<ast::UnaryOp> valid_operators =
+ MutationWrapUnaryOperator::GetValidUnaryWrapper(*expr_sem_node);
+
+ // Transformation only applies when there are available unary operators
+ // for the given expression.
+ if (valid_operators.empty()) {
+ continue;
+ }
+
+ ast::UnaryOp unary_op_wrapper =
+ valid_operators[probability_context->GetRandomIndex(valid_operators)];
+
+ result.push_back(std::make_unique<MutationWrapUnaryOperator>(
+ node_id_map->GetId(expr_ast_node), node_id_map->TakeFreshId(),
+ unary_op_wrapper));
+ }
+
+ return result;
+}
+
+uint32_t MutationFinderWrapUnaryOperators::GetChanceOfApplyingMutation(
+ ProbabilityContext* probability_context) const {
+ return probability_context->GetChanceOfWrappingUnaryOperators();
+}
+
+} // namespace ast_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h
new file mode 100644
index 0000000..02538fc
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// Looks for opportunities to apply
+/// `MutationFinderWrapUnaryOperators`.
+///
+/// For each expression in the module, try to wrap it within
+/// a unary operator.
+class MutationFinderWrapUnaryOperators : 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_WRAP_UNARY_OPERATORS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
new file mode 100644
index 0000000..1f5d5ae
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
@@ -0,0 +1,127 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationWrapUnaryOperator::MutationWrapUnaryOperator(
+ protobufs::MutationWrapUnaryOperator message)
+ : message_(std::move(message)) {}
+
+MutationWrapUnaryOperator::MutationWrapUnaryOperator(
+ uint32_t expression_id,
+ uint32_t fresh_id,
+ ast::UnaryOp unary_op_wrapper) {
+ message_.set_expression_id(expression_id);
+ message_.set_fresh_id(fresh_id);
+ message_.set_unary_op_wrapper(static_cast<uint32_t>(unary_op_wrapper));
+}
+
+bool MutationWrapUnaryOperator::IsApplicable(
+ const tint::Program& program,
+ const NodeIdMap& node_id_map) const {
+ // Check if id that will be assigned is fresh.
+ if (!node_id_map.IdIsFreshAndValid(message_.fresh_id())) {
+ return false;
+ }
+
+ const auto* expression_ast_node =
+ tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id()));
+
+ if (!expression_ast_node) {
+ // Either the node is not present with the given id or
+ // the node is not a valid expression type.
+ return false;
+ }
+
+ const auto* expression_sem_node =
+ tint::As<sem::Expression>(program.Sem().Get(expression_ast_node));
+
+ if (!expression_sem_node) {
+ // Semantic information for the expression ast node is not present
+ // or the semantic node is not a valid expression type node.
+ return false;
+ }
+
+ ast::UnaryOp unary_op_wrapper =
+ static_cast<ast::UnaryOp>(message_.unary_op_wrapper());
+
+ std::vector<ast::UnaryOp> valid_ops =
+ GetValidUnaryWrapper(*expression_sem_node);
+
+ // There is no available unary operator or |unary_op_wrapper| is a
+ // type that is not allowed for the given expression.
+ if (std::find(valid_ops.begin(), valid_ops.end(), unary_op_wrapper) ==
+ valid_ops.end()) {
+ return false;
+ }
+
+ return true;
+}
+
+void MutationWrapUnaryOperator::Apply(const NodeIdMap& node_id_map,
+ tint::CloneContext* clone_context,
+ NodeIdMap* new_node_id_map) const {
+ auto* expression_node =
+ tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id()));
+
+ auto* replacement_expression_node =
+ clone_context->dst->create<ast::UnaryOpExpression>(
+ static_cast<ast::UnaryOp>(message_.unary_op_wrapper()),
+ clone_context->Clone(expression_node));
+
+ clone_context->Replace(expression_node, replacement_expression_node);
+
+ new_node_id_map->Add(replacement_expression_node, message_.fresh_id());
+}
+
+protobufs::Mutation MutationWrapUnaryOperator::ToMessage() const {
+ protobufs::Mutation mutation;
+ *mutation.mutable_wrap_unary_operator() = message_;
+ return mutation;
+}
+
+std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper(
+ const sem::Expression& expr) {
+ const auto* expr_type = expr.Type();
+ if (expr_type->is_bool_scalar_or_vector()) {
+ return {ast::UnaryOp::kNot};
+ }
+
+ if (expr_type->is_signed_scalar_or_vector()) {
+ return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement};
+ }
+
+ if (expr_type->is_unsigned_scalar_or_vector()) {
+ return {ast::UnaryOp::kComplement};
+ }
+
+ if (expr_type->is_float_scalar_or_vector()) {
+ return {ast::UnaryOp::kNegation};
+ }
+
+ return {};
+}
+
+} // namespace ast_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
new file mode 100644
index 0000000..25fec00
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
@@ -0,0 +1,85 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
+
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
+
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// @see MutationWrapUnaryOperator::Apply
+class MutationWrapUnaryOperator : public Mutation {
+ public:
+ /// @brief Constructs an instance of this mutation from a protobuf message.
+ /// @param message - protobuf message
+ explicit MutationWrapUnaryOperator(
+ protobufs::MutationWrapUnaryOperator message);
+
+ /// @brief Constructor.
+ /// @param expression_id - the id of an expression.
+ /// @param fresh_id - a fresh id for the created expression node with
+ /// unary operator wrapper.
+ /// @param unary_op_wrapper - a `ast::UnaryOp` instance.
+ MutationWrapUnaryOperator(uint32_t expression_id,
+ uint32_t fresh_id,
+ ast::UnaryOp unary_op_wrapper);
+
+ /// @copybrief Mutation::IsApplicable
+ ///
+ /// The mutation is applicable iff:
+ /// - `expression_id` must refer to a valid expression that can be wrapped
+ /// with unary operator.
+ /// - `fresh_id` must be fresh.
+ /// - `unary_op_wrapper` is a unary expression that is valid based on the
+ /// type of the given expression.
+ ///
+ /// @copydetails Mutation::IsApplicable
+ bool IsApplicable(const tint::Program& program,
+ const NodeIdMap& node_id_map) const override;
+
+ /// @copybrief Mutation::Apply
+ ///
+ /// Wrap an expression in a unary operator that is valid based on
+ /// the type of the expression.
+ ///
+ /// @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;
+
+ /// Return list of unary operator wrappers allowed for the given
+ /// expression.
+ /// @param expr - an `ast::Expression` instance from node id map.
+ /// @return a list of unary operators.
+ static std::vector<ast::UnaryOp> GetValidUnaryWrapper(
+ const sem::Expression& expr);
+
+ private:
+ protobufs::MutationWrapUnaryOperator message_;
+};
+
+} // namespace ast_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
new file mode 100644
index 0000000..4b8435e
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
@@ -0,0 +1,548 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.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 {
+
+TEST(WrapUnaryOperatorTest, Applicable1) {
+ std::string content = R"(
+ fn main() {
+ var a = 5;
+ if (a < 5) {
+ a = 6;
+ }
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ auto expression_id = node_id_map.GetId(
+ main_fn_statements[1]->As<ast::IfStatement>()->condition);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNot),
+ 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() {
+ var a = 5;
+ if (!((a < 5))) {
+ a = 6;
+ }
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable2) {
+ std::string content = R"(
+ fn main() {
+ let a = vec3<bool>(true, false, true);
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNot),
+ 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 = !(vec3<bool>(true, false, true));
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable3) {
+ std::string content = R"(
+ fn main() {
+ var a : u32;
+ a = 6u;
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[1]->As<ast::AssignmentStatement>()->rhs;
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kComplement),
+ 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() {
+ var a : u32;
+ a = ~(6u);
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable4) {
+ std::string content = R"(
+ fn main() -> vec2<bool> {
+ var a = (vec2<u32> (1u, 2u) == vec2<u32> (1u, 2u));
+ return a;
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::BinaryExpression>()
+ ->lhs;
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kComplement),
+ 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() -> vec2<bool> {
+ var a = (~(vec2<u32>(1u, 2u)) == vec2<u32>(1u, 2u));
+ return a;
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable5) {
+ std::string content = R"(
+ fn main() {
+ let a : f32 = -(1.0);
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::UnaryOpExpression>()
+ ->expr;
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNegation),
+ 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 : f32 = -(-(1.0));
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable6) {
+ std::string content = R"(
+ fn main() {
+ var a : vec4<f32> = vec4<f32>(-1.0, -1.0, -1.0, -1.0);
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNegation),
+ 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() {
+ var a : vec4<f32> = -(vec4<f32>(-1.0, -1.0, -1.0, -1.0));
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable7) {
+ std::string content = R"(
+ fn main() {
+ var a = 1;
+ for(var i : i32 = 1; i < 5; i = i + 1) {
+ a = a + 1;
+ }
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[1]
+ ->As<ast::ForLoopStatement>()
+ ->initializer->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNegation),
+ 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() {
+ var a = 1;
+ for(var i : i32 = -(1); (i < 5); i = (i + 1)) {
+ a = (a + 1);
+ }
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable8) {
+ std::string content = R"(
+ fn main() {
+ var a : vec4<i32> = vec4<i32>(1, 0, -1, 0);
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ ASSERT_TRUE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kComplement),
+ 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() {
+ var a : vec4<i32> = ~(vec4<i32>(1, 0, -1, 0));
+}
+)";
+ ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable1) {
+ std::string content = R"(
+ fn main() {
+ let a = mat2x3<f32>(vec3<f32>(1.,0.,1.), vec3<f32>(0.,1.,0.));
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ // There is no unary operator that can be applied to matrix type.
+ ASSERT_FALSE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNegation),
+ node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable2) {
+ std::string content = R"(
+ fn main() {
+ let a = 1;
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ // Not cannot be applied to integer types.
+ ASSERT_FALSE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNot),
+ node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable3) {
+ std::string content = R"(
+ fn main() {
+ let a = vec2<u32>(1u, 2u);
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ // Negation cannot be applied to unsigned integer scalar or vectors.
+ ASSERT_FALSE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNegation),
+ node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable4) {
+ std::string content = R"(
+ fn main() {
+ let a = 1.5;
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ // Cannot wrap float types with complement operator.
+ ASSERT_FALSE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kComplement),
+ node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable5) {
+ std::string content = R"(
+ fn main() {
+ let a = 1.5;
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* expr = main_fn_statements[0]
+ ->As<ast::VariableDeclStatement>()
+ ->variable->constructor->As<ast::Expression>();
+
+ const auto expression_id = node_id_map.GetId(expr);
+ ASSERT_NE(expression_id, 0);
+
+ // Id for the replacement expression is not fresh.
+ ASSERT_FALSE(
+ MaybeApplyMutation(program,
+ MutationWrapUnaryOperator(expression_id, expression_id,
+ ast::UnaryOp::kNegation),
+ node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable6) {
+ std::string content = R"(
+ fn main() {
+ let a = 1.5;
+ }
+ )";
+ 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_statements =
+ program.AST().Functions()[0]->body->statements;
+
+ const auto* statement =
+ main_fn_statements[0]->As<ast::VariableDeclStatement>();
+
+ const auto statement_id = node_id_map.GetId(statement);
+ ASSERT_NE(statement_id, 0);
+
+ // The id provided for the expression is not a valid expression type.
+ ASSERT_FALSE(MaybeApplyMutation(
+ program,
+ MutationWrapUnaryOperator(statement_id, node_id_map.TakeFreshId(),
+ ast::UnaryOp::kNegation),
+ node_id_map, &program, &node_id_map, nullptr));
+}
+
+} // namespace
+} // namespace ast_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
index 63d62e2..ab73a3a 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
@@ -22,8 +22,8 @@
#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/mutation_finders/wrap_unary_operators.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
-
#include "src/tint/program_builder.h"
namespace tint {
@@ -50,6 +50,8 @@
enable_all_mutations, probability_context, &result);
MaybeAddFinder<MutationFinderReplaceIdentifiers>(
enable_all_mutations, probability_context, &result);
+ MaybeAddFinder<MutationFinderWrapUnaryOperators>(
+ enable_all_mutations, probability_context, &result);
} while (result.empty());
return result;
}
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
index 543a57b..117fd2d 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
@@ -51,7 +51,7 @@
}
}
-bool NodeIdMap::IdIsFreshAndValid(IdType id) {
+bool NodeIdMap::IdIsFreshAndValid(IdType id) const {
return id && !id_to_node_.count(id);
}
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
index 18dd96a..1aae93f 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
@@ -72,7 +72,7 @@
/// @param id - an id that is used to check in the map.
/// @return true the given id is fresh and valid (non-zero).
/// @return false otherwise.
- bool IdIsFreshAndValid(IdType id);
+ bool IdIsFreshAndValid(IdType id) const;
/// @brief Returns an id that is guaranteed to be unoccupied in this map.
///
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
index f41f5b9..1d9461f 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
@@ -23,6 +23,7 @@
const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfWrappingUnaryOperators = {30, 70};
} // namespace
@@ -31,7 +32,9 @@
chance_of_changing_binary_operators_(
RandomFromRange(kChanceOfChangingBinaryOperators)),
chance_of_replacing_identifiers_(
- RandomFromRange(kChanceOfReplacingIdentifiers)) {
+ RandomFromRange(kChanceOfReplacingIdentifiers)),
+ chance_of_wrapping_unary_operators_(
+ RandomFromRange(kChanceOfWrappingUnaryOperators)) {
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 b890aba..eacc1bd 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
@@ -65,6 +65,11 @@
return chance_of_replacing_identifiers_;
}
+ /// @return the probability of wrapping an expression in a unary operator.
+ uint32_t GetChanceOfWrappingUnaryOperators() const {
+ return chance_of_wrapping_unary_operators_;
+ }
+
private:
/// @param range - a pair of integers `a` and `b` s.t. `a <= b`.
/// @return an random number in the range `[a; b]`.
@@ -74,6 +79,7 @@
uint32_t chance_of_changing_binary_operators_;
uint32_t chance_of_replacing_identifiers_;
+ uint32_t chance_of_wrapping_unary_operators_;
};
} // namespace ast_fuzzer
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 e60e35d..0a5951f 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
@@ -20,6 +20,7 @@
oneof mutation {
MutationReplaceIdentifier replace_identifier = 1;
MutationChangeBinaryOperator change_binary_operator = 2;
+ MutationWrapUnaryOperator wrap_unary_operator = 3;
};
}
@@ -59,3 +60,17 @@
// A BinaryOp representing the new binary operator.
uint32 new_operator = 2;
}
+
+message MutationWrapUnaryOperator {
+ // This transformation wraps an expression with a allowed unary
+ // expression operator.
+
+ // The id of the expression.
+ uint32 expression_id = 1;
+
+ // A fresh id for the created unary expression.
+ uint32 fresh_id = 2;
+
+ // The unary operator to wrap the expression with.
+ uint32 unary_op_wrapper = 3;
+}